1use std::io::Cursor;
3
4use binrw::{BinRead, BinWrite};
5use btleplug::api::{Characteristic, Peripheral as _};
6
7use crate::*;
8
9mod battery;
10mod current_temp;
11mod dsk;
12mod last_location;
13mod liquid_level;
14mod liquid_state;
15mod mug_color;
16mod mug_meta;
17mod name;
18mod ota;
19mod push_events;
20mod target_temp;
21mod temperature_unit;
22mod time_date_zone;
23
24pub use battery::Battery;
25pub use last_location::LastLocation;
26pub use liquid_level::LiquidLevel;
27pub use liquid_state::LiquidState;
28pub use mug_color::Color;
29pub use mug_meta::MugMeta;
30pub use ota::Ota;
31pub use push_events::PushEvent;
32pub use temperature_unit::TemperatureUnit;
33pub use time_date_zone::TimeDateZone;
34
35pub(crate) type Peripheral = <btleplug::platform::Adapter as btleplug::api::Central>::Peripheral;
36
37#[derive(Clone)]
41pub struct EmberMug {
42 peripheral: std::sync::Arc<EmberMugInner>,
44 characteristics: std::collections::BTreeSet<Characteristic>,
46 adapter: btleplug::platform::Adapter,
48}
49
50#[derive(Clone)]
51struct EmberMugInner(Peripheral);
52
53impl std::ops::Deref for EmberMugInner {
54 type Target = Peripheral;
55 fn deref(&self) -> &Self::Target {
56 &self.0
57 }
58}
59
60impl Drop for EmberMugInner {
61 fn drop(&mut self) {
62 let peripheral = self.0.clone();
63 futures::executor::block_on(async move {
64 let _ = peripheral.disconnect().await;
65 });
66 }
67}
68
69impl EmberMug {
70 pub async fn find_and_connect() -> Result<Self, ConnectError> {
72 use futures::TryStreamExt;
73 let mut stream = Box::pin(crate::btle::get_mugs().await?);
75 let Some((adapter, mug)) = stream.try_next().await? else {
76 return Err(ConnectError::NoDevice);
77 };
78 Self::connect_mug(adapter, mug).await
79 }
80
81 pub async fn connect_mug(
83 adapter: btleplug::platform::Adapter,
84 peripheral: Peripheral,
85 ) -> Result<Self, ConnectError> {
86 tracing::debug!(peripheral.address = ?peripheral.address(), peripheral.id = ?peripheral.id(), "connecting to mug");
87 peripheral.connect().await?;
88 peripheral.discover_services().await?;
89 Ok(Self {
90 characteristics: peripheral.characteristics(),
91 peripheral: std::sync::Arc::new(EmberMugInner(peripheral)),
92 adapter,
93 })
94 }
95
96 pub async fn is_connected(&self) -> Result<bool, btleplug::Error> {
98 match tokio::time::timeout(
99 std::time::Duration::from_secs(1),
100 self.peripheral.is_connected(),
101 )
102 .await
103 {
104 Ok(r) => r,
105 Err(_e) => Ok(false),
106 }
107 }
108
109 pub async fn disconnected(&self) -> Result<(), btleplug::Error> {
111 use btleplug::api::Central as _;
112 use futures::StreamExt;
113 let peripheral_id = std::sync::Arc::new(self.peripheral.id());
114 let mut stream = Box::pin(self.adapter.events().await?.filter_map(move |e| {
115 let peripheral_id = peripheral_id.clone();
116 async move {
117 match e {
118 btleplug::api::CentralEvent::DeviceDisconnected(id)
119 if &id == peripheral_id.as_ref() =>
120 {
121 Some(())
122 }
123 _ => None,
124 }
125 }
126 }));
127 stream.next().await;
128 Ok(())
129 }
130}
131
132impl EmberMug {
133 pub fn get_characteristic(&self, uuid: &uuid::Uuid) -> Option<&Characteristic> {
135 self.get_characteristic_on_service(uuid, &crate::EMBER_MUG_SERVICE_UUID)
136 }
137
138 pub fn get_characteristics(&self) -> impl Iterator<Item = &Characteristic> {
140 self.characteristics.iter()
141 }
142
143 pub fn get_characteristic_on_service(
145 &self,
146 uuid: &uuid::Uuid,
147 service_uuid: &uuid::Uuid,
148 ) -> Option<&Characteristic> {
149 self.characteristics
150 .iter()
151 .find(|&c| &c.uuid == uuid && &c.service_uuid == service_uuid)
152 }
153}
154
155impl EmberMug {
156 pub async fn read_deserialize<T: BinRead + binrw::meta::ReadEndian>(
158 &self,
159 uuid: &KnownCharacteristic,
160 ) -> Result<T, ReadError>
161 where
162 for<'a> T::Args<'a>: Default,
163 {
164 T::read(&mut Cursor::new(self.read(uuid).await?)).map_err(Into::into)
165 }
166
167 pub async fn read(&self, uuid: &KnownCharacteristic) -> Result<Vec<u8>, ReadError> {
169 let characteristic = self
170 .get_characteristic(&uuid.get())
171 .ok_or_else(|| ReadError::NoSuchCharacteristic(*uuid))?;
172 self.peripheral
173 .read(characteristic)
174 .await
175 .map_err(|e| ReadError::ReadOperation(e, characteristic.uuid, Some(*uuid)))
176 }
177
178 pub async fn write<D>(
180 &self,
181 write: btleplug::api::WriteType,
182 uuid: &KnownCharacteristic,
183 data: &D,
184 ) -> Result<(), WriteError>
185 where
186 D: BinWrite + binrw::meta::WriteEndian + Send + Sync,
187 for<'a> <D as BinWrite>::Args<'a>: Default,
188 {
189 let mut buf = Cursor::new(vec![]);
190 data.write(&mut buf)?;
191 let characteristic = self
192 .get_characteristic(&uuid.get())
193 .ok_or_else(|| WriteError::NoSuchCharacteristic(*uuid))?;
194 self.peripheral
195 .write(characteristic, buf.get_ref(), write)
196 .await
197 .map_err(|e| WriteError::WriteOperation(e, characteristic.uuid, Some(*uuid)))
198 }
199
200 pub async fn command<D>(&self, uuid: &KnownCharacteristic, data: &D) -> Result<(), WriteError>
202 where
203 D: BinWrite + binrw::meta::WriteEndian + Send + Sync,
204 for<'a> <D as BinWrite>::Args<'a>: Default,
205 {
206 self.write(btleplug::api::WriteType::WithoutResponse, uuid, data)
207 .await
208 }
209
210 pub async fn request<D>(&self, uuid: &KnownCharacteristic, data: &D) -> Result<(), WriteError>
212 where
213 D: BinWrite + binrw::meta::WriteEndian + Send + Sync,
214 for<'a> <D as BinWrite>::Args<'a>: Default,
215 {
216 self.write(btleplug::api::WriteType::WithResponse, uuid, data)
217 .await
218 }
219}
220
221#[derive(BinRead, BinWrite, Debug)]
222#[cfg_attr(
223 feature = "serde",
224 derive(serde::Deserialize, serde::Serialize),
225 serde(transparent)
226)]
227#[br(little)]
228#[bw(little)]
229pub struct Temperature {
231 pub temperature: u16,
233}
234
235impl Temperature {
236 pub fn to_degree(&self) -> f32 {
238 f32::from(self.temperature) * 0.01
239 }
240
241 pub fn from_degree(deg: f32) -> Self {
243 Self {
244 temperature: (deg * 100.0) as u16,
245 }
246 }
247}
248
249impl std::fmt::Display for Temperature {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 write!(f, "{:.1}", self.to_degree())
252 }
253}