1use tracing::info;
7
8use crate::device::Device;
9use crate::error::{Error, Result};
10use crate::uuid::{CALIBRATION, COMMAND, READ_INTERVAL};
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)]
62#[repr(u8)]
63pub enum BluetoothRange {
64 Standard = 0x00,
66 Extended = 0x01,
68}
69
70#[derive(Debug, Clone)]
72pub struct DeviceSettings {
73 pub interval: MeasurementInterval,
75 pub smart_home_enabled: bool,
77 pub bluetooth_range: BluetoothRange,
79}
80
81#[derive(Debug, Clone, Default)]
83pub struct CalibrationData {
84 pub raw: Vec<u8>,
86 pub co2_offset: Option<i16>,
88}
89
90impl Device {
91 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 pub async fn set_interval(&self, interval: MeasurementInterval) -> Result<()> {
110 info!("Setting measurement interval to {:?}", interval);
111
112 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 pub async fn set_smart_home(&self, enabled: bool) -> Result<()> {
131 info!("Setting Smart Home integration to {}", enabled);
132
133 let cmd = [0x91, if enabled { 0x01 } else { 0x00 }];
135 self.write_characteristic(COMMAND, &cmd).await?;
136
137 Ok(())
138 }
139
140 pub async fn set_bluetooth_range(&self, range: BluetoothRange) -> Result<()> {
142 info!("Setting Bluetooth range to {:?}", range);
143
144 let cmd = [0x92, range as u8];
146 self.write_characteristic(COMMAND, &cmd).await?;
147
148 Ok(())
149 }
150
151 pub async fn get_calibration(&self) -> Result<CalibrationData> {
153 let raw = self.read_characteristic(CALIBRATION).await?;
154
155 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}