aranet_core/
settings.rs

1//! Device settings read/write.
2//!
3//! This module provides functionality to read and modify device
4//! settings on Aranet sensors.
5
6use tracing::info;
7
8use crate::device::Device;
9use crate::error::{Error, Result};
10use crate::uuid::{CALIBRATION, COMMAND, READ_INTERVAL};
11
12/// Measurement interval options.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[repr(u8)]
15pub enum MeasurementInterval {
16    /// 1 minute interval.
17    OneMinute = 0x01,
18    /// 2 minute interval.
19    TwoMinutes = 0x02,
20    /// 5 minute interval.
21    FiveMinutes = 0x05,
22    /// 10 minute interval.
23    TenMinutes = 0x0A,
24}
25
26impl MeasurementInterval {
27    /// Get the interval in seconds.
28    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    /// Try to create from seconds value.
38    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    /// Try to create from minutes value.
49    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/// Bluetooth range options.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62#[repr(u8)]
63pub enum BluetoothRange {
64    /// Standard range.
65    Standard = 0x00,
66    /// Extended range.
67    Extended = 0x01,
68}
69
70/// Device settings.
71#[derive(Debug, Clone)]
72pub struct DeviceSettings {
73    /// Current measurement interval.
74    pub interval: MeasurementInterval,
75    /// Smart Home integration enabled.
76    pub smart_home_enabled: bool,
77    /// Bluetooth range setting.
78    pub bluetooth_range: BluetoothRange,
79}
80
81/// Calibration data from the device.
82#[derive(Debug, Clone, Default)]
83pub struct CalibrationData {
84    /// Raw calibration bytes.
85    pub raw: Vec<u8>,
86    /// CO2 calibration offset (if available).
87    pub co2_offset: Option<i16>,
88}
89
90impl Device {
91    /// Get the current measurement interval.
92    pub async fn get_interval(&self) -> Result<MeasurementInterval> {
93        let data = self.read_characteristic(READ_INTERVAL).await?;
94
95        if data.len() < 2 {
96            return Err(Error::InvalidData("Invalid interval data".to_string()));
97        }
98
99        let seconds = u16::from_le_bytes([data[0], data[1]]);
100
101        MeasurementInterval::from_seconds(seconds)
102            .ok_or_else(|| Error::InvalidData(format!("Unknown interval: {} seconds", seconds)))
103    }
104
105    /// Set the measurement interval.
106    ///
107    /// The device will start using the new interval after the current
108    /// measurement cycle completes.
109    pub async fn set_interval(&self, interval: MeasurementInterval) -> Result<()> {
110        info!("Setting measurement interval to {:?}", interval);
111
112        // Command format: 0x90 XX (XX = interval in minutes)
113        let minutes = match interval {
114            MeasurementInterval::OneMinute => 0x01,
115            MeasurementInterval::TwoMinutes => 0x02,
116            MeasurementInterval::FiveMinutes => 0x05,
117            MeasurementInterval::TenMinutes => 0x0A,
118        };
119
120        let cmd = [0x90, minutes];
121        self.write_characteristic(COMMAND, &cmd).await?;
122
123        Ok(())
124    }
125
126    /// Enable or disable Smart Home integration.
127    ///
128    /// When enabled, the device advertises sensor data that can be read
129    /// without connecting (passive scanning).
130    pub async fn set_smart_home(&self, enabled: bool) -> Result<()> {
131        info!("Setting Smart Home integration to {}", enabled);
132
133        // Command format: 0x91 XX (XX = 00 disabled, 01 enabled)
134        let cmd = [0x91, if enabled { 0x01 } else { 0x00 }];
135        self.write_characteristic(COMMAND, &cmd).await?;
136
137        Ok(())
138    }
139
140    /// Set the Bluetooth range.
141    pub async fn set_bluetooth_range(&self, range: BluetoothRange) -> Result<()> {
142        info!("Setting Bluetooth range to {:?}", range);
143
144        // Command format: 0x92 XX (XX = 00 standard, 01 extended)
145        let cmd = [0x92, range as u8];
146        self.write_characteristic(COMMAND, &cmd).await?;
147
148        Ok(())
149    }
150
151    /// Read calibration data from the device.
152    pub async fn get_calibration(&self) -> Result<CalibrationData> {
153        let raw = self.read_characteristic(CALIBRATION).await?;
154
155        // Parse CO2 offset if available (typically at offset 2-3)
156        let co2_offset = if raw.len() >= 4 {
157            Some(i16::from_le_bytes([raw[2], raw[3]]))
158        } else {
159            None
160        };
161
162        Ok(CalibrationData { raw, co2_offset })
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn test_interval_from_seconds() {
172        assert_eq!(
173            MeasurementInterval::from_seconds(60),
174            Some(MeasurementInterval::OneMinute)
175        );
176        assert_eq!(
177            MeasurementInterval::from_seconds(120),
178            Some(MeasurementInterval::TwoMinutes)
179        );
180        assert_eq!(
181            MeasurementInterval::from_seconds(300),
182            Some(MeasurementInterval::FiveMinutes)
183        );
184        assert_eq!(
185            MeasurementInterval::from_seconds(600),
186            Some(MeasurementInterval::TenMinutes)
187        );
188        assert_eq!(MeasurementInterval::from_seconds(100), None);
189    }
190
191    #[test]
192    fn test_interval_from_minutes() {
193        assert_eq!(
194            MeasurementInterval::from_minutes(1),
195            Some(MeasurementInterval::OneMinute)
196        );
197        assert_eq!(
198            MeasurementInterval::from_minutes(2),
199            Some(MeasurementInterval::TwoMinutes)
200        );
201        assert_eq!(
202            MeasurementInterval::from_minutes(5),
203            Some(MeasurementInterval::FiveMinutes)
204        );
205        assert_eq!(
206            MeasurementInterval::from_minutes(10),
207            Some(MeasurementInterval::TenMinutes)
208        );
209        assert_eq!(MeasurementInterval::from_minutes(3), None);
210    }
211
212    #[test]
213    fn test_interval_as_seconds() {
214        assert_eq!(MeasurementInterval::OneMinute.as_seconds(), 60);
215        assert_eq!(MeasurementInterval::TwoMinutes.as_seconds(), 120);
216        assert_eq!(MeasurementInterval::FiveMinutes.as_seconds(), 300);
217        assert_eq!(MeasurementInterval::TenMinutes.as_seconds(), 600);
218    }
219}