#![cfg_attr(all(not(test), not(feature = "std")), no_std)]
#[cfg(feature = "sen60")]
mod feature_check_sen60 {
#[cfg(any(
feature = "sen63c",
feature = "sen65",
feature = "sen66",
feature = "sen68"
))]
compile_error!("Only one sensor variant feature can be enabled at a time");
}
#[cfg(feature = "sen63c")]
mod feature_check_sen63c {
#[cfg(any(feature = "sen65", feature = "sen66", feature = "sen68"))]
compile_error!("Only one sensor variant feature can be enabled at a time");
}
#[cfg(feature = "sen65")]
mod feature_check_sen65 {
#[cfg(any(feature = "sen66", feature = "sen68"))]
compile_error!("Only one sensor variant feature can be enabled at a time");
}
#[cfg(feature = "sen66")]
mod feature_check_sen66 {
#[cfg(feature = "sen68")]
compile_error!("Only one sensor variant feature can be enabled at a time");
}
use crc_internal::CrcError;
#[cfg(feature = "async")]
pub mod asynchronous;
pub mod blocking;
mod crc_internal;
const MODULE_ADDR: u8 = 0x6B;
#[cfg(not(feature = "sen60"))]
#[repr(u16)]
#[derive(Copy, Clone, Debug)]
enum CommandId {
StartContinuousMeasurement = 0x0021,
StopMeasurement = 0x0104,
GetDataReady = 0x0202,
#[cfg(feature = "sen63c")]
ReadMeasuredValues = 0x0471,
#[cfg(feature = "sen65")]
ReadMeasuredValues = 0x0446,
#[cfg(feature = "sen66")]
ReadMeasuredValues = 0x0300,
#[cfg(feature = "sen68")]
ReadMeasuredValues = 0x0467,
#[cfg(feature = "sen63c")]
ReadMeasuredRawValues = 0x0492,
#[cfg(any(feature = "sen65", feature = "sen68"))]
ReadMeasuredRawValues = 0x0455,
#[cfg(feature = "sen66")]
ReadMeasuredRawValues = 0x0405,
ReadNumberConcentrationValues = 0x0316,
SetTempOffsetPars = 0x60B2,
SetTempAccelPars = 0x6100,
GetProductName = 0xD014,
GetSerialNumber = 0xD033,
ReadDeviceStatus = 0xD206,
ReadAndClearDeviceStatus = 0xD210,
DeviceReset = 0xD304,
StartFanCleaning = 0x5607,
ActivateShtHeater = 0x6765,
#[cfg(any(feature = "sen65", feature = "sen68", feature = "sen66"))]
VocAlgoTuningPars = 0x60D0,
#[cfg(any(feature = "sen65", feature = "sen68", feature = "sen66"))]
VocAlgoState = 0x6181,
#[cfg(any(feature = "sen65", feature = "sen68", feature = "sen66"))]
NoxAlgoTuningPars = 0x60E1,
#[cfg(any(feature = "sen63c", feature = "sen66"))]
PerformForcedCo2Recalibration = 0x6707,
#[cfg(any(feature = "sen63c", feature = "sen66"))]
Co2SensorAutoCalibrationState = 0x6711,
#[cfg(any(feature = "sen63c", feature = "sen66"))]
AmbientPressure = 0x6720,
#[cfg(any(feature = "sen63c", feature = "sen66"))]
SensorAltitude = 0x6736,
}
fn get_execution_time(command: CommandId) -> u32 {
match command {
CommandId::StartContinuousMeasurement => 50,
CommandId::StopMeasurement => 1000,
CommandId::ActivateShtHeater => 1300,
#[cfg(any(feature = "sen63c", feature = "sen66"))]
CommandId::PerformForcedCo2Recalibration => 500,
_ => 20,
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
enum ModuleState {
Idle,
Measuring,
}
const MAX_RX_BYTES: usize = 48;
const MAX_TX_BYTES: usize = 18;
type Result<T> = core::result::Result<T, Sen6xError>;
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "std", derive(thiserror::Error))]
pub enum Sen6xError {
#[cfg_attr(
feature = "std",
error("An error occurred while reading from the SEN6x module")
)]
ReadI2CError,
#[cfg_attr(
feature = "std",
error("An error occurred while writing to the SEN6x module")
)]
WriteI2CError,
#[cfg_attr(
feature = "std",
error("The SEN6x module is in a state that does not permit this command")
)]
InvalidState,
#[cfg_attr(
feature = "std",
error("The SEN6x module returned data which could not be parsed")
)]
InvalidData,
#[cfg_attr(
feature = "std",
error("The SEN6x module provided toto much data to the driver implementation")
)]
TooMuchData,
#[cfg_attr(feature = "std", error("CRC failure on SEN6x data"))]
CrcError(CrcError),
}
impl From<CrcError> for Sen6xError {
fn from(e: CrcError) -> Self {
Sen6xError::CrcError(e)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MeasuredSample {
pub pm1: f32,
pub pm2_5: f32,
pub pm4: f32,
pub pm10: f32,
pub humidity: f32,
pub temperature: f32,
#[cfg(any(feature = "sen63c", feature = "sen66"))]
pub co2: u16,
#[cfg(any(feature = "sen65", feature = "sen66", feature = "sen68"))]
pub voc: f32,
#[cfg(any(feature = "sen65", feature = "sen66", feature = "sen68"))]
pub nox: f32,
#[cfg(feature = "sen68")]
pub hcho: f32,
}
#[cfg(feature = "sen66")]
impl From<[u16; 9]> for MeasuredSample {
fn from(data: [u16; 9]) -> Self {
Self {
pm1: data[0] as f32 / 10.0,
pm2_5: data[1] as f32 / 10.0,
pm4: data[2] as f32 / 10.0,
pm10: data[3] as f32 / 10.0,
humidity: data[4] as f32 / 100.0,
temperature: data[5] as f32 / 200.0,
voc: data[6] as f32 / 10.0,
nox: data[7] as f32 / 10.0,
co2: data[8],
}
}
}
#[cfg(feature = "sen63c")]
impl From<[u16; 7]> for MeasuredSample {
fn from(data: [u16; 7]) -> Self {
Self {
pm1: data[0] as f32 / 10.0,
pm2_5: data[1] as f32 / 10.0,
pm4: data[2] as f32 / 10.0,
pm10: data[3] as f32 / 10.0,
humidity: data[4] as f32 / 100.0,
temperature: data[5] as f32 / 200.0,
co2: data[6],
}
}
}
#[cfg(feature = "sen65")]
impl From<[u16; 8]> for MeasuredSample {
fn from(data: [u16; 8]) -> Self {
Self {
pm1: data[0] as f32 / 10.0,
pm2_5: data[1] as f32 / 10.0,
pm4: data[2] as f32 / 10.0,
pm10: data[3] as f32 / 10.0,
humidity: data[4] as f32 / 100.0,
temperature: data[5] as f32 / 200.0,
voc: data[6] as f32 / 10.0,
nox: data[7] as f32 / 10.0,
}
}
}
#[cfg(feature = "sen68")]
impl From<[u16; 9]> for MeasuredSample {
fn from(data: [u16; 9]) -> Self {
Self {
pm1: data[0] as f32 / 10.0,
pm2_5: data[1] as f32 / 10.0,
pm4: data[2] as f32 / 10.0,
pm10: data[3] as f32 / 10.0,
humidity: data[4] as f32 / 100.0,
temperature: data[5] as f32 / 200.0,
voc: data[6] as f32 / 10.0,
nox: data[7] as f32 / 10.0,
hcho: data[8] as f32 / 10.0,
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RawMeasuredSample {
pub raw_humidity: i16,
pub raw_temperature: i16,
#[cfg(any(feature = "sen65", feature = "sen66", feature = "sen68"))]
pub raw_voc: u16,
#[cfg(any(feature = "sen65", feature = "sen66", feature = "sen68"))]
pub raw_nox: u16,
#[cfg(feature = "sen66")]
pub raw_co2: u16,
}
#[cfg(feature = "sen63c")]
impl From<[u16; 2]> for RawMeasuredSample {
fn from(data: [u16; 2]) -> Self {
Self {
raw_humidity: data[0] as i16,
raw_temperature: data[1] as i16,
}
}
}
#[cfg(any(feature = "sen65", feature = "sen68"))]
impl From<[u16; 4]> for RawMeasuredSample {
fn from(data: [u16; 4]) -> Self {
Self {
raw_humidity: data[0] as i16,
raw_temperature: data[1] as i16,
raw_voc: data[2],
raw_nox: data[3],
}
}
}
#[cfg(any(feature = "sen66"))]
impl From<[u16; 5]> for RawMeasuredSample {
fn from(data: [u16; 5]) -> Self {
Self {
raw_humidity: data[0] as i16,
raw_temperature: data[1] as i16,
raw_voc: data[2],
raw_nox: data[3],
raw_co2: data[4],
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RawConcentrationSample {
pub pm1: u16,
pub pm2_5: u16,
pub pm4: u16,
pub pm10: u16,
}
impl From<[u16; 4]> for RawConcentrationSample {
fn from(data: [u16; 4]) -> Self {
Self {
pm1: data[0],
pm2_5: data[1],
pm4: data[2],
pm10: data[3],
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DeviceStatus {
pub fan_speed_warning: bool,
pub co2_error: bool,
pub pm_error: bool,
pub gas_error: bool,
pub rh_t_error: bool,
pub fan_error: bool,
pub hcho_error: bool,
}
impl From<[u16; 2]> for DeviceStatus {
fn from(data: [u16; 2]) -> Self {
Self {
fan_speed_warning: (data[0] & (1 << 5)) != 0,
#[cfg(any(feature = "sen66", feature = "sen68", feature = "sen65"))]
co2_error: (data[1] & (1 << 9)) != 0,
#[cfg(feature = "sen63c")]
co2_error: (data[1] & (1 << 12)) != 0,
pm_error: (data[1] & (1 << 11)) != 0,
gas_error: (data[1] & (1 << 7)) != 0,
rh_t_error: (data[1] & (1 << 6)) != 0,
fan_error: (data[1] & (1 << 4)) != 0,
hcho_error: (data[1] & (1 << 10)) != 0,
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TempOffsetPars {
pub offset: i16,
pub slope: i16,
pub time_constant: u16,
pub slot: u16,
}
impl From<TempOffsetPars> for [u16; 4] {
fn from(data: TempOffsetPars) -> [u16; 4] {
[
data.offset as u16,
data.slope as u16,
data.time_constant,
data.slot,
]
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TempAccelPars {
pub k: u16,
pub p: u16,
pub t1: u16,
pub t2: u16,
}
impl From<TempAccelPars> for [u16; 4] {
fn from(data: TempAccelPars) -> [u16; 4] {
[data.k, data.p, data.t1, data.t2]
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AlgorithmTuningParameters {
pub index_offset: i16,
pub learning_time_offset_hours: i16,
pub learning_time_gain_hours: i16,
pub gating_max_duration_minutes: i16,
pub std_initial: i16,
pub gain_factor: i16,
}
impl From<AlgorithmTuningParameters> for [u16; 6] {
fn from(data: AlgorithmTuningParameters) -> [u16; 6] {
[
data.index_offset as u16,
data.learning_time_offset_hours as u16,
data.learning_time_gain_hours as u16,
data.gating_max_duration_minutes as u16,
data.std_initial as u16,
data.gain_factor as u16,
]
}
}
impl From<[u16; 6]> for AlgorithmTuningParameters {
fn from(data: [u16; 6]) -> Self {
Self {
index_offset: data[0] as i16,
learning_time_offset_hours: data[1] as i16,
learning_time_gain_hours: data[2] as i16,
gating_max_duration_minutes: data[3] as i16,
std_initial: data[4] as i16,
gain_factor: data[5] as i16,
}
}
}