1#![no_std]
35
36use core::fmt::Debug;
37use core::marker::PhantomData;
38
39use embedded_hal::delay::DelayNs;
40
41pub mod device;
42use device::*;
43
44pub mod base;
45use base::*;
46
47pub struct Scd30<Conn, Delay, Err> {
50 conn: Conn,
51 delay: Delay,
52 _err: PhantomData<Err>,
53}
54
55#[derive(Debug)]
57pub enum Error<ConnErr> {
58 Conn(ConnErr),
59 Crc(u8, u8),
60 NoDevice,
61}
62
63impl<ConnErr> From<ConnErr> for Error<ConnErr> {
64 fn from(conn_err: ConnErr) -> Self {
65 Error::Conn(conn_err)
66 }
67}
68
69#[derive(PartialEq, Clone, Debug)]
71pub struct Measurement {
72 pub co2: f32,
75 pub temp: f32,
78 pub rh: f32,
81}
82
83impl<Conn, Delay, Err> Scd30<Conn, Delay, Err>
84where
85 Conn: Base<Err>,
86 Delay: DelayNs,
87 Err: Debug,
88{
89 pub fn new(conn: Conn, delay: Delay) -> Result<Self, Error<Err>> {
91 let mut s = Scd30 {
93 conn,
94 delay,
95 _err: PhantomData,
96 };
97
98 let v = s.firmware_version()?;
100 if v == 0x00 || v == 0xFF {
101 return Err(Error::NoDevice);
102 }
103
104 Ok(s)
106 }
107
108 pub fn start_continuous(&mut self, pressure_compensation: u16) -> Result<(), Error<Err>> {
111 self.conn
112 .write_command(Command::StartContinuousMode, Some(pressure_compensation))
113 }
114
115 pub fn stop_continuous(&mut self) -> Result<(), Error<Err>> {
117 self.conn.write_command(Command::StopContinuousMode, None)
118 }
119
120 pub fn set_measurement_interval(&mut self, interval: u16) -> Result<(), Error<Err>> {
122 self.conn
123 .write_command(Command::SetMeasurementInterval, Some(interval))
124 }
125
126 pub fn set_afc(&mut self, enabled: bool) -> Result<(), Error<Err>> {
128 let v = match enabled {
129 true => 1,
130 false => 0,
131 };
132
133 self.conn.write_command(Command::SetAfc, Some(v))
134 }
135
136 pub fn set_frc(&mut self, cal_ppm: u16) -> Result<(), Error<Err>> {
139 self.conn.write_command(Command::SetFrc, Some(cal_ppm))
140 }
141
142 pub fn set_temp_offset(&mut self, temperature: f32) -> Result<(), Error<Err>> {
145 let temperature = (temperature as u16) * 100;
146 self.conn
147 .write_command(Command::SetTempOffset, Some(temperature))
148 }
149
150 pub fn set_alt_offset(&mut self, altitude: u16) -> Result<(), Error<Err>> {
153 self.conn.write_command(Command::SetAltComp, Some(altitude))
154 }
155
156 pub fn soft_reset(&mut self) -> Result<(), Error<Err>> {
158 self.conn.write_command(Command::SoftReset, None)
159 }
160
161 pub fn firmware_version(&mut self) -> Result<u16, Error<Err>> {
163 let mut buff = [0u8; 3];
164
165 self.conn
166 .read_command(Command::GetFirmwareVersion, &mut buff)?;
167
168 let crc = crc8(&buff[..2]);
169 if crc != buff[2] {
170 return Err(Error::Crc(crc, buff[2]));
171 }
172
173 let v: u16 = (buff[0] as u16) << 8 | (buff[1] as u16);
174
175 Ok(v)
176 }
177
178 pub fn data_ready(&mut self) -> Result<bool, Error<Err>> {
180 let mut buff = [0u8; 3];
181
182 self.conn.read_command(Command::GetDataReady, &mut buff)?;
183
184 let crc = crc8(&buff[..2]);
185 if crc != buff[2] {
186 return Err(Error::Crc(crc, buff[2]));
187 }
188
189 Ok(buff[1] != 0)
190 }
191
192 pub fn read_data(&mut self) -> Result<Measurement, Error<Err>> {
194 let mut buff = [0u8; 18];
195
196 self.conn
197 .read_command(Command::ReadMeasurement, &mut buff)?;
198
199 let co2 = convert(&buff[0..6])?;
200 let temp = convert(&buff[6..12])?;
201 let rh = convert(&buff[12..18])?;
202
203 Ok(Measurement { co2, temp, rh })
204 }
205}
206
207fn convert<E>(line: &[u8]) -> Result<f32, Error<E>> {
209 assert_eq!(line.len(), 6);
211
212 let crc1 = crc8(&line[0..2]);
214 if crc1 != line[2] {
215 return Err(Error::Crc(crc1, line[2]));
216 }
217
218 let crc2 = crc8(&line[3..5]);
219 if crc2 != line[5] {
220 return Err(Error::Crc(crc2, line[5]));
221 }
222
223 let u: u32 = ((line[0] as u32) << 24)
226 | ((line[1] as u32) << 16)
227 | ((line[3] as u32) << 8)
228 | ((line[4] as u32) << 0);
229
230 let v = f32::from_bits(u);
232
233 Ok(v)
234}
235
236#[cfg(test)]
237mod test {
238 extern crate std;
239 use std::vec;
240
241 use embedded_hal_mock::eh1::delay::NoopDelay;
242 use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
243 use embedded_hal_mock::eh1::MockError;
244
245 use assert_approx_eq::assert_approx_eq;
246
247 use super::*;
248
249 #[test]
250 fn test_start_continuous() {
251 let expectations = [I2cTransaction::write(
253 DEFAULT_ADDRESS,
254 vec![0x00, 0x10, 0x00, 0x00, 0x81],
255 )];
256 let mut i2c = I2cMock::new(&expectations);
257
258 let mut sensor = Scd30 {
260 conn: i2c.clone(),
261 delay: NoopDelay {},
262 _err: PhantomData,
263 };
264
265 sensor.start_continuous(0).unwrap();
267
268 i2c.done();
270 }
271
272 #[test]
273 fn test_stop_continuous() {
274 let expectations = [I2cTransaction::write(DEFAULT_ADDRESS, vec![0x01, 0x04])];
276 let mut i2c = I2cMock::new(&expectations);
277
278 let mut sensor = Scd30 {
280 conn: i2c.clone(),
281 delay: NoopDelay {},
282 _err: PhantomData,
283 };
284
285 sensor.stop_continuous().unwrap();
287
288 i2c.done();
290 }
291
292 #[test]
293 fn test_set_measurement_interval() {
294 let expectations = [I2cTransaction::write(
296 DEFAULT_ADDRESS,
297 vec![0x46, 0x00, 0x00, 0x02, 0xE3],
298 )];
299 let mut i2c = I2cMock::new(&expectations);
300
301 let mut sensor = Scd30 {
303 conn: i2c.clone(),
304 delay: NoopDelay {},
305 _err: PhantomData,
306 };
307
308 sensor.set_measurement_interval(2).unwrap();
310
311 i2c.done();
313 }
314
315 #[test]
316 fn test_set_frc() {
317 let expectations = [I2cTransaction::write(
319 DEFAULT_ADDRESS,
320 vec![0x52, 0x04, 0x01, 0xc2, 0x50],
321 )];
322 let mut i2c = I2cMock::new(&expectations);
323
324 let mut sensor = Scd30 {
326 conn: i2c.clone(),
327 delay: NoopDelay {},
328 _err: PhantomData,
329 };
330
331 sensor.set_frc(450).unwrap();
333
334 i2c.done();
336 }
337
338 #[test]
339 fn set_temp_offset() {
340 let expectations = [I2cTransaction::write(
342 DEFAULT_ADDRESS,
343 vec![0x54, 0x03, 0x01, 0xF4, 0x33],
344 )];
345 let mut i2c = I2cMock::new(&expectations);
346
347 let mut sensor = Scd30 {
349 conn: i2c.clone(),
350 delay: NoopDelay {},
351 _err: PhantomData,
352 };
353
354 sensor.set_temp_offset(5.0).unwrap();
356
357 i2c.done();
359 }
360
361 #[test]
362 fn set_alt_offset() {
363 let expectations = [I2cTransaction::write(
365 DEFAULT_ADDRESS,
366 vec![0x51, 0x02, 0x03, 0xE8, 0xD4],
367 )];
368 let mut i2c = I2cMock::new(&expectations);
369
370 let mut sensor = Scd30 {
372 conn: i2c.clone(),
373 delay: NoopDelay {},
374 _err: PhantomData,
375 };
376
377 sensor.set_alt_offset(1000).unwrap();
379
380 i2c.done();
382 }
383
384 #[test]
385 fn test_soft_reset() {
386 let expectations = [I2cTransaction::write(DEFAULT_ADDRESS, vec![0xD3, 0x04])];
388 let mut i2c = I2cMock::new(&expectations);
389
390 let mut sensor = Scd30 {
392 conn: i2c.clone(),
393 delay: NoopDelay {},
394 _err: PhantomData,
395 };
396
397 sensor.soft_reset().unwrap();
399
400 i2c.done();
402 }
403
404 #[test]
405 fn test_read_data_ready() {
406 let expectations = [
408 I2cTransaction::write(DEFAULT_ADDRESS, vec![0x02, 0x02]),
409 I2cTransaction::read(DEFAULT_ADDRESS | I2C_READ_FLAG, vec![0x00, 0x01, 0xB0]),
410 ];
411 let mut i2c = I2cMock::new(&expectations);
412
413 let mut sensor = Scd30 {
415 conn: i2c.clone(),
416 delay: NoopDelay {},
417 _err: PhantomData,
418 };
419
420 let ready = sensor.data_ready().unwrap();
422 assert!(ready);
423
424 i2c.done();
426 }
427
428 #[test]
429 fn test_read_measurement() {
430 let expectations = [
432 I2cTransaction::write(DEFAULT_ADDRESS, vec![0x03, 0x00]),
433 I2cTransaction::read(
434 DEFAULT_ADDRESS | I2C_READ_FLAG,
435 vec![
436 0x43, 0xDB, 0xCB, 0x8C, 0x2E, 0x8F, 0x41, 0xD9, 0x70, 0xE7, 0xFF, 0xF5, 0x42, 0x43, 0xBF, 0x3A, 0x1B, 0x74, ],
440 ),
441 ];
442 let mut i2c = I2cMock::new(&expectations);
443
444 let mut sensor = Scd30 {
446 conn: i2c.clone(),
447 delay: NoopDelay {},
448 _err: PhantomData,
449 };
450
451 let m = sensor.read_data().unwrap();
453
454 assert_approx_eq!(m.co2, 439.0, 0.1);
455 assert_approx_eq!(m.temp, 27.2, 0.1);
456 assert_approx_eq!(m.rh, 48.8, 0.1);
457
458 i2c.done();
460 }
461
462 #[test]
463 fn test_convert() {
464 let tests = &[
466 ([0x43, 0xDB, 0xCB, 0x8C, 0x2E, 0x8F], 439.0),
467 ([0x41, 0xD9, 0x70, 0xE7, 0xFF, 0xF5], 27.2),
468 ([0x42, 0x43, 0xBF, 0x3A, 0x1B, 0x74], 48.8),
469 ];
470
471 for t in tests {
472 let v = convert::<()>(&t.0).unwrap();
473 assert_approx_eq!(v, t.1, 0.1);
474 }
475 }
476}