use self::commands::{
GetDataReady, GetSensorVariant, PerformForcedRecalibration, ReadMeasurement, Reinit, StartPeriodicMeasurement,
StopPeriodicMeasurement,
};
use embedded_devices_derive::{forward_command_fns, sensor};
use uom::si::f64::{Ratio, ThermodynamicTemperature};
use super::{
commands::Crc8Error,
scd4x::commands::{DataReadyStatus, SensorVariant, TargetCo2Concentration},
};
pub use super::scd4x::address;
pub mod commands;
pub type TransportError<E> = embedded_interfaces::TransportError<Crc8Error, E>;
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, thiserror::Error)]
pub enum InitError<BusError> {
#[error("transport error")]
Transport(#[from] TransportError<BusError>),
#[error("invalid sensor variant {0:?}")]
InvalidSensorVariant(SensorVariant),
}
#[derive(Debug, embedded_devices_derive::Measurement)]
pub struct Measurement {
#[measurement(RelativeHumidity)]
pub relative_humidity: Ratio,
#[measurement(Temperature)]
pub temperature: ThermodynamicTemperature,
#[measurement(Co2Concentration)]
pub co2_concentration: Ratio,
}
#[maybe_async_cfg::maybe(
idents(
hal(sync = "embedded_hal", async = "embedded_hal_async"),
CommandInterface,
I2cDevice
),
sync(feature = "sync"),
async(feature = "async")
)]
pub struct SCD40<D: hal::delay::DelayNs, I: embedded_interfaces::commands::CommandInterface> {
delay: D,
interface: I,
}
#[maybe_async_cfg::maybe(
idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), I2cDevice),
sync(feature = "sync"),
async(feature = "async")
)]
impl<D, I> SCD40<D, embedded_interfaces::i2c::I2cDevice<I, hal::i2c::SevenBitAddress>>
where
I: hal::i2c::I2c<hal::i2c::SevenBitAddress> + hal::i2c::ErrorType,
D: hal::delay::DelayNs,
{
#[inline]
pub fn new_i2c(delay: D, interface: I, address: self::address::Address) -> Self {
Self {
delay,
interface: embedded_interfaces::i2c::I2cDevice::new(interface, address.into()),
}
}
}
pub trait SCD40Command {}
#[forward_command_fns]
#[sensor(RelativeHumidity, Temperature, Co2Concentration)]
#[maybe_async_cfg::maybe(
idents(
hal(sync = "embedded_hal", async = "embedded_hal_async"),
CommandInterface,
ResettableDevice
),
sync(feature = "sync"),
async(feature = "async")
)]
impl<D: hal::delay::DelayNs, I: embedded_interfaces::commands::CommandInterface> SCD40<D, I> {
pub async fn init(&mut self) -> Result<(), InitError<I::BusError>> {
use crate::device::ResettableDevice;
self.delay.delay_ms(30).await;
self.reset().await?;
match self.execute::<GetSensorVariant>(()).await?.read_variant() {
SensorVariant::r#SCD40 => Ok(()),
variant => Err(InitError::InvalidSensorVariant(variant)),
}
}
pub async fn perform_forced_recalibration(
&mut self,
target_co2_concentration: Ratio,
) -> Result<Option<Ratio>, TransportError<I::BusError>> {
let frc_correction = self
.execute::<PerformForcedRecalibration>(
TargetCo2Concentration::default().with_target_co2_concentration(target_co2_concentration),
)
.await?;
Ok((frc_correction.read_raw_correction() != u16::MAX).then(|| frc_correction.read_correction()))
}
}
#[maybe_async_cfg::maybe(
idents(
hal(sync = "embedded_hal", async = "embedded_hal_async"),
CommandInterface,
ResettableDevice
),
sync(feature = "sync"),
async(feature = "async")
)]
impl<D: hal::delay::DelayNs, I: embedded_interfaces::commands::CommandInterface> crate::device::ResettableDevice
for SCD40<D, I>
{
type Error = TransportError<I::BusError>;
async fn reset(&mut self) -> Result<(), Self::Error> {
let _ = self.execute::<StopPeriodicMeasurement>(()).await;
self.execute::<Reinit>(()).await?;
Ok(())
}
}
#[maybe_async_cfg::maybe(
idents(
hal(sync = "embedded_hal", async = "embedded_hal_async"),
CommandInterface,
ContinuousSensor
),
sync(feature = "sync"),
async(feature = "async")
)]
impl<D: hal::delay::DelayNs, I: embedded_interfaces::commands::CommandInterface> crate::sensor::ContinuousSensor
for SCD40<D, I>
{
type Error = TransportError<I::BusError>;
type Measurement = Measurement;
async fn start_measuring(&mut self) -> Result<(), Self::Error> {
self.execute::<StartPeriodicMeasurement>(()).await?;
Ok(())
}
async fn stop_measuring(&mut self) -> Result<(), Self::Error> {
self.execute::<StopPeriodicMeasurement>(()).await?;
Ok(())
}
async fn measurement_interval_us(&mut self) -> Result<u32, Self::Error> {
Ok(5_000_000)
}
async fn current_measurement(&mut self) -> Result<Option<Self::Measurement>, Self::Error> {
let measurement = self.execute::<ReadMeasurement>(()).await?;
Ok(Some(Measurement {
relative_humidity: measurement.read_relative_humidity(),
temperature: measurement.read_temperature(),
co2_concentration: measurement.read_co2_concentration(),
}))
}
async fn is_measurement_ready(&mut self) -> Result<bool, Self::Error> {
Ok(self.execute::<GetDataReady>(()).await?.read_data_ready() == DataReadyStatus::Ready)
}
async fn next_measurement(&mut self) -> Result<Self::Measurement, Self::Error> {
loop {
if self.is_measurement_ready().await? {
return self.current_measurement().await?.ok_or_else(|| {
TransportError::Unexpected("measurement was not ready even though we expected it to be")
});
}
self.delay.delay_ms(100).await;
}
}
}