libscd/internal/
scd30.rs

1use crate::internal::common::opcode_with_data_into_payload;
2use crate::measurement::Measurement;
3use core::ops::Range;
4
5// Section 1.1.1
6pub const I2C_ADDRESS: u8 = 0x61;
7
8// Section 1.1.2.
9// The datasheet is ambiguous whether the driver should wait after each write
10// command. For some commands (1.4.4-GetDataReady, 1.4.5-DataMeasurement)
11// it's explicitly specified that the implementations must wait at least 3ms
12// before reading the response. For other commands, such as 1.4.6-FRC/ASC, it
13// is not explicitly specified, but then it would contradict the diagram
14// at 1.1.2. So take the safer route and always perform a delay after a write
15// command
16pub const WRITE_DELAY_MILLIS: u32 = 5;
17
18// Section 1.1. Boot delay is at most 2s.
19pub const BOOT_DELAY_MILLIS: u32 = 2_000;
20
21// Section 1.4.1
22pub const AMBIENT_PRESSURE_DISABLE_COMPENSATION: u16 = 0;
23pub const AMBIENT_PRESSURE_RANGE_HPA: Range<u16> = 700..1401;
24
25// Section 1.4.3
26pub const MEASUREMENT_INTERVAL_RANGE: Range<u16> = 2..1801;
27
28// Section 1.4.6
29pub const FRC_PPM_RANGE: Range<u16> = 400..2001;
30
31pub const START_CONTINUOUS_MEASUREMENT: Command = Command(0x0010);
32pub const STOP_CONTINUOUS_MEASUREMENT: Command = Command(0x0104);
33pub const GET_SET_MEASUREMENT_INTERVAL: Command = Command(0x4600);
34pub const GET_DATA_READY_STATUS: Command = Command(0x0202);
35pub const READ_MEASUREMENT: Command = Command(0x0300);
36pub const MANAGE_AUTOMATIC_SELF_CALIBRATION: Command = Command(0x5306);
37pub const SET_FORCED_RECALIBRATION_VALUE: Command = Command(0x5204);
38pub const GET_SET_TEMPERATURE_OFFSET: Command = Command(0x5403);
39pub const GET_SET_ALTITUDE_COMPENSATION: Command = Command(0x5102);
40pub const READ_FIRMWARE_VERSION: Command = Command(0xD100);
41pub const SOFT_RESET: Command = Command(0xD304);
42
43#[derive(Copy, Clone)]
44pub struct Command(u16);
45
46impl Command {
47    pub const fn prepare(self) -> [u8; 2] {
48        self.0.to_be_bytes()
49    }
50
51    pub const fn prepare_with_data(self, data: u16) -> [u8; 5] {
52        opcode_with_data_into_payload(self.0, data)
53    }
54}
55
56pub fn decode_measurement_data(buf: [u8; 18]) -> Measurement {
57    let co2 = f32::from_be_bytes([buf[0], buf[1], buf[3], buf[4]]);
58    let tmp = f32::from_be_bytes([buf[6], buf[7], buf[9], buf[10]]);
59    let hum = f32::from_be_bytes([buf[12], buf[13], buf[15], buf[16]]);
60
61    Measurement {
62        temperature: tmp,
63        humidity: hum,
64        co2: co2 as u16,
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    const F32_TOLERANCE: f32 = 0.05;
73
74    #[test]
75    fn test_prepare_command() {
76        assert_eq!([0x00, 0x10], START_CONTINUOUS_MEASUREMENT.prepare());
77    }
78
79    #[test]
80    fn test_prepare_command_with_data() {
81        assert_eq!(
82            [0x54, 0x03, 0x01, 0xF4, 0x33],
83            GET_SET_TEMPERATURE_OFFSET.prepare_with_data(0x01F4)
84        );
85    }
86
87    #[test]
88    fn test_decode_measurement_data() {
89        const EXPECTED_HUMIDITY: f32 = 48.8;
90        const EXPECTED_TEMPERATURE: f32 = 27.2;
91
92        let buf = [
93            0x43, 0xDB, 0xCB, // CO2: MMSB, MLSB, CRC
94            0x8C, 0x2E, 0x8F, // CO2: LMSB, LLSB, CRC
95            0x41, 0xD9, 0x70, // TMP: MMSB, MLSB, CRC
96            0xE7, 0xFF, 0xF5, // TMP: LMSB, LLSB, CRC
97            0x42, 0x43, 0xBF, // RH%: MMSB, MLSB, CRC
98            0x3A, 0x1B, 0x74, // RH%: LMSB, LLSB, CRC
99        ];
100
101        let m = decode_measurement_data(buf);
102        assert_eq!(439, m.co2);
103        assert!(
104            (EXPECTED_HUMIDITY - m.humidity).abs() < F32_TOLERANCE,
105            "Expected: {}; Actual: {}",
106            EXPECTED_HUMIDITY,
107            m.humidity
108        );
109        assert!(
110            (EXPECTED_TEMPERATURE - m.temperature).abs() < F32_TOLERANCE,
111            "Expected: {}; Actual: {}",
112            EXPECTED_TEMPERATURE,
113            m.temperature
114        );
115    }
116}