1#![cfg_attr(not(test), no_std)]
47
48use crc_internal::CrcError;
49
50#[cfg(feature = "async")]
51pub mod asynchronous;
52
53pub mod blocking;
54
55mod crc_internal;
56
57pub const STCC4_ADDR_DEFAULT: u8 = 0x64;
59
60pub const STCC4_ADDR_ALT: u8 = 0x65;
62
63pub const I2C_GENERAL_CALL_ADDR: u8 = 0x00;
65
66pub const EXIT_SLEEP_PAYLOAD: u8 = 0x00;
68
69pub const SOFT_RESET_CMD: u8 = 0x06;
71
72const MAX_RX_BYTES: usize = 18;
74
75const MAX_TX_BYTES: usize = 8;
77
78#[repr(u16)]
80#[derive(Copy, Clone, Debug)]
81enum CommandId {
82 StartContinuousMeasurement = 0x218B,
83 StopContinuousMeasurement = 0x3F86,
84 ReadMeasurement = 0xEC05,
85 SetRhtCompensation = 0xE000,
86 SetPressureCompensation = 0xE016,
87 MeasureSingleShot = 0x219D,
88 EnterSleepMode = 0x3650,
89 PerformConditioning = 0x29BC,
90 PerformFactoryReset = 0x3632,
91 PerformSelfTest = 0x278C,
92 EnableTestingMode = 0x3FBC,
93 DisableTestingMode = 0x3F3D,
94 PerformForcedRecalibration = 0x362F,
95 GetProductId = 0x365B,
96}
97
98fn get_execution_time(command: CommandId) -> u32 {
100 match command {
101 CommandId::StopContinuousMeasurement => 1_200,
102 CommandId::ReadMeasurement => 1,
103 CommandId::SetRhtCompensation => 1,
104 CommandId::SetPressureCompensation => 1,
105 CommandId::MeasureSingleShot => 500,
106 CommandId::EnterSleepMode => 1,
107 CommandId::PerformConditioning => 22_000,
108 CommandId::PerformFactoryReset => 90,
109 CommandId::PerformSelfTest => 360,
110 CommandId::PerformForcedRecalibration => 90,
111 CommandId::GetProductId => 1,
112 _ => 0,
113 }
114}
115
116#[derive(Copy, Clone, Debug, PartialEq)]
118enum ModuleState {
119 Idle,
120 Measuring,
121 Sleep,
122}
123
124type Result<T, E> = core::result::Result<T, Stcc4Error<E>>;
126
127#[derive(Debug, Eq, PartialEq)]
129#[cfg_attr(feature = "defmt", derive(defmt::Format))]
130pub enum Stcc4Error<E> {
131 ReadI2cError(E),
133 WriteI2cError(E),
135 InvalidState,
137 InvalidData,
139 CrcError(CrcError),
141}
142
143impl<E> From<CrcError> for Stcc4Error<E> {
144 fn from(e: CrcError) -> Self {
145 Stcc4Error::CrcError(e)
146 }
147}
148
149#[derive(Clone, Debug)]
151#[cfg_attr(feature = "defmt", derive(defmt::Format))]
152#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
153pub struct Measurement {
154 pub co2_ppm: u16,
156 pub temperature_c: f32,
158 pub humidity_percent: f32,
160 pub status: SensorStatus,
162}
163
164#[derive(Copy, Clone, Debug, PartialEq, Eq)]
166#[cfg_attr(feature = "defmt", derive(defmt::Format))]
167#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
168pub struct SensorStatus {
169 pub raw: u16,
171}
172
173impl SensorStatus {
174 pub fn testing_mode(&self) -> bool {
176 (self.raw & 0x0040) != 0
177 }
178}
179
180#[derive(Copy, Clone, Debug, PartialEq, Eq)]
182#[cfg_attr(feature = "defmt", derive(defmt::Format))]
183#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
184pub struct SelfTestResult {
185 pub raw: u16,
187}
188
189impl SelfTestResult {
190 pub fn is_success(&self) -> bool {
192 self.raw == 0x0000 || self.raw == 0x0010
193 }
194
195 pub fn supply_voltage_out_of_range(&self) -> bool {
197 (self.raw & 0x0001) != 0
198 }
199
200 pub fn sht_missing(&self) -> bool {
202 (self.raw & 0x0010) != 0
203 }
204
205 pub fn memory_error(&self) -> bool {
207 (self.raw & 0x0060) != 0
208 }
209
210 pub fn debug_bits(&self) -> u8 {
212 ((self.raw >> 1) & 0x0007) as u8
213 }
214}
215
216#[derive(Copy, Clone, Debug, PartialEq, Eq)]
218#[cfg_attr(feature = "defmt", derive(defmt::Format))]
219#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
220pub struct FrcCorrection(pub i16);
221
222#[derive(Copy, Clone, Debug, PartialEq, Eq)]
224#[cfg_attr(feature = "defmt", derive(defmt::Format))]
225#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
226pub struct ProductId {
227 pub product_id: u32,
229 pub serial_number: u64,
231}
232
233#[inline]
234fn raw_to_humidity_percent(raw: u16) -> f32 {
235 125.0 * (raw as f32) / 65_535.0 - 6.0
236}
237
238#[inline]
239fn raw_to_temperature_c(raw: u16) -> f32 {
240 175.0 * (raw as f32) / 65_535.0 - 45.0
241}
242
243#[inline]
244fn clamp_u16(value: f32) -> u16 {
245 if value <= 0.0 {
246 0
247 } else if value >= u16::MAX as f32 {
248 u16::MAX
249 } else {
250 (value + 0.5) as u16
251 }
252}
253
254#[inline]
255fn humidity_percent_to_raw(rh_percent: f32) -> u16 {
256 clamp_u16(((rh_percent + 6.0) * 65_535.0) / 125.0)
257}
258
259#[inline]
260fn temperature_c_to_raw(temperature_c: f32) -> u16 {
261 clamp_u16(((temperature_c + 45.0) * 65_535.0) / 175.0)
262}
263
264#[inline]
265fn pressure_pa_to_raw(pressure_pa: u32) -> u16 {
266 let raw = (pressure_pa / 2) as f32;
267 clamp_u16(raw)
268}
269
270#[inline]
271fn frc_correction_from_raw(raw: u16) -> FrcCorrection {
272 FrcCorrection((raw as i32 - 32_768) as i16)
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn conversion_roundtrip_temperature() {
282 let t_c = 25.0_f32;
283 let raw = temperature_c_to_raw(t_c);
284 let out = raw_to_temperature_c(raw);
285 assert!((out - t_c).abs() < 0.5);
286 }
287
288 #[test]
289 fn conversion_roundtrip_humidity() {
291 let rh = 50.0_f32;
292 let raw = humidity_percent_to_raw(rh);
293 let out = raw_to_humidity_percent(raw);
294 assert!((out - rh).abs() < 0.5);
295 }
296
297 #[test]
298 fn pressure_conversion() {
300 let pressure_pa = 101_300_u32;
301 let raw = pressure_pa_to_raw(pressure_pa);
302 assert_eq!(raw, 50_650);
303 }
304
305 #[test]
306 fn frc_correction_conversion() {
308 let raw = 32_668_u16;
309 let correction = frc_correction_from_raw(raw);
310 assert_eq!(correction.0, -100);
311 }
312
313 #[test]
314 fn sensor_status_testing_mode() {
316 let status = SensorStatus { raw: 0x0040 };
317 assert!(status.testing_mode());
318
319 let status = SensorStatus { raw: 0x0000 };
320 assert!(!status.testing_mode());
321 }
322
323 #[test]
324 fn self_test_helpers() {
326 let result = SelfTestResult { raw: 0x0001 };
327 assert!(!result.is_success());
328 assert!(result.supply_voltage_out_of_range());
329
330 let result = SelfTestResult { raw: 0x0010 };
331 assert!(result.is_success());
332 assert!(result.sht_missing());
333
334 let result = SelfTestResult { raw: 0x0060 };
335 assert!(result.memory_error());
336
337 let result = SelfTestResult { raw: 0x000E };
338 assert_eq!(result.debug_bits(), 0x07);
339 }
340
341 #[test]
342 fn clamp_u16_bounds() {
344 assert_eq!(clamp_u16(-10.0), 0);
345 assert_eq!(clamp_u16(100_000.0), u16::MAX);
346 }
347
348 #[test]
349 fn execution_time_mapping() {
351 assert_eq!(
352 get_execution_time(CommandId::StopContinuousMeasurement),
353 1_200
354 );
355 assert_eq!(get_execution_time(CommandId::PerformConditioning), 22_000);
356 assert_eq!(get_execution_time(CommandId::StartContinuousMeasurement), 0);
357 }
358}