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<()> {
137 info!("Setting measurement interval to {:?}", interval);
138
139 let minutes = match interval {
141 MeasurementInterval::OneMinute => 0x01,
142 MeasurementInterval::TwoMinutes => 0x02,
143 MeasurementInterval::FiveMinutes => 0x05,
144 MeasurementInterval::TenMinutes => 0x0A,
145 };
146
147 let cmd = [0x90, minutes];
148 self.write_characteristic(COMMAND, &cmd).await?;
149
150 Ok(())
151 }
152
153 pub async fn set_smart_home(&self, enabled: bool) -> Result<()> {
158 info!("Setting Smart Home integration to {}", enabled);
159
160 let cmd = [0x91, if enabled { 0x01 } else { 0x00 }];
162 self.write_characteristic(COMMAND, &cmd).await?;
163
164 Ok(())
165 }
166
167 pub async fn set_bluetooth_range(&self, range: BluetoothRange) -> Result<()> {
169 info!("Setting Bluetooth range to {:?}", range);
170
171 let cmd = [0x92, range as u8];
173 self.write_characteristic(COMMAND, &cmd).await?;
174
175 Ok(())
176 }
177
178 pub async fn get_calibration(&self) -> Result<CalibrationData> {
180 let raw = self.read_characteristic(CALIBRATION).await?;
181
182 let co2_offset = if raw.len() >= 4 {
184 Some(i16::from_le_bytes([raw[2], raw[3]]))
185 } else {
186 None
187 };
188
189 Ok(CalibrationData { raw, co2_offset })
190 }
191
192 pub async fn get_settings(&self) -> Result<DeviceSettings> {
202 let data = self.read_characteristic(SENSOR_STATE).await?;
203
204 if data.len() < 3 {
205 return Err(Error::InvalidData(
206 "Sensor state data too short".to_string(),
207 ));
208 }
209
210 debug!("Sensor state raw: {:02x?} (len={})", data, data.len());
211
212 let device_type_byte = data[0];
217 let config_flags = data[1];
218 let option_flags = data[2];
219
220 let is_aranet4 = device_type_byte == 0xF1;
221 let is_aranet_radon = device_type_byte == 0xF3;
222 let is_aranet_radiation = device_type_byte == 0xF4;
223
224 let buzzer_enabled = (config_flags & 0x01) != 0;
229 let temp_bit = (config_flags >> 5) & 0x01;
230 let bit7 = (config_flags >> 7) & 0x01;
231
232 let temperature_unit = if is_aranet_radiation || temp_bit == 1 {
235 TemperatureUnit::Celsius
236 } else {
237 TemperatureUnit::Fahrenheit
238 };
239
240 let radon_unit = if is_aranet_radon {
242 if bit7 == 1 {
243 RadonUnit::BqM3
244 } else {
245 RadonUnit::PciL
246 }
247 } else {
248 RadonUnit::BqM3 };
250
251 let auto_calibration_enabled = is_aranet4 && bit7 == 1;
253
254 let bluetooth_range = if (option_flags >> 1) & 0x01 == 1 {
258 BluetoothRange::Extended
259 } else {
260 BluetoothRange::Standard
261 };
262
263 let smart_home_enabled = (option_flags >> 7) & 0x01 == 1;
264
265 debug!(
266 "Parsed settings: smart_home={}, bt_range={:?}, temp_unit={:?}, radon_unit={:?}",
267 smart_home_enabled, bluetooth_range, temperature_unit, radon_unit
268 );
269
270 Ok(DeviceSettings {
271 smart_home_enabled,
272 bluetooth_range,
273 temperature_unit,
274 radon_unit,
275 buzzer_enabled,
276 auto_calibration_enabled,
277 })
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn test_interval_from_seconds() {
287 assert_eq!(
288 MeasurementInterval::from_seconds(60),
289 Some(MeasurementInterval::OneMinute)
290 );
291 assert_eq!(
292 MeasurementInterval::from_seconds(120),
293 Some(MeasurementInterval::TwoMinutes)
294 );
295 assert_eq!(
296 MeasurementInterval::from_seconds(300),
297 Some(MeasurementInterval::FiveMinutes)
298 );
299 assert_eq!(
300 MeasurementInterval::from_seconds(600),
301 Some(MeasurementInterval::TenMinutes)
302 );
303 assert_eq!(MeasurementInterval::from_seconds(100), None);
304 }
305
306 #[test]
307 fn test_interval_from_minutes() {
308 assert_eq!(
309 MeasurementInterval::from_minutes(1),
310 Some(MeasurementInterval::OneMinute)
311 );
312 assert_eq!(
313 MeasurementInterval::from_minutes(2),
314 Some(MeasurementInterval::TwoMinutes)
315 );
316 assert_eq!(
317 MeasurementInterval::from_minutes(5),
318 Some(MeasurementInterval::FiveMinutes)
319 );
320 assert_eq!(
321 MeasurementInterval::from_minutes(10),
322 Some(MeasurementInterval::TenMinutes)
323 );
324 assert_eq!(MeasurementInterval::from_minutes(3), None);
325 }
326
327 #[test]
328 fn test_interval_as_seconds() {
329 assert_eq!(MeasurementInterval::OneMinute.as_seconds(), 60);
330 assert_eq!(MeasurementInterval::TwoMinutes.as_seconds(), 120);
331 assert_eq!(MeasurementInterval::FiveMinutes.as_seconds(), 300);
332 assert_eq!(MeasurementInterval::TenMinutes.as_seconds(), 600);
333 }
334}