1use embedded_hal::delay::DelayNs;
2use embedded_hal::i2c::I2c;
3
4use crate::crc::crc8;
5use crate::types::{Measurement, RawMeasurement};
6use crate::Error;
7
8pub const DEFAULT_ADDRESS: u8 = 0x62;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16#[repr(u16)]
17enum Command {
18 StartPeriodicMeasurement = 0x21B1,
19 ReadMeasurement = 0xEC05,
20 StopPeriodicMeasurement = 0x3F86,
21 GetDataReadyStatus = 0xE4B8,
22 GetSerialNumber = 0x3682,
23
24 MeasureSingleShot = 0x219D,
26 MeasureSingleShotRhtOnly = 0x2196,
27 PowerDown = 0x36E0,
28 WakeUp = 0x36F6,
29
30 SetTemperatureOffset = 0x241D,
32 GetTemperatureOffset = 0x2318,
33 SetSensorAltitude = 0x2427,
34 GetSensorAltitude = 0x2322,
35 SetAmbientPressure = 0xE000,
36 PersistSettings = 0x3615,
37 Reinit = 0x3646,
38 PerformSelfTest = 0x3639,
39 PerformFactoryReset = 0x3632,
40 PerformForcedRecalibration = 0x362F,
41 SetAutomaticSelfCalibrationEnabled = 0x2416,
42 GetAutomaticSelfCalibrationEnabled = 0x2313,
43}
44
45impl Command {
46 const fn delay_ms(self) -> u32 {
47 match self {
48 Self::StartPeriodicMeasurement => 1,
49 Self::ReadMeasurement => 1,
50 Self::StopPeriodicMeasurement => 500,
51 Self::GetDataReadyStatus => 1,
52 Self::GetSerialNumber => 1,
53 Self::MeasureSingleShot => 5000,
54 Self::MeasureSingleShotRhtOnly => 50,
55 Self::PowerDown => 1,
56 Self::WakeUp => 20,
57 Self::SetTemperatureOffset => 1,
58 Self::GetTemperatureOffset => 1,
59 Self::SetSensorAltitude => 1,
60 Self::GetSensorAltitude => 1,
61 Self::SetAmbientPressure => 1,
62 Self::PersistSettings => 800,
63 Self::Reinit => 20,
64 Self::PerformSelfTest => 10_000,
65 Self::PerformFactoryReset => 1200,
66 Self::PerformForcedRecalibration => 400,
67 Self::SetAutomaticSelfCalibrationEnabled => 1,
68 Self::GetAutomaticSelfCalibrationEnabled => 1,
69 }
70 }
71}
72
73#[derive(Debug)]
92pub struct Scd41<I2C, D> {
93 i2c: I2C,
94 delay: D,
95 address: u8,
96 is_running: bool,
97}
98
99impl<I2C, D> Scd41<I2C, D> {
100 #[must_use]
102 pub fn new(i2c: I2C, delay: D) -> Self {
103 Self::new_with_address(i2c, delay, DEFAULT_ADDRESS)
104 }
105
106 #[must_use]
108 pub fn new_with_address(i2c: I2C, delay: D, address: u8) -> Self {
109 Self {
110 i2c,
111 delay,
112 address,
113 is_running: false,
114 }
115 }
116
117 #[must_use]
119 pub fn destroy(self) -> I2C {
120 self.i2c
121 }
122}
123
124impl<I2C, D, E> Scd41<I2C, D>
125where
126 I2C: I2c<Error = E>,
127 D: DelayNs,
128{
129 pub fn start_periodic_measurement(&mut self) -> Result<(), Error<E>> {
138 self.write_command(Command::StartPeriodicMeasurement)?;
139 self.is_running = true;
140 Ok(())
141 }
142
143 pub fn stop_periodic_measurement(&mut self) -> Result<(), Error<E>> {
145 self.write_command(Command::StopPeriodicMeasurement)?;
146 self.is_running = false;
147 Ok(())
148 }
149
150 pub fn data_ready(&mut self) -> Result<bool, Error<E>> {
152 let status = self.read_u16(Command::GetDataReadyStatus)?;
153 Ok((status & 0x07FF) != 0)
154 }
155
156 pub fn measurement(&mut self) -> Result<Measurement, Error<E>> {
158 let raw = self.raw_measurement()?;
159 Ok(Measurement::from_raw(raw))
160 }
161
162 pub fn raw_measurement(&mut self) -> Result<RawMeasurement, Error<E>> {
166 let mut buf = [0u8; 9];
167 self.read_words(Command::ReadMeasurement, &mut buf)?;
168
169 let co2 = u16::from_be_bytes([buf[0], buf[1]]);
170 let t = u16::from_be_bytes([buf[3], buf[4]]);
171 let rh = u16::from_be_bytes([buf[6], buf[7]]);
172
173 Ok(RawMeasurement {
174 co2_ppm: co2,
175 temperature_ticks: t,
176 humidity_ticks: rh,
177 })
178 }
179
180 pub fn measure_single_shot(&mut self) -> Result<(), Error<E>> {
183 self.write_command(Command::MeasureSingleShot)
184 }
185
186 pub fn measure_single_shot_rht_only(&mut self) -> Result<(), Error<E>> {
188 self.write_command(Command::MeasureSingleShotRhtOnly)
189 }
190
191 pub fn power_down(&mut self) -> Result<(), Error<E>> {
195 self.write_command(Command::PowerDown)
196 }
197
198 pub fn wake_up_best_effort(&mut self) {
201 let _ = self.write_command(Command::WakeUp);
202 }
203
204 pub fn serial_number(&mut self) -> Result<u64, Error<E>> {
206 let mut buf = [0u8; 9];
207 self.read_words(Command::GetSerialNumber, &mut buf)?;
208
209 let w0 = u16::from_be_bytes([buf[0], buf[1]]) as u64;
210 let w1 = u16::from_be_bytes([buf[3], buf[4]]) as u64;
211 let w2 = u16::from_be_bytes([buf[6], buf[7]]) as u64;
212
213 Ok((w0 << 32) | (w1 << 16) | w2)
214 }
215
216 pub fn temperature_offset(&mut self) -> Result<f32, Error<E>> {
218 let raw = self.read_u16(Command::GetTemperatureOffset)?;
219 Ok((raw as f32) * 175.0 / 65536.0)
220 }
221
222 pub fn set_temperature_offset(&mut self, offset_c: f32) -> Result<(), Error<E>> {
224 if !(0.0..=175.0).contains(&offset_c) {
225 return Err(Error::InvalidInput);
226 }
227 let raw = ((offset_c * 65536.0) / 175.0) as u16;
228 self.write_command_with_u16(Command::SetTemperatureOffset, raw)
229 }
230
231 pub fn altitude(&mut self) -> Result<u16, Error<E>> {
233 self.read_u16(Command::GetSensorAltitude)
234 }
235
236 pub fn set_altitude(&mut self, meters: u16) -> Result<(), Error<E>> {
238 self.write_command_with_u16(Command::SetSensorAltitude, meters)
239 }
240
241 pub fn set_ambient_pressure(&mut self, pressure_hpa: u16) -> Result<(), Error<E>> {
243 self.write_command_with_u16(Command::SetAmbientPressure, pressure_hpa)
244 }
245
246 pub fn set_automatic_self_calibration(&mut self, enabled: bool) -> Result<(), Error<E>> {
250 self.write_command_with_u16(Command::SetAutomaticSelfCalibrationEnabled, enabled as u16)
251 }
252
253 pub fn automatic_self_calibration(&mut self) -> Result<bool, Error<E>> {
255 Ok(self.read_u16(Command::GetAutomaticSelfCalibrationEnabled)? != 0)
256 }
257
258 pub fn persist_settings(&mut self) -> Result<(), Error<E>> {
260 self.write_command(Command::PersistSettings)
261 }
262
263 pub fn reinit(&mut self) -> Result<(), Error<E>> {
265 self.write_command(Command::Reinit)
266 }
267
268 pub fn self_test_is_ok(&mut self) -> Result<bool, Error<E>> {
270 Ok(self.read_u16(Command::PerformSelfTest)? == 0)
271 }
272
273 pub fn factory_reset(&mut self) -> Result<(), Error<E>> {
275 self.write_command(Command::PerformFactoryReset)
276 }
277
278 pub fn forced_recalibration(&mut self, target_co2_ppm: u16) -> Result<u16, Error<E>> {
282 self.write_command_with_u16(Command::PerformForcedRecalibration, target_co2_ppm)?;
283 self.read_u16_raw_response()
284 }
285
286 fn write_command(&mut self, cmd: Command) -> Result<(), Error<E>> {
287 let allowed_while_running = matches!(
289 cmd,
290 Command::ReadMeasurement
291 | Command::GetDataReadyStatus
292 | Command::StopPeriodicMeasurement
293 );
294 if self.is_running && !allowed_while_running {
295 return Err(Error::InvalidInput);
297 }
298
299 let word = (cmd as u16).to_be_bytes();
300 self.i2c.write(self.address, &word).map_err(Error::I2c)?;
301 self.delay.delay_ms(cmd.delay_ms());
302 Ok(())
303 }
304
305 fn write_command_with_u16(&mut self, cmd: Command, data: u16) -> Result<(), Error<E>> {
306 let cmd_bytes = (cmd as u16).to_be_bytes();
307 let data_bytes = data.to_be_bytes();
308 let crc = crc8(&data_bytes);
309 let payload = [
310 cmd_bytes[0],
311 cmd_bytes[1],
312 data_bytes[0],
313 data_bytes[1],
314 crc,
315 ];
316
317 self.i2c.write(self.address, &payload).map_err(Error::I2c)?;
318 self.delay.delay_ms(cmd.delay_ms());
319 Ok(())
320 }
321
322 fn read_u16(&mut self, cmd: Command) -> Result<u16, Error<E>> {
323 self.write_command(cmd)?;
324 self.read_u16_raw_response()
325 }
326
327 fn read_u16_raw_response(&mut self) -> Result<u16, Error<E>> {
328 let mut buf = [0u8; 3];
329 self.i2c.read(self.address, &mut buf).map_err(Error::I2c)?;
330 if crc8(&buf[0..2]) != buf[2] {
331 return Err(Error::Crc);
332 }
333 Ok(u16::from_be_bytes([buf[0], buf[1]]))
334 }
335
336 fn read_words(&mut self, cmd: Command, buf: &mut [u8]) -> Result<(), Error<E>> {
337 self.write_command(cmd)?;
338 self.i2c.read(self.address, buf).map_err(Error::I2c)?;
339
340 if !buf.len().is_multiple_of(3) {
342 return Err(Error::UnexpectedResponse);
343 }
344 for chunk in buf.chunks_exact(3) {
345 if crc8(&chunk[0..2]) != chunk[2] {
346 return Err(Error::Crc);
347 }
348 }
349 Ok(())
350 }
351}