Skip to main content

pamoja_sensors/opt3001/
sim.rs

1//! An OPT3001 that is not there.
2//!
3//! [`part`] answers the way an OPT3001 reading 380 lux does, and [`reporting`] at any light
4//! level. Its configuration register keeps the flags the part sets for itself, the overflow,
5//! conversion-ready, high, and low flags, whatever a driver writes there, with the
6//! conversion-ready flag set, so every conversion reads as finished.
7
8use pamoja_hal::sim::WordPart;
9
10use super::{raw_from_milli_lux, register, Configuration, DEVICE_ID, MANUFACTURER_ID};
11
12/// The illuminance [`part`] reports.
13pub const LUX: f32 = 380.0;
14
15/// The configuration register's flags, which the part sets and a write leaves alone: the
16/// overflow flag (bit 8), the conversion-ready flag (bit 7), and the high and low flags
17/// (bits 6 and 5).
18const FLAGS: u16 = 0x01E0;
19
20/// The conversion-ready flag, bit 7.
21const CONVERSION_READY: u16 = 1 << 7;
22
23/// An OPT3001 answering at an address, reading [`LUX`].
24///
25/// # Arguments
26///
27/// * `address` - the address it answers to, from
28///   [`I2C_ADDRESS_GND`](super::I2C_ADDRESS_GND) to
29///   [`I2C_ADDRESS_SCL`](super::I2C_ADDRESS_SCL).
30///
31/// # Returns
32///
33/// The part.
34#[must_use]
35pub fn part(address: u8) -> WordPart {
36    reporting(address, LUX)
37}
38
39/// An OPT3001 that reads what it is asked to.
40///
41/// # Arguments
42///
43/// * `address` - the address it answers to.
44/// * `lux` - the illuminance it reports, which lands on the nearest step the part's exponent
45///   and mantissa can represent.
46///
47/// # Returns
48///
49/// The part.
50///
51/// # Examples
52///
53/// ```
54/// use pamoja_hal::script::DelayLog;
55/// use pamoja_sensors::opt3001::{sim, Opt3001, I2C_ADDRESS_GND};
56///
57/// let mut light = Opt3001::new(sim::reporting(I2C_ADDRESS_GND, 1200.0), I2C_ADDRESS_GND, DelayLog::new());
58/// let reading = light.measure().expect("the part answers");
59/// assert_eq!(reading.lux(), 1200.0);
60/// ```
61#[must_use]
62pub fn reporting(address: u8, lux: f32) -> WordPart {
63    let milli_lux = (lux.max(0.0) * 1000.0 + 0.5) as u32;
64    WordPart::new(address)
65        .holding(register::MANUFACTURER_ID, MANUFACTURER_ID)
66        .holding(register::DEVICE_ID, DEVICE_ID)
67        .holding(
68            register::CONFIGURATION,
69            Configuration::default().bits() | CONVERSION_READY,
70        )
71        .read_only(register::CONFIGURATION, FLAGS)
72        .holding(register::RESULT, raw_from_milli_lux(milli_lux))
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use crate::opt3001::{Opt3001, I2C_ADDRESS_VDD};
79    use pamoja_hal::script::DelayLog;
80
81    #[test]
82    fn the_flags_are_the_ones_the_decoder_reads() {
83        let flags = Configuration::from_bits(FLAGS);
84        assert!(flags.overflow && flags.conversion_ready && flags.flag_high && flags.flag_low);
85        assert!(Configuration::from_bits(CONVERSION_READY).conversion_ready);
86        assert_eq!(
87            Configuration::default().bits() & FLAGS,
88            0,
89            "a written configuration never carries a flag"
90        );
91    }
92
93    #[test]
94    fn the_driver_reads_the_light_the_part_holds() {
95        let mut light = Opt3001::new(part(I2C_ADDRESS_VDD), I2C_ADDRESS_VDD, DelayLog::new());
96        assert_eq!(light.measure().expect("a reading").lux(), LUX);
97
98        let mut dim = Opt3001::new(
99            reporting(I2C_ADDRESS_VDD, 0.64),
100            I2C_ADDRESS_VDD,
101            DelayLog::new(),
102        );
103        assert_eq!(dim.measure().expect("a reading").lux(), 0.64);
104    }
105}