embedded_ccs811/
app_mode.rs

1use crate::hal::{delay::DelayNs, digital::OutputPin};
2use crate::{
3    hal, mode, register_access::get_errors, AlgorithmResult, BitFlags, Ccs811, Ccs811AppMode,
4    Ccs811Awake, Error, ErrorAwake, InterruptMode, MeasurementMode, ModeChangeError, Register,
5};
6
7impl<I2C, E> Ccs811AppMode for Ccs811Awake<I2C, mode::App>
8where
9    I2C: hal::i2c::I2c<Error = E>,
10{
11    type Error = ErrorAwake<E>;
12    type ModeChangeError = ModeChangeError<ErrorAwake<E>, Self>;
13    type BootModeType = Ccs811Awake<I2C, mode::Boot>;
14
15    fn set_mode(&mut self, mode: MeasurementMode) -> Result<(), Self::Error> {
16        let idle_mode = self.meas_mode_reg & 0b0000_1100;
17        let meas_mode = match mode {
18            MeasurementMode::Idle => idle_mode,
19            MeasurementMode::ConstantPower1s => idle_mode | (1 << 4),
20            MeasurementMode::PulseHeating10s => idle_mode | (2 << 4),
21            MeasurementMode::LowPowerPulseHeating60s => idle_mode | (3 << 4),
22            MeasurementMode::ConstantPower250ms => idle_mode | (4 << 4),
23        };
24        self.write_register_1byte(Register::MEAS_MODE, meas_mode)?;
25        self.meas_mode_reg = meas_mode;
26        Ok(())
27    }
28
29    fn has_data_ready(&mut self) -> Result<bool, Self::Error> {
30        let status = self.read_status()?;
31        Ok((status & BitFlags::DATA_READY) != 0)
32    }
33
34    fn raw_data(&mut self) -> Result<(u8, u16), Self::Error> {
35        let data = self.read_register_2bytes(Register::RAW_DATA)?;
36        Ok(handle_raw_data(data[0], data[1]))
37    }
38
39    fn data(&mut self) -> nb::Result<AlgorithmResult, Self::Error> {
40        let mut data = [0; 8];
41        self.i2c
42            .write_read(self.address, &[Register::ALG_RESULT_DATA], &mut data)
43            .map_err(ErrorAwake::I2C)?;
44        let status = data[4];
45        if (status & BitFlags::ERROR) != 0 {
46            get_errors(data[5]).map_err(ErrorAwake::Device)?;
47        } else if (status & BitFlags::DATA_READY) == 0 {
48            return Err(nb::Error::WouldBlock);
49        }
50        let raw = handle_raw_data(data[6], data[7]);
51        Ok(AlgorithmResult {
52            eco2: (u16::from(data[0]) << 8) | u16::from(data[1]),
53            etvoc: (u16::from(data[2]) << 8) | u16::from(data[3]),
54            raw_current: raw.0,
55            raw_voltage: raw.1,
56        })
57    }
58
59    #[allow(clippy::manual_range_contains)] // avoid creating range with exact floats
60    fn set_environment(
61        &mut self,
62        humidity_percentage: f32,
63        temperature_celsius: f32,
64    ) -> Result<(), Self::Error> {
65        if humidity_percentage < 0.0
66            || humidity_percentage > 100.0
67            || temperature_celsius > 254.998_05
68        {
69            return Err(ErrorAwake::InvalidInputData);
70        }
71        let raw_humidity = get_raw_humidity(humidity_percentage);
72        let raw_temp = get_raw_temperature(temperature_celsius);
73        let raw = [
74            Register::ENV_DATA,
75            raw_humidity.0,
76            raw_humidity.1,
77            raw_temp.0,
78            raw_temp.1,
79        ];
80        self.i2c
81            .write(self.address, &raw)
82            .map_err(ErrorAwake::I2C)?;
83        self.check_status_error()
84    }
85
86    fn baseline(&mut self) -> Result<[u8; 2], Self::Error> {
87        self.read_register_2bytes(Register::BASELINE)
88    }
89
90    fn set_baseline(&mut self, baseline: [u8; 2]) -> Result<(), Self::Error> {
91        self.i2c
92            .write(
93                self.address,
94                &[Register::BASELINE, baseline[0], baseline[1]],
95            )
96            .map_err(ErrorAwake::I2C)?;
97        self.check_status_error()
98    }
99
100    fn set_eco2_thresholds(
101        &mut self,
102        low_to_medium: u16,
103        medium_to_high: u16,
104    ) -> Result<(), Self::Error> {
105        self.i2c
106            .write(
107                self.address,
108                &[
109                    Register::THRESHOLDS,
110                    (low_to_medium >> 8) as u8,
111                    low_to_medium as u8,
112                    (medium_to_high >> 8) as u8,
113                    medium_to_high as u8,
114                ],
115            )
116            .map_err(ErrorAwake::I2C)?;
117        self.check_status_error()
118    }
119
120    fn set_interrupt_mode(&mut self, mode: InterruptMode) -> Result<(), Self::Error> {
121        let int_mask = match mode {
122            InterruptMode::Disabled => 0,
123            InterruptMode::OnDataReady => BitFlags::INTERRUPT,
124            InterruptMode::OnThresholdCrossed => BitFlags::INTERRUPT | BitFlags::THRESH,
125        };
126        let meas_mode = (self.meas_mode_reg & (0b111 << 4)) | int_mask;
127        self.write_register_1byte(Register::MEAS_MODE, meas_mode)?;
128        self.meas_mode_reg = meas_mode;
129        Ok(())
130    }
131
132    // Note: is_verifying is false after a reset
133    fn software_reset(mut self) -> Result<Self::BootModeType, Self::ModeChangeError> {
134        match self.write_sw_reset() {
135            Err(e) => Err(ModeChangeError::new(self, e)),
136            Ok(_) => Ok(Ccs811Awake::create(self.i2c, self.address)),
137        }
138    }
139}
140
141fn get_raw_humidity(humidity_percentage: f32) -> (u8, u8) {
142    get_raw_environment_data(humidity_percentage)
143}
144
145fn get_raw_temperature(temperature_celsius: f32) -> (u8, u8) {
146    let value = temperature_celsius + 25.0;
147    if value < 0.0 {
148        (0, 0)
149    } else {
150        get_raw_environment_data(value)
151    }
152}
153
154fn get_raw_environment_data(value: f32) -> (u8, u8) {
155    let main = (value as u8) << 1;
156    let rest = value - f32::from(value as u8);
157    let rest = (rest * 512.0) as u16;
158    (main | (((rest & (1 << 8)) >> 8) as u8), rest as u8)
159}
160
161fn handle_raw_data(data0: u8, data1: u8) -> (u8, u16) {
162    (data1 >> 2, u16::from(data0) | (u16::from(data1 & 0x3) << 8))
163}
164
165impl<I2C, CommE, PinE, NWAKE, WAKEDELAY> Ccs811AppMode for Ccs811<I2C, NWAKE, WAKEDELAY, mode::App>
166where
167    I2C: hal::i2c::I2c<Error = CommE>,
168    NWAKE: OutputPin<Error = PinE>,
169    WAKEDELAY: DelayNs,
170{
171    type Error = Error<CommE, PinE>;
172    type ModeChangeError = ModeChangeError<Error<CommE, PinE>, Self>;
173    type BootModeType = Ccs811<I2C, NWAKE, WAKEDELAY, mode::Boot>;
174
175    fn set_mode(&mut self, mode: MeasurementMode) -> Result<(), Self::Error> {
176        self.on_awaken(|s| s.dev.set_mode(mode))
177    }
178
179    fn has_data_ready(&mut self) -> Result<bool, Self::Error> {
180        self.on_awaken(|s| s.dev.has_data_ready())
181    }
182
183    fn raw_data(&mut self) -> Result<(u8, u16), Self::Error> {
184        self.on_awaken(|s| s.dev.raw_data())
185    }
186
187    fn data(&mut self) -> nb::Result<AlgorithmResult, Self::Error> {
188        self.on_awaken_nb(|s| s.dev.data())
189    }
190
191    fn baseline(&mut self) -> Result<[u8; 2], Self::Error> {
192        self.on_awaken(|s| s.dev.baseline())
193    }
194
195    fn set_baseline(&mut self, baseline: [u8; 2]) -> Result<(), Self::Error> {
196        self.on_awaken(|s| s.dev.set_baseline(baseline))
197    }
198
199    fn set_environment(
200        &mut self,
201        humidity_percentage: f32,
202        temperature_celsius: f32,
203    ) -> Result<(), Self::Error> {
204        self.on_awaken(|s| {
205            s.dev
206                .set_environment(humidity_percentage, temperature_celsius)
207        })
208    }
209
210    fn set_eco2_thresholds(
211        &mut self,
212        low_to_medium: u16,
213        medium_to_high: u16,
214    ) -> Result<(), Self::Error> {
215        self.on_awaken(|s| s.dev.set_eco2_thresholds(low_to_medium, medium_to_high))
216    }
217
218    fn set_interrupt_mode(&mut self, mode: InterruptMode) -> Result<(), Self::Error> {
219        self.on_awaken(|s| s.dev.set_interrupt_mode(mode))
220    }
221
222    fn software_reset(self) -> Result<Self::BootModeType, Self::ModeChangeError> {
223        self.wrap_mode_change(|s| s.software_reset())
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn convert_humidity() {
233        assert_eq!((0, 0), get_raw_humidity(0.0));
234        assert_eq!((0x64, 0), get_raw_humidity(50.0));
235        assert_eq!((0x61, 0), get_raw_humidity(48.5));
236        assert_eq!((0x60, 0x80), get_raw_humidity(48.25));
237        assert_eq!((0x60, 0x40), get_raw_humidity(48.125));
238        assert_eq!((0x60, 0x20), get_raw_humidity(48.0625));
239        assert_eq!((0x60, 0x10), get_raw_humidity(48.03125));
240        assert_eq!((0x60, 0x08), get_raw_humidity(48.015_625));
241        assert_eq!((0x60, 0x04), get_raw_humidity(48.007_813));
242        assert_eq!((0x60, 0x02), get_raw_humidity(48.003_906));
243        assert_eq!((0x60, 0x01), get_raw_humidity(48.001_953));
244        assert_eq!((0x61, 0xFF), get_raw_humidity(48.998_047));
245    }
246
247    #[test]
248    fn convert_temperature() {
249        assert_eq!((0, 0), get_raw_temperature(-25.5));
250        assert_eq!((0, 0), get_raw_temperature(-25.0));
251        assert_eq!((0x64, 0), get_raw_temperature(25.0));
252        assert_eq!((0x61, 0), get_raw_temperature(23.5));
253    }
254}