embedded_devices/devices/sensirion/scd41/
mod.rs1use self::commands::{
76 GetDataReady, GetSensorVariant, MeasureSingleShot, PerformForcedRecalibration, ReadMeasurement, Reinit,
77 StartPeriodicMeasurement, StopPeriodicMeasurement, WakeUp,
78};
79use embedded_devices_derive::{forward_command_fns, sensor};
80use uom::si::f64::{Ratio, ThermodynamicTemperature};
81
82use super::{
83 commands::Crc8Error,
84 scd4x::commands::{DataReadyStatus, SensorVariant, TargetCo2Concentration},
85};
86
87pub use super::scd4x::address;
88pub mod commands;
89
90pub type TransportError<E> = embedded_interfaces::TransportError<Crc8Error, E>;
92
93#[cfg_attr(feature = "defmt", derive(defmt::Format))]
94#[derive(Debug, thiserror::Error)]
95pub enum InitError<BusError> {
96 #[error("transport error")]
98 Transport(#[from] TransportError<BusError>),
99 #[error("invalid sensor variant {0:?}")]
101 InvalidSensorVariant(SensorVariant),
102}
103
104#[derive(Debug, embedded_devices_derive::Measurement)]
106pub struct Measurement {
107 #[measurement(RelativeHumidity)]
109 pub relative_humidity: Ratio,
110 #[measurement(Temperature)]
112 pub temperature: ThermodynamicTemperature,
113 #[measurement(Co2Concentration)]
115 pub co2_concentration: Ratio,
116}
117
118#[maybe_async_cfg::maybe(
124 idents(
125 hal(sync = "embedded_hal", async = "embedded_hal_async"),
126 CommandInterface,
127 I2cDevice
128 ),
129 sync(feature = "sync"),
130 async(feature = "async")
131)]
132pub struct SCD41<D: hal::delay::DelayNs, I: embedded_interfaces::commands::CommandInterface> {
133 delay: D,
135 interface: I,
137}
138
139#[maybe_async_cfg::maybe(
140 idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), I2cDevice),
141 sync(feature = "sync"),
142 async(feature = "async")
143)]
144impl<D, I> SCD41<D, embedded_interfaces::i2c::I2cDevice<I, hal::i2c::SevenBitAddress>>
145where
146 I: hal::i2c::I2c<hal::i2c::SevenBitAddress> + hal::i2c::ErrorType,
147 D: hal::delay::DelayNs,
148{
149 #[inline]
155 pub fn new_i2c(delay: D, interface: I, address: self::address::Address) -> Self {
156 Self {
157 delay,
158 interface: embedded_interfaces::i2c::I2cDevice::new(interface, address.into()),
159 }
160 }
161}
162
163pub trait SCD41Command {}
164
165#[forward_command_fns]
166#[sensor(RelativeHumidity, Temperature, Co2Concentration)]
167#[maybe_async_cfg::maybe(
168 idents(
169 hal(sync = "embedded_hal", async = "embedded_hal_async"),
170 CommandInterface,
171 ResettableDevice
172 ),
173 sync(feature = "sync"),
174 async(feature = "async")
175)]
176impl<D: hal::delay::DelayNs, I: embedded_interfaces::commands::CommandInterface> SCD41<D, I> {
177 pub async fn init(&mut self) -> Result<(), InitError<I::BusError>> {
180 use crate::device::ResettableDevice;
181
182 self.delay.delay_ms(30).await;
184 self.reset().await?;
185
186 match self.execute::<GetSensorVariant>(()).await?.read_variant() {
188 SensorVariant::r#SCD41 => Ok(()),
189 variant => Err(InitError::InvalidSensorVariant(variant)),
190 }
191 }
192
193 pub async fn perform_forced_recalibration(
202 &mut self,
203 target_co2_concentration: Ratio,
204 ) -> Result<Option<Ratio>, TransportError<I::BusError>> {
205 let frc_correction = self
206 .execute::<PerformForcedRecalibration>(
207 TargetCo2Concentration::default().with_target_co2_concentration(target_co2_concentration),
208 )
209 .await?;
210
211 Ok((frc_correction.read_raw_correction() != u16::MAX).then(|| frc_correction.read_correction()))
212 }
213}
214
215#[maybe_async_cfg::maybe(
216 idents(
217 hal(sync = "embedded_hal", async = "embedded_hal_async"),
218 CommandInterface,
219 ResettableDevice
220 ),
221 sync(feature = "sync"),
222 async(feature = "async")
223)]
224impl<D: hal::delay::DelayNs, I: embedded_interfaces::commands::CommandInterface> crate::device::ResettableDevice
225 for SCD41<D, I>
226{
227 type Error = TransportError<I::BusError>;
228
229 async fn reset(&mut self) -> Result<(), Self::Error> {
231 let _ = self.execute::<WakeUp>(()).await;
233 let _ = self.execute::<StopPeriodicMeasurement>(()).await;
235 self.execute::<Reinit>(()).await?;
237 Ok(())
238 }
239}
240
241#[maybe_async_cfg::maybe(
242 idents(
243 hal(sync = "embedded_hal", async = "embedded_hal_async"),
244 CommandInterface,
245 ContinuousSensor
246 ),
247 sync(feature = "sync"),
248 async(feature = "async")
249)]
250impl<D: hal::delay::DelayNs, I: embedded_interfaces::commands::CommandInterface> crate::sensor::ContinuousSensor
251 for SCD41<D, I>
252{
253 type Error = TransportError<I::BusError>;
254 type Measurement = Measurement;
255
256 async fn start_measuring(&mut self) -> Result<(), Self::Error> {
258 self.execute::<StartPeriodicMeasurement>(()).await?;
259 Ok(())
260 }
261
262 async fn stop_measuring(&mut self) -> Result<(), Self::Error> {
264 self.execute::<StopPeriodicMeasurement>(()).await?;
265 Ok(())
266 }
267
268 async fn measurement_interval_us(&mut self) -> Result<u32, Self::Error> {
270 Ok(5_000_000)
271 }
272
273 async fn current_measurement(&mut self) -> Result<Option<Self::Measurement>, Self::Error> {
275 let measurement = self.execute::<ReadMeasurement>(()).await?;
276 Ok(Some(Measurement {
277 relative_humidity: measurement.read_relative_humidity(),
278 temperature: measurement.read_temperature(),
279 co2_concentration: measurement.read_co2_concentration(),
280 }))
281 }
282
283 async fn is_measurement_ready(&mut self) -> Result<bool, Self::Error> {
285 Ok(self.execute::<GetDataReady>(()).await?.read_data_ready() == DataReadyStatus::Ready)
286 }
287
288 async fn next_measurement(&mut self) -> Result<Self::Measurement, Self::Error> {
291 loop {
292 if self.is_measurement_ready().await? {
293 return self.current_measurement().await?.ok_or_else(|| {
294 TransportError::Unexpected("measurement was not ready even though we expected it to be")
295 });
296 }
297 self.delay.delay_ms(100).await;
298 }
299 }
300}
301
302#[maybe_async_cfg::maybe(
303 idents(
304 hal(sync = "embedded_hal", async = "embedded_hal_async"),
305 CommandInterface,
306 ContinuousSensor,
307 OneshotSensor
308 ),
309 sync(feature = "sync"),
310 async(feature = "async")
311)]
312impl<D: hal::delay::DelayNs, I: embedded_interfaces::commands::CommandInterface> crate::sensor::OneshotSensor
313 for SCD41<D, I>
314{
315 type Error = TransportError<I::BusError>;
316 type Measurement = Measurement;
317
318 async fn measure(&mut self) -> Result<Self::Measurement, Self::Error> {
320 use crate::sensor::ContinuousSensor;
321 self.execute::<MeasureSingleShot>(()).await?;
322 self.next_measurement().await
323 }
324}