Skip to main content

pamoja_sensors/sht3x/
sim.rs

1//! An SHT3x that is not there.
2//!
3//! [`part`] answers the way an SHT3x at 22.5 C and 45 % does, and [`reporting`] at any
4//! temperature and humidity. It takes Sensirion's 16-bit commands: every single-shot
5//! measurement, with or without clock stretching, and a fetch in periodic mode, answer with
6//! the reading and its CRCs; the status command answers with the status a part reports after
7//! a reset; a reset, the heater, and the rest answer with nothing.
8
9use pamoja_hal::sim::CommandPart;
10
11use super::{
12    command, humidity_raw_from_relative_humidity, single_shot, temperature_raw_from_celsius,
13    Measurement, Repeatability, Status,
14};
15
16/// The temperature [`part`] reports.
17pub const CELSIUS: f32 = 22.5;
18
19/// The relative humidity [`part`] reports, as a percentage.
20pub const RELATIVE_HUMIDITY: f32 = 45.0;
21
22/// An SHT3x answering at an address, reading [`CELSIUS`] and [`RELATIVE_HUMIDITY`].
23///
24/// # Arguments
25///
26/// * `address` - the address it answers to, [`I2C_ADDRESS_A`](super::I2C_ADDRESS_A) with
27///   ADDR low.
28///
29/// # Returns
30///
31/// The part.
32#[must_use]
33pub fn part(address: u8) -> CommandPart {
34    reporting(address, CELSIUS, RELATIVE_HUMIDITY)
35}
36
37/// An SHT3x that reads what it is asked to.
38///
39/// # Arguments
40///
41/// * `address` - the address it answers to.
42/// * `celsius` - the temperature it reports.
43/// * `relative_humidity` - the humidity it reports, as a percentage.
44///
45/// # Returns
46///
47/// The part. Its readings land on the nearest steps its 16-bit words represent, within
48/// three thousandths of a degree and two thousandths of a percent.
49///
50/// # Examples
51///
52/// ```
53/// use pamoja_hal::script::DelayLog;
54/// use pamoja_sensors::sht3x::{sim, Sht3x, I2C_ADDRESS_A};
55///
56/// let mut sensor = Sht3x::new(sim::reporting(I2C_ADDRESS_A, 30.0, 70.0), I2C_ADDRESS_A, DelayLog::new());
57/// let reading = sensor.measure().expect("the part answers");
58/// assert!((reading.temperature_celsius() - 30.0).abs() < 0.01);
59/// ```
60#[must_use]
61pub fn reporting(address: u8, celsius: f32, relative_humidity: f32) -> CommandPart {
62    let reading = Measurement {
63        temperature_raw: temperature_raw_from_celsius(celsius),
64        humidity_raw: humidity_raw_from_relative_humidity(relative_humidity),
65    }
66    .to_bytes();
67    let status = Status::from_bits(Status::DEFAULT).to_bytes();
68    let mut part = CommandPart::new(address, 2)
69        .answering(&command::READ_STATUS.to_be_bytes(), &status)
70        .answering(&command::FETCH_DATA.to_be_bytes(), &reading);
71    for repeatability in [
72        Repeatability::Low,
73        Repeatability::Medium,
74        Repeatability::High,
75    ] {
76        for stretching in [false, true] {
77            part.answer(
78                &single_shot(repeatability, stretching).to_be_bytes(),
79                &reading,
80            );
81        }
82    }
83    part
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use crate::sht3x::{Sht3x, I2C_ADDRESS_A, I2C_ADDRESS_B};
90    use pamoja_hal::script::DelayLog;
91
92    #[test]
93    fn the_driver_resets_the_part_and_reads_it() {
94        let mut sensor = Sht3x::new(part(I2C_ADDRESS_A), I2C_ADDRESS_A, DelayLog::new());
95        let reading = sensor.measure().expect("a reading");
96        assert!((reading.temperature_celsius() - CELSIUS).abs() < 0.003);
97        assert!((reading.relative_humidity() - RELATIVE_HUMIDITY).abs() < 0.002);
98        assert_eq!(
99            sensor.status().map(|status| status.bits()),
100            Some(Status::DEFAULT)
101        );
102
103        let (part, _) = sensor.release();
104        assert_eq!(
105            part.received()[0],
106            command::SOFT_RESET.to_be_bytes(),
107            "the reset went first"
108        );
109    }
110
111    #[test]
112    fn a_part_reports_what_it_was_asked_to() {
113        let mut sensor = Sht3x::new(
114            reporting(I2C_ADDRESS_B, -12.0, 88.5),
115            I2C_ADDRESS_B,
116            DelayLog::new(),
117        );
118        let reading = sensor.measure().expect("a reading");
119        assert!((reading.temperature_celsius() + 12.0).abs() < 0.003);
120        assert!((reading.relative_humidity() - 88.5).abs() < 0.002);
121    }
122}