Skip to main content

pamoja_sensors/hdc1080/
sim.rs

1//! An HDC1080 that is not there.
2//!
3//! [`part`] answers the way an HDC1080 at 22.5 C and 45 % does, and [`reporting`] at any
4//! temperature and humidity. It answers at the one address the part has, [`I2C_ADDRESS`],
5//! and a measurement reads temperature then humidity in one four-byte read, as the part's
6//! does.
7
8use pamoja_hal::sim::WordPart;
9
10use super::{
11    humidity_register, register, temperature_register, DEVICE_ID, I2C_ADDRESS, MANUFACTURER_ID,
12};
13
14/// The temperature [`part`] reports.
15pub const CELSIUS: f32 = 22.5;
16
17/// The relative humidity [`part`] reports, as a percentage.
18pub const RELATIVE_HUMIDITY: f32 = 45.0;
19
20/// An HDC1080 reading [`CELSIUS`] and [`RELATIVE_HUMIDITY`].
21///
22/// # Returns
23///
24/// The part.
25#[must_use]
26pub fn part() -> WordPart {
27    reporting(CELSIUS, RELATIVE_HUMIDITY)
28}
29
30/// An HDC1080 that reads what it is asked to.
31///
32/// # Arguments
33///
34/// * `celsius` - the temperature it reports.
35/// * `relative_humidity` - the humidity it reports, as a percentage.
36///
37/// # Returns
38///
39/// The part. Its readings land on the nearest steps its 14-bit converters represent, within
40/// three thousandths of a degree and two thousandths of a percent.
41///
42/// # Examples
43///
44/// ```
45/// use pamoja_hal::script::DelayLog;
46/// use pamoja_sensors::hdc1080::{sim, Hdc1080};
47///
48/// let mut sensor = Hdc1080::new(sim::reporting(4.0, 91.0), DelayLog::new());
49/// let reading = sensor.measure().expect("the part answers");
50/// assert!((reading.celsius() - 4.0).abs() < 0.01);
51/// ```
52#[must_use]
53pub fn reporting(celsius: f32, relative_humidity: f32) -> WordPart {
54    let milli_celsius = rounded(celsius * 1000.0);
55    let milli_percent = rounded(relative_humidity.max(0.0) * 1000.0) as u32;
56    WordPart::new(I2C_ADDRESS)
57        .holding(register::MANUFACTURER_ID, MANUFACTURER_ID)
58        .holding(register::DEVICE_ID, DEVICE_ID)
59        .holding(register::TEMPERATURE, temperature_register(milli_celsius))
60        .holding(register::HUMIDITY, humidity_register(milli_percent))
61}
62
63// Rounds away from zero, since a part cannot call `f32::round` without an operating system.
64fn rounded(value: f32) -> i32 {
65    if value >= 0.0 {
66        (value + 0.5) as i32
67    } else {
68        (value - 0.5) as i32
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use crate::hdc1080::Hdc1080;
76    use pamoja_hal::script::DelayLog;
77
78    #[test]
79    fn the_driver_reads_what_the_part_holds() {
80        let mut sensor = Hdc1080::new(part(), DelayLog::new());
81        let reading = sensor.measure().expect("a reading");
82        assert!((reading.celsius() - CELSIUS).abs() < 0.003);
83        assert!((reading.relative_humidity() - RELATIVE_HUMIDITY).abs() < 0.002);
84
85        let mut cold = Hdc1080::new(reporting(-20.0, 12.5), DelayLog::new());
86        let reading = cold.measure().expect("a reading");
87        assert!((reading.celsius() + 20.0).abs() < 0.003);
88        assert!((reading.relative_humidity() - 12.5).abs() < 0.002);
89    }
90}