Skip to main content

pamoja_sensors/opt3001/
driver.rs

1//! The OPT3001 driven over I2C: identify, configure, convert once, and read the result.
2
3use embedded_hal::delay::DelayNs;
4use embedded_hal::i2c::I2c;
5use pamoja_core::Sensor;
6
7use super::{
8    raw_from_milli_lux, register, Configuration, ConversionTime, Mode, Reading, DEVICE_ID,
9    MANUFACTURER_ID, RANGE_AUTOMATIC,
10};
11use crate::driver::{read_word, write_word};
12use crate::error::{DriverError, SensorError};
13
14/// How long the driver waits between polls of the configuration register, in
15/// milliseconds, once the conversion time has elapsed.
16pub const POLL_INTERVAL_MILLIS: u32 = 10;
17
18/// How many polls the driver makes before a conversion is called overdue.
19///
20/// A conversion in automatic range can run a full extra conversion time when the
21/// light changes sharply during it, so the polls cover another 800 ms conversion.
22pub const STATUS_POLLS: u8 = 100;
23
24/// An OPT3001 on an I2C bus, measuring illuminance on demand in single-shot mode.
25///
26/// [`init`](Opt3001::init) checks the manufacturer and device id registers and
27/// writes the configuration with the part in shutdown, its power-on state.
28/// [`measure`](Opt3001::measure) writes the single-shot mode, which starts one
29/// conversion at the configured integration time, waits that time, polls the
30/// conversion-ready flag, which a read of the configuration register clears, then
31/// reads the result register. The part returns itself to shutdown when the
32/// conversion ends. As a [`Sensor`] its reading is the [`Reading`]; the lux value
33/// is selected with [`Sensor::map`].
34///
35/// # Examples
36///
37/// A greenhouse's light, read from an OPT3001 that is not plugged in yet: the simulated
38/// part sees 320 lux, and the driver hands the reading back in lux.
39///
40/// ```
41/// use pamoja_core::Sensor;
42/// use pamoja_hal::script::{block_on, DelayLog};
43/// use pamoja_sensors::opt3001::{sim, Opt3001, I2C_ADDRESS_GND};
44///
45/// let part = sim::reporting(I2C_ADDRESS_GND, 320.0);
46/// let sensor = Opt3001::new(part, I2C_ADDRESS_GND, DelayLog::new());
47/// let mut lux = sensor.map(|reading| reading.lux());
48/// let reading = block_on(lux.read())?;
49/// assert!((reading - 320.0).abs() < 0.5);
50/// # Ok::<(), pamoja_core::Error>(())
51/// ```
52#[derive(Debug)]
53pub struct Opt3001<I2C, D> {
54    bus: I2C,
55    delay: D,
56    address: u8,
57    conversion_time: ConversionTime,
58    range_number: u8,
59    initialized: bool,
60}
61
62impl<I2C, D> Opt3001<I2C, D> {
63    /// Wraps an I2C bus and the part's address.
64    ///
65    /// # Arguments
66    ///
67    /// * `bus` - the I2C bus the part is on.
68    /// * `address` - the address the ADDR pin selects, one of the
69    ///   `I2C_ADDRESS_*` constants.
70    /// * `delay` - the timer that paces the conversions.
71    ///
72    /// # Returns
73    ///
74    /// The driver, with the 800 ms integration time and automatic full-scale range,
75    /// the part's reset settings.
76    pub fn new(bus: I2C, address: u8, delay: D) -> Self {
77        Opt3001 {
78            bus,
79            delay,
80            address,
81            conversion_time: ConversionTime::Ms800,
82            range_number: RANGE_AUTOMATIC,
83            initialized: false,
84        }
85    }
86
87    /// Sets the integration time of each conversion.
88    ///
89    /// # Arguments
90    ///
91    /// * `conversion_time` - 100 ms for speed, 800 ms for resolution.
92    ///
93    /// # Returns
94    ///
95    /// The driver.
96    pub fn with_conversion_time(mut self, conversion_time: ConversionTime) -> Self {
97        self.conversion_time = conversion_time;
98        self.initialized = false;
99        self
100    }
101
102    /// Sets the full-scale range number, or automatic range selection.
103    ///
104    /// # Arguments
105    ///
106    /// * `range_number` - 0 to [`RANGE_MAX`](super::RANGE_MAX) for a fixed range,
107    ///   or [`RANGE_AUTOMATIC`].
108    ///
109    /// # Returns
110    ///
111    /// The driver.
112    pub fn with_range(mut self, range_number: u8) -> Self {
113        self.range_number = range_number;
114        self.initialized = false;
115        self
116    }
117
118    /// Returns the configuration the driver writes, with the part in shutdown.
119    pub fn configuration(&self) -> Configuration {
120        self.configuration_in(Mode::Shutdown)
121    }
122
123    /// Gives back the bus and the delay.
124    ///
125    /// # Returns
126    ///
127    /// The bus and delay the driver was built from.
128    pub fn release(self) -> (I2C, D) {
129        (self.bus, self.delay)
130    }
131
132    fn configuration_in(&self, mode: Mode) -> Configuration {
133        Configuration {
134            range_number: self.range_number,
135            conversion_time: self.conversion_time,
136            mode,
137            ..Configuration::default()
138        }
139    }
140}
141
142impl<I2C: I2c, D: DelayNs> Opt3001<I2C, D> {
143    /// Checks the part's identity and writes the settings with the part in shutdown.
144    ///
145    /// # Errors
146    ///
147    /// Returns [`DriverError::Bus`] if the bus fails, and [`DriverError::Sensor`]
148    /// with [`SensorError::Identity`] if either id register is not an OPT3001's.
149    pub fn init(&mut self) -> Result<(), DriverError<I2C::Error>> {
150        if self.read(register::MANUFACTURER_ID)? != MANUFACTURER_ID
151            || self.read(register::DEVICE_ID)? != DEVICE_ID
152        {
153            return Err(SensorError::Identity.into());
154        }
155        self.write(register::CONFIGURATION, self.configuration().bits())?;
156        self.initialized = true;
157        Ok(())
158    }
159
160    /// Runs one conversion and returns the result.
161    ///
162    /// Initializes the part first if [`init`](Opt3001::init) has not run.
163    ///
164    /// # Returns
165    ///
166    /// The result register as a [`Reading`].
167    ///
168    /// # Errors
169    ///
170    /// Returns [`DriverError::Bus`] if the bus fails, [`DriverError::Timeout`] if
171    /// the conversion-ready flag never sets, or any error of [`init`](Opt3001::init).
172    pub fn measure(&mut self) -> Result<Reading, DriverError<I2C::Error>> {
173        if !self.initialized {
174            self.init()?;
175        }
176        let single_shot = self.configuration_in(Mode::SingleShot);
177        self.write(register::CONFIGURATION, single_shot.bits())?;
178        self.delay
179            .delay_ms(u32::from(self.conversion_time.millis()));
180
181        let mut ready = false;
182        for _ in 0..STATUS_POLLS {
183            let status = Configuration::from_bits(self.read(register::CONFIGURATION)?);
184            if status.conversion_ready {
185                ready = true;
186                break;
187            }
188            self.delay.delay_ms(POLL_INTERVAL_MILLIS);
189        }
190        if !ready {
191            return Err(DriverError::Timeout);
192        }
193        Ok(Reading::new(self.read(register::RESULT)?))
194    }
195
196    /// Writes the low and high limit registers the interrupt pin compares against.
197    ///
198    /// # Arguments
199    ///
200    /// * `low_milli_lux` - the low limit in millilux.
201    /// * `high_milli_lux` - the high limit in millilux.
202    ///
203    /// # Errors
204    ///
205    /// Returns [`DriverError::Bus`] if the bus fails.
206    pub fn set_limits(
207        &mut self,
208        low_milli_lux: u32,
209        high_milli_lux: u32,
210    ) -> Result<(), DriverError<I2C::Error>> {
211        self.write(register::LOW_LIMIT, raw_from_milli_lux(low_milli_lux))?;
212        self.write(register::HIGH_LIMIT, raw_from_milli_lux(high_milli_lux))
213    }
214
215    fn read(&mut self, register: u8) -> Result<u16, DriverError<I2C::Error>> {
216        read_word(&mut self.bus, self.address, register).map_err(DriverError::Bus)
217    }
218
219    fn write(&mut self, register: u8, value: u16) -> Result<(), DriverError<I2C::Error>> {
220        write_word(&mut self.bus, self.address, register, value).map_err(DriverError::Bus)
221    }
222}
223
224impl<I2C, D> Sensor for Opt3001<I2C, D>
225where
226    I2C: I2c + Send,
227    I2C::Error: core::fmt::Debug,
228    D: DelayNs + Send,
229{
230    type Reading = Reading;
231
232    async fn read(&mut self) -> pamoja_core::Result<Reading> {
233        self.measure().map_err(pamoja_core::Error::from)
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    use crate::opt3001::{I2C_ADDRESS_GND, I2C_ADDRESS_SDA};
241    use alloc::vec;
242    use alloc::vec::Vec;
243    use embedded_hal::i2c::{ErrorKind, NoAcknowledgeSource};
244    use pamoja_hal::script::{block_on, DelayLog, I2cScript, I2cStep};
245
246    const CONVERSION_READY: u16 = 1 << 7;
247
248    fn word(address: u8, register: u8, value: u16) -> I2cStep {
249        let [high, low] = value.to_be_bytes();
250        I2cStep::write(address, [register, high, low])
251    }
252
253    fn reply(address: u8, register: u8, value: u16) -> I2cStep {
254        I2cStep::write_read(address, [register], value.to_be_bytes())
255    }
256
257    fn init_steps(address: u8, shutdown: u16) -> Vec<I2cStep> {
258        vec![
259            reply(address, register::MANUFACTURER_ID, MANUFACTURER_ID),
260            reply(address, register::DEVICE_ID, DEVICE_ID),
261            word(address, register::CONFIGURATION, shutdown),
262        ]
263    }
264
265    #[test]
266    fn a_single_shot_conversion_follows_the_datasheet_sequence() {
267        let raw = raw_from_milli_lux(320_000);
268        let mut steps = init_steps(I2C_ADDRESS_GND, 0xC810);
269        steps.extend([
270            word(I2C_ADDRESS_GND, register::CONFIGURATION, 0xCA10),
271            reply(
272                I2C_ADDRESS_GND,
273                register::CONFIGURATION,
274                0xC810 | CONVERSION_READY,
275            ),
276            reply(I2C_ADDRESS_GND, register::RESULT, raw),
277        ]);
278        let mut sensor = Opt3001::new(I2cScript::new(steps), I2C_ADDRESS_GND, DelayLog::new());
279        let reading = sensor.measure().unwrap();
280        assert_eq!(reading.raw(), raw);
281        assert_eq!(reading.milli_lux(), super::super::milli_lux(raw));
282        let (bus, delay) = sensor.release();
283        assert!(bus.done());
284        assert_eq!(delay.waits_ns(), [800_000_000]);
285    }
286
287    #[test]
288    fn the_integration_time_and_range_reach_the_configuration_and_the_wait() {
289        let configuration = Configuration {
290            range_number: 5,
291            conversion_time: ConversionTime::Ms100,
292            mode: Mode::Shutdown,
293            ..Configuration::default()
294        };
295        let shutdown = configuration.bits();
296        let single_shot = Configuration {
297            mode: Mode::SingleShot,
298            ..configuration
299        }
300        .bits();
301        let mut steps = init_steps(I2C_ADDRESS_SDA, shutdown);
302        steps.extend([
303            word(I2C_ADDRESS_SDA, register::CONFIGURATION, single_shot),
304            reply(I2C_ADDRESS_SDA, register::CONFIGURATION, shutdown),
305            reply(
306                I2C_ADDRESS_SDA,
307                register::CONFIGURATION,
308                shutdown | CONVERSION_READY,
309            ),
310            reply(I2C_ADDRESS_SDA, register::RESULT, 0x0C80),
311        ]);
312        let mut sensor = Opt3001::new(I2cScript::new(steps), I2C_ADDRESS_SDA, DelayLog::new())
313            .with_conversion_time(ConversionTime::Ms100)
314            .with_range(5);
315        assert_eq!(sensor.configuration(), configuration);
316        assert_eq!(sensor.measure().unwrap().milli_lux(), 32_000);
317        let (bus, delay) = sensor.release();
318        assert!(bus.done());
319        assert_eq!(delay.waits_ns(), [100_000_000, 10_000_000]);
320    }
321
322    #[test]
323    fn a_part_with_the_wrong_id_is_refused() {
324        let steps = [
325            reply(I2C_ADDRESS_GND, register::MANUFACTURER_ID, MANUFACTURER_ID),
326            reply(I2C_ADDRESS_GND, register::DEVICE_ID, 0x3002),
327        ];
328        let mut sensor = Opt3001::new(I2cScript::new(steps), I2C_ADDRESS_GND, DelayLog::new());
329        assert_eq!(
330            sensor.init(),
331            Err(DriverError::Sensor(SensorError::Identity))
332        );
333    }
334
335    #[test]
336    fn a_conversion_that_never_finishes_is_a_timeout() {
337        let mut steps = init_steps(I2C_ADDRESS_GND, 0xC810);
338        steps.push(word(I2C_ADDRESS_GND, register::CONFIGURATION, 0xCA10));
339        for _ in 0..STATUS_POLLS {
340            steps.push(reply(I2C_ADDRESS_GND, register::CONFIGURATION, 0xC810));
341        }
342        let mut sensor = Opt3001::new(I2cScript::new(steps), I2C_ADDRESS_GND, DelayLog::new());
343        assert_eq!(sensor.measure(), Err(DriverError::Timeout));
344        let (bus, delay) = sensor.release();
345        assert!(bus.done());
346        assert_eq!(delay.total_millis(), 800 + 10 * u64::from(STATUS_POLLS));
347    }
348
349    #[test]
350    fn a_bus_fault_surfaces_as_a_bus_error() {
351        let kind = ErrorKind::NoAcknowledge(NoAcknowledgeSource::Address);
352        let mut sensor = Opt3001::new(
353            I2cScript::new([I2cStep::fault(I2C_ADDRESS_GND, kind)]),
354            I2C_ADDRESS_GND,
355            DelayLog::new(),
356        );
357        assert!(matches!(sensor.init(), Err(DriverError::Bus(_))));
358    }
359
360    #[test]
361    fn limits_are_written_in_the_result_format() {
362        let mut steps = init_steps(I2C_ADDRESS_GND, 0xC810);
363        steps.push(word(
364            I2C_ADDRESS_GND,
365            register::LOW_LIMIT,
366            raw_from_milli_lux(10_000),
367        ));
368        steps.push(word(
369            I2C_ADDRESS_GND,
370            register::HIGH_LIMIT,
371            raw_from_milli_lux(5_000_000),
372        ));
373        let mut sensor = Opt3001::new(I2cScript::new(steps), I2C_ADDRESS_GND, DelayLog::new());
374        sensor.init().unwrap();
375        sensor.set_limits(10_000, 5_000_000).unwrap();
376        assert!(sensor.release().0.done());
377    }
378
379    #[test]
380    fn as_a_sensor_it_reads_and_maps_to_lux() {
381        let mut steps = init_steps(I2C_ADDRESS_GND, 0xC810);
382        steps.extend([
383            word(I2C_ADDRESS_GND, register::CONFIGURATION, 0xCA10),
384            reply(
385                I2C_ADDRESS_GND,
386                register::CONFIGURATION,
387                0xC810 | CONVERSION_READY,
388            ),
389            reply(I2C_ADDRESS_GND, register::RESULT, 0x0C80),
390        ]);
391        let sensor = Opt3001::new(I2cScript::new(steps), I2C_ADDRESS_GND, DelayLog::new());
392        let mut lux = sensor.map(|reading| reading.lux());
393        assert!((block_on(lux.read()).unwrap() - 32.0).abs() < 1e-3);
394    }
395
396    #[test]
397    fn identity_and_one_conversion_make_the_transfers_the_datasheet_gives() {
398        const PART: u8 = I2C_ADDRESS_GND;
399        let result = crate::opt3001::raw_from_milli_lux(320_000).to_be_bytes();
400        let bus = I2cScript::new([
401            I2cStep::write_read(PART, [0x7E], [0x54, 0x49]),
402            I2cStep::write_read(PART, [0x7F], [0x30, 0x01]),
403            I2cStep::write(PART, [0x01, 0xC8, 0x10]),
404            I2cStep::write(PART, [0x01, 0xCA, 0x10]),
405            I2cStep::write_read(PART, [0x01], [0xC8, 0x90]),
406            I2cStep::write_read(PART, [0x00], result),
407        ]);
408
409        let sensor = Opt3001::new(bus, PART, DelayLog::new());
410        let mut lux = sensor.map(|reading| reading.lux());
411        let reading = block_on(Sensor::read(&mut lux)).expect("the scripted part answers");
412        assert!((reading - 320.0).abs() < 0.5);
413    }
414}