use crate::error::Error;
use crate::internal::common::opcode_with_data_into_payload;
use crate::measurement::Measurement;
use crate::SensorVariant;
use core::ops::Range;
pub const I2C_ADDRESS: u8 = 0x62;
pub const MAX_ALTITUDE: u16 = 3_000;
pub const AMBIENT_PRESSURE_RANGE_HPA: Range<u16> = 700..1201;
const TWO_P16_M1: f32 = u16::MAX as f32;
const TEMP_K1: f32 = 175.0f32;
pub const START_PERIODIC_MEASUREMENT: Command = Command::new(0x21b1, 0, false);
pub const START_LOW_POWER_PERIODIC_MEASUREMENT: Command = Command::new(0x21ac, 0, false);
pub const STOP_PERIODIC_MEASUREMENT: Command = Command::new(0x3f86, 500, true);
pub const GET_DATA_READY_STATUS: Command = Command::new(0xe4b8, 1, true);
pub const READ_MEASUREMENT: Command = Command::new(0xec05, 1, true);
pub const SET_TEMPERATURE_OFFSET: Command = Command::new(0x241d, 1, false);
pub const GET_TEMPERATURE_OFFSET: Command = Command::new(0x2318, 1, false);
pub const SET_SENSOR_ALTITUDE: Command = Command::new(0x2427, 1, false);
pub const GET_SENSOR_ALTITUDE: Command = Command::new(0x2322, 1, false);
pub const SET_AMBIENT_PRESSURE: Command = Command::new(0xe000, 1, true);
pub const GET_AMBIENT_PRESSURE: Command = Command::new(0xe000, 1, true);
pub const SET_AUTOMATIC_SELF_CALIBRATION_ENABLED: Command = Command::new(0x2416, 1, false);
pub const GET_AUTOMATIC_SELF_CALIBRATION_ENABLED: Command = Command::new(0x2313, 1, false);
pub const SET_AUTOMATIC_SELF_CALIBRATION_TARGET: Command = Command::new(0x243a, 1, false);
pub const GET_AUTOMATIC_SELF_CALIBRATION_TARGET: Command = Command::new(0x233f, 1, false);
pub const PERFORM_FORCED_RECALIBRATION: Command = Command::new(0x362f, 400, false);
pub const PERSIST_SETTINGS: Command = Command::new(0x3615, 800, false);
pub const GET_SERIAL_NUMBER: Command = Command::new(0x3682, 1, false);
pub const PERFORM_SELF_TEST: Command = Command::new(0x3639, 10_000, false);
pub const PERFORM_FACTORY_RESET: Command = Command::new(0x3632, 1_200, false);
pub const REINIT: Command = Command::new(0x3646, 30, false);
pub const GET_SENSOR_VARIANT: Command = Command::new(0x202F, 1, true);
#[cfg(feature = "scd41")]
pub const MEASURE_SINGLE_SHOT: Command = Command::new(0x219d, 5_000, false);
#[cfg(feature = "scd41")]
pub const MEASURE_SINGLE_SHOT_RHT_ONLY: Command = Command::new(0x2196, 50, false);
#[cfg(feature = "scd41")]
pub const POWER_DOWN: Command = Command::new(0x36e0, 1, false);
#[cfg(feature = "scd41")]
pub const WAKE_UP: Command = Command::new(0x36f6, 30, false);
#[cfg(feature = "scd41")]
pub const SET_AUTOMATIC_SELF_CALIBRATION_INITIAL_PERIOD: Command = Command::new(0x2445, 1, false);
#[cfg(feature = "scd41")]
pub const GET_AUTOMATIC_SELF_CALIBRATION_INITIAL_PERIOD: Command = Command::new(0x2340, 1, false);
#[cfg(feature = "scd41")]
pub const SET_AUTOMATIC_SELF_CALIBRATION_STANDARD_PERIOD: Command = Command::new(0x244e, 1, false);
#[cfg(feature = "scd41")]
pub const GET_AUTOMATIC_SELF_CALIBRATION_STANDARD_PERIOD: Command = Command::new(0x234b, 1, false);
#[derive(Copy, Clone)]
pub struct Command {
pub op_code: u16,
pub exec_time: u16,
pub allowed_while_running: bool,
}
impl Command {
const fn new(op_code: u16, exec_time: u16, allowed_while_running: bool) -> Self {
Self {
op_code,
exec_time,
allowed_while_running,
}
}
pub const fn prepare(self) -> [u8; 2] {
self.op_code.to_be_bytes()
}
pub const fn prepare_with_data(self, data: u16) -> [u8; 5] {
opcode_with_data_into_payload(self.op_code, data)
}
}
pub fn decode_sensor_variant(buf: [u8; 3]) -> Option<SensorVariant> {
const MASK: u16 = 0xF000;
const SHIFT: u16 = 12;
let word = u16::from_be_bytes([buf[0], buf[1]]);
let decoded = (word & MASK) >> SHIFT;
match decoded {
0b0000 => Some(SensorVariant::Scd40),
0b0001 => Some(SensorVariant::Scd41),
0b0101 => Some(SensorVariant::Scd43),
_ => None,
}
}
pub fn decode_serial_number(buf: [u8; 9]) -> u64 {
(u64::from(buf[0]) << 40)
| (u64::from(buf[1]) << 32)
| (u64::from(buf[3]) << 24)
| (u64::from(buf[4]) << 16)
| (u64::from(buf[6]) << 8)
| u64::from(buf[7])
}
pub fn decode_measurement(buf: [u8; 9]) -> Measurement {
Measurement {
temperature: decode_temp_measurement(buf[3], buf[4]),
humidity: decode_humidity_measurement(buf[6], buf[7]),
co2: decode_co2_measurement(buf[0], buf[1]),
}
}
fn decode_temp_measurement(msb: u8, lsb: u8) -> f32 {
let raw = u16::from_be_bytes([msb, lsb]);
raw as f32 * TEMP_K1 / TWO_P16_M1 - 45.0
}
fn decode_humidity_measurement(msb: u8, lsb: u8) -> f32 {
let raw = u16::from_be_bytes([msb, lsb]);
raw as f32 * 100.0 / TWO_P16_M1
}
fn decode_co2_measurement(msb: u8, lsb: u8) -> u16 {
u16::from_be_bytes([msb, lsb])
}
pub fn encode_temperature_offset<E>(offset: f32) -> Result<u16, Error<E>> {
if !offset.is_finite() || offset.is_sign_negative() {
return Err(Error::InvalidInput);
}
Ok((offset * TWO_P16_M1 / TEMP_K1) as u16)
}
pub fn decode_temperature_offset(buf: [u8; 3]) -> f32 {
let offset = u16::from_be_bytes([buf[0], buf[1]]);
offset as f32 * TEMP_K1 / TWO_P16_M1
}
pub fn decode_frc_status(buf: [u8; 3]) -> Option<i16> {
const FRC_FAILED: u16 = 0xFFFF;
let result = u16::from_be_bytes([buf[0], buf[1]]);
if FRC_FAILED == result {
return None;
}
let frc_correction = result as i32 - 0x8000;
Some(frc_correction as i16)
}
pub fn decode_has_data_ready(buf: [u8; 3]) -> bool {
let status = u16::from_be_bytes([buf[0], buf[1]]);
(status & 0x07FF) != 0
}
#[cfg(test)]
mod tests {
use super::*;
use crate::internal::crc::crc8;
use crate::SensorVariant;
const F32_TOLERANCE: f32 = 0.005;
#[test]
fn test_decode_scd40() {
assert_eq!(
Some(SensorVariant::Scd40),
decode_sensor_variant([0x04, 0x40, 0x51])
);
}
#[test]
fn test_decode_scd41() {
assert_eq!(
Some(SensorVariant::Scd41),
decode_sensor_variant([0x14, 0x40, 0x3F])
);
}
#[test]
fn test_decode_scd43() {
assert_eq!(
Some(SensorVariant::Scd43),
decode_sensor_variant([0x54, 0x41, 0xE9])
);
}
#[test]
fn test_decode_serial_number() {
let response = [0xF8, 0x96, 0x31, 0x9F, 0x07, 0xC2, 0x3B, 0xBE, 0x89];
let serial_number = decode_serial_number(response);
assert_eq!(273_325_796_834_238, serial_number);
}
#[test]
fn test_prepare_command() {
assert_eq!([0x36, 0x82], GET_SERIAL_NUMBER.prepare());
}
#[test]
fn test_prepare_command_with_data() {
assert_eq!(
[0x24, 0x1D, 0x07, 0xE6, 0x48],
SET_TEMPERATURE_OFFSET.prepare_with_data(0x07E6)
);
}
#[test]
fn test_decode_temperature_offset_1() {
let raw = [0x07, 0x0E6, 0x48];
let offset = decode_temperature_offset(raw);
assert!(offset.is_finite());
assert!((5.4 - offset).abs() < F32_TOLERANCE);
}
#[test]
fn test_decode_temperature_offset_2() {
let raw = [0x09, 0x012, 0x63];
let offset = decode_temperature_offset(raw);
assert!(offset.is_finite());
assert!((6.2 - offset).abs() < F32_TOLERANCE);
}
#[test]
fn test_encode_temperature_offset() {
let word = encode_temperature_offset::<()>(5.4).unwrap();
assert_eq!(0x07E6, word);
}
#[test]
fn test_encode_decode_temperature_offset() {
const MIN_OFFSET: f32 = 0.0;
const MAX_OFFSET: f32 = 20.0;
const INCREMENT: f32 = 0.01;
let mut offset = MIN_OFFSET;
while offset <= MAX_OFFSET {
let encoded = encode_temperature_offset::<()>(offset)
.unwrap()
.to_be_bytes();
let wire_format = [encoded[0], encoded[1], crc8(&encoded)];
let decoded = decode_temperature_offset(wire_format);
assert!(
(decoded - offset).abs() < F32_TOLERANCE,
"Offset={}; Decoded={}",
offset,
decoded
);
offset += INCREMENT;
}
}
#[test]
fn test_encode_temperature_offset_rejects_negative() {
assert_eq!(
Err(Error::InvalidInput),
encode_temperature_offset::<()>(-1.0)
);
}
#[test]
fn test_encode_temperature_offset_rejects_nan() {
assert_eq!(
Err(Error::InvalidInput),
encode_temperature_offset::<()>(f32::NAN)
);
}
#[test]
fn test_decode_temp_measurement() {
const EXPECTED: f32 = 25.0;
let decoded = decode_temp_measurement(0x66, 0x67);
assert!(decoded.is_finite());
assert!(
(EXPECTED - decoded).abs() < F32_TOLERANCE,
"Expected: {}; Decoded: {}",
EXPECTED,
decoded
);
}
#[test]
fn test_decode_co2_measurement() {
let decoded = decode_co2_measurement(0x01, 0xF4);
assert_eq!(500, decoded)
}
#[test]
fn test_decode_humidity_measurement() {
const EXPECTED: f32 = 37.0;
let decoded = decode_humidity_measurement(0x5E, 0xB9);
assert!(decoded.is_finite());
assert!(
(EXPECTED - decoded).abs() < F32_TOLERANCE,
"Expected: {}; Decoded: {}",
EXPECTED,
decoded
);
}
#[test]
fn test_decode_measurement() {
let m = decode_measurement([0x01, 0xF4, 0x33, 0x66, 0x67, 0xA2, 0x5E, 0xB9, 0x3C]);
assert_eq!(500, m.co2);
assert!((25.0 - m.temperature).abs() < F32_TOLERANCE);
assert!((37.0 - m.humidity).abs() < F32_TOLERANCE);
}
#[test]
fn test_decode_frc_status() {
let status = decode_frc_status([0x7F, 0xCE, 0x7B]);
assert_eq!(Some(-50), status);
}
#[test]
fn test_decode_frc_status_failed() {
let status = decode_frc_status([0xFF, 0xFF, crc8(&[0xFF, 0xFF])]);
assert_eq!(None, status);
}
}