1use tracing::{debug, info};
7
8use crate::device::Device;
9use crate::error::{Error, Result};
10use crate::uuid::{CALIBRATION, COMMAND, READ_INTERVAL, SENSOR_STATE};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[repr(u8)]
15pub enum MeasurementInterval {
16 OneMinute = 0x01,
18 TwoMinutes = 0x02,
20 FiveMinutes = 0x05,
22 TenMinutes = 0x0A,
24}
25
26impl MeasurementInterval {
27 pub fn as_seconds(&self) -> u16 {
29 match self {
30 MeasurementInterval::OneMinute => 60,
31 MeasurementInterval::TwoMinutes => 120,
32 MeasurementInterval::FiveMinutes => 300,
33 MeasurementInterval::TenMinutes => 600,
34 }
35 }
36
37 pub fn from_seconds(seconds: u16) -> Option<Self> {
39 match seconds {
40 60 => Some(MeasurementInterval::OneMinute),
41 120 => Some(MeasurementInterval::TwoMinutes),
42 300 => Some(MeasurementInterval::FiveMinutes),
43 600 => Some(MeasurementInterval::TenMinutes),
44 _ => None,
45 }
46 }
47
48 pub fn from_minutes(minutes: u8) -> Option<Self> {
50 match minutes {
51 1 => Some(MeasurementInterval::OneMinute),
52 2 => Some(MeasurementInterval::TwoMinutes),
53 5 => Some(MeasurementInterval::FiveMinutes),
54 10 => Some(MeasurementInterval::TenMinutes),
55 _ => None,
56 }
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
62#[repr(u8)]
63pub enum BluetoothRange {
64 #[default]
66 Standard = 0x00,
67 Extended = 0x01,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
73pub enum TemperatureUnit {
74 #[default]
76 Celsius,
77 Fahrenheit,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
83pub enum RadonUnit {
84 #[default]
86 BqM3,
87 PciL,
89}
90
91#[derive(Debug, Clone, Default)]
93pub struct DeviceSettings {
94 pub smart_home_enabled: bool,
96 pub bluetooth_range: BluetoothRange,
98 pub temperature_unit: TemperatureUnit,
100 pub radon_unit: RadonUnit,
102 pub buzzer_enabled: bool,
104 pub auto_calibration_enabled: bool,
106}
107
108#[derive(Debug, Clone, Default)]
110pub struct CalibrationData {
111 pub raw: Vec<u8>,
113 pub co2_offset: Option<i16>,
115}
116
117impl Device {
118 pub async fn get_interval(&self) -> Result<MeasurementInterval> {
120 let data = self.read_characteristic(READ_INTERVAL).await?;
121
122 if data.len() < 2 {
123 return Err(Error::InvalidData("Invalid interval data".to_string()));
124 }
125
126 let seconds = u16::from_le_bytes([data[0], data[1]]);
127
128 MeasurementInterval::from_seconds(seconds)
129 .ok_or_else(|| Error::InvalidData(format!("Unknown interval: {} seconds", seconds)))
130 }
131
132 pub async fn set_interval(&self, interval: MeasurementInterval) -> Result<()> {
140 info!("Setting measurement interval to {:?}", interval);
141
142 let minutes = match interval {
144 MeasurementInterval::OneMinute => 0x01,
145 MeasurementInterval::TwoMinutes => 0x02,
146 MeasurementInterval::FiveMinutes => 0x05,
147 MeasurementInterval::TenMinutes => 0x0A,
148 };
149
150 let cmd = [0x90, minutes];
151 self.write_characteristic(COMMAND, &cmd).await?;
152
153 Ok(())
154 }
155
156 pub async fn set_interval_verified(&self, interval: MeasurementInterval) -> Result<()> {
167 self.set_interval(interval).await?;
168
169 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
171
172 let actual = self.get_interval().await?;
173 if actual != interval {
174 return Err(Error::WriteFailed {
175 uuid: COMMAND.to_string(),
176 reason: format!(
177 "Interval verification failed: expected {:?}, got {:?}",
178 interval, actual
179 ),
180 });
181 }
182
183 info!("Measurement interval verified: {:?}", interval);
184 Ok(())
185 }
186
187 pub async fn set_smart_home(&self, enabled: bool) -> Result<()> {
195 info!("Setting Smart Home integration to {}", enabled);
196
197 let cmd = [0x91, if enabled { 0x01 } else { 0x00 }];
199 self.write_characteristic(COMMAND, &cmd).await?;
200
201 Ok(())
202 }
203
204 pub async fn set_smart_home_verified(&self, enabled: bool) -> Result<()> {
214 self.set_smart_home(enabled).await?;
215
216 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
218
219 let settings = self.get_settings().await?;
220 if settings.smart_home_enabled != enabled {
221 return Err(Error::WriteFailed {
222 uuid: COMMAND.to_string(),
223 reason: format!(
224 "Smart Home verification failed: expected {}, got {}",
225 enabled, settings.smart_home_enabled
226 ),
227 });
228 }
229
230 info!("Smart Home integration verified: {}", enabled);
231 Ok(())
232 }
233
234 pub async fn set_bluetooth_range(&self, range: BluetoothRange) -> Result<()> {
239 info!("Setting Bluetooth range to {:?}", range);
240
241 let cmd = [0x92, range as u8];
243 self.write_characteristic(COMMAND, &cmd).await?;
244
245 Ok(())
246 }
247
248 pub async fn set_bluetooth_range_verified(&self, range: BluetoothRange) -> Result<()> {
258 self.set_bluetooth_range(range).await?;
259
260 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
262
263 let settings = self.get_settings().await?;
264 if settings.bluetooth_range != range {
265 return Err(Error::WriteFailed {
266 uuid: COMMAND.to_string(),
267 reason: format!(
268 "Bluetooth range verification failed: expected {:?}, got {:?}",
269 range, settings.bluetooth_range
270 ),
271 });
272 }
273
274 info!("Bluetooth range verified: {:?}", range);
275 Ok(())
276 }
277
278 pub async fn get_calibration(&self) -> Result<CalibrationData> {
280 let raw = self.read_characteristic(CALIBRATION).await?;
281
282 let co2_offset = if raw.len() >= 4 {
284 Some(i16::from_le_bytes([raw[2], raw[3]]))
285 } else {
286 None
287 };
288
289 Ok(CalibrationData { raw, co2_offset })
290 }
291
292 pub async fn get_settings(&self) -> Result<DeviceSettings> {
302 let data = self.read_characteristic(SENSOR_STATE).await?;
303
304 if data.len() < 3 {
305 return Err(Error::InvalidData(
306 "Sensor state data too short".to_string(),
307 ));
308 }
309
310 debug!("Sensor state raw: {:02x?} (len={})", data, data.len());
311
312 let device_type_byte = data[0];
317 let config_flags = data[1];
318 let option_flags = data[2];
319
320 let is_aranet4 = device_type_byte == 0xF1;
321 let is_aranet_radon = device_type_byte == 0xF3;
322 let is_aranet_radiation = device_type_byte == 0xF4;
323
324 let buzzer_enabled = (config_flags & 0x01) != 0;
329 let temp_bit = (config_flags >> 5) & 0x01;
330 let bit7 = (config_flags >> 7) & 0x01;
331
332 let temperature_unit = if is_aranet_radiation || temp_bit == 1 {
335 TemperatureUnit::Celsius
336 } else {
337 TemperatureUnit::Fahrenheit
338 };
339
340 let radon_unit = if is_aranet_radon {
342 if bit7 == 1 {
343 RadonUnit::BqM3
344 } else {
345 RadonUnit::PciL
346 }
347 } else {
348 RadonUnit::BqM3 };
350
351 let auto_calibration_enabled = is_aranet4 && bit7 == 1;
353
354 let bluetooth_range = if (option_flags >> 1) & 0x01 == 1 {
358 BluetoothRange::Extended
359 } else {
360 BluetoothRange::Standard
361 };
362
363 let smart_home_enabled = (option_flags >> 7) & 0x01 == 1;
364
365 debug!(
366 "Parsed settings: smart_home={}, bt_range={:?}, temp_unit={:?}, radon_unit={:?}",
367 smart_home_enabled, bluetooth_range, temperature_unit, radon_unit
368 );
369
370 Ok(DeviceSettings {
371 smart_home_enabled,
372 bluetooth_range,
373 temperature_unit,
374 radon_unit,
375 buzzer_enabled,
376 auto_calibration_enabled,
377 })
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
386 fn test_interval_from_seconds() {
387 assert_eq!(
388 MeasurementInterval::from_seconds(60),
389 Some(MeasurementInterval::OneMinute)
390 );
391 assert_eq!(
392 MeasurementInterval::from_seconds(120),
393 Some(MeasurementInterval::TwoMinutes)
394 );
395 assert_eq!(
396 MeasurementInterval::from_seconds(300),
397 Some(MeasurementInterval::FiveMinutes)
398 );
399 assert_eq!(
400 MeasurementInterval::from_seconds(600),
401 Some(MeasurementInterval::TenMinutes)
402 );
403 assert_eq!(MeasurementInterval::from_seconds(100), None);
404 }
405
406 #[test]
407 fn test_interval_from_minutes() {
408 assert_eq!(
409 MeasurementInterval::from_minutes(1),
410 Some(MeasurementInterval::OneMinute)
411 );
412 assert_eq!(
413 MeasurementInterval::from_minutes(2),
414 Some(MeasurementInterval::TwoMinutes)
415 );
416 assert_eq!(
417 MeasurementInterval::from_minutes(5),
418 Some(MeasurementInterval::FiveMinutes)
419 );
420 assert_eq!(
421 MeasurementInterval::from_minutes(10),
422 Some(MeasurementInterval::TenMinutes)
423 );
424 assert_eq!(MeasurementInterval::from_minutes(3), None);
425 }
426
427 #[test]
428 fn test_interval_as_seconds() {
429 assert_eq!(MeasurementInterval::OneMinute.as_seconds(), 60);
430 assert_eq!(MeasurementInterval::TwoMinutes.as_seconds(), 120);
431 assert_eq!(MeasurementInterval::FiveMinutes.as_seconds(), 300);
432 assert_eq!(MeasurementInterval::TenMinutes.as_seconds(), 600);
433 }
434}