person_sensor/
person_sensor.rs

1use core::marker::PhantomData;
2
3use crc16::MCRF4XX;
4use embedded_hal_async::{digital::Wait, i2c::I2c};
5
6use crate::{Face, PersonID, MAX_DETECTIONS};
7
8const PERSON_SENSOR_I2C_ADDRESS: u8 = 0x62;
9
10#[repr(u8)]
11pub(crate) enum PersonSensorMode {
12    /// Lowest power mode, sensor is in standby and not capturing.
13    Standby = 0x00,
14    /// Capture continuously, setting the GPIO trigger pin to high if a face is detected.
15    Continuous = 0x01,
16}
17
18/// The operating mode of the face labeling model.
19#[derive(Debug, PartialEq, Eq, Clone, Copy)]
20#[repr(u8)]
21pub enum IDMode {
22    /// Faces will be returned with both the location and ID, if IDs have been set up.
23    ///
24    /// The device will generate results at a lower rate in this mode due to the additional
25    /// processing.
26    LabelAndLocation = 0x01,
27    /// Faces will be returned with only the location, even if IDs are persisted.
28    ///
29    /// The device will generate results at a higher rate in this mode.
30    LocationOnly = 0x00,
31}
32
33#[derive(Debug, PartialEq, Eq, Clone, Copy)]
34pub enum ReadError<E> {
35    ChecksumMismatch,
36    I2CError(E),
37}
38
39impl<E> From<E> for ReadError<E> {
40    fn from(error: E) -> Self {
41        Self::I2CError(error)
42    }
43}
44
45pub struct ContinuousCaptureMode;
46pub struct StandbyMode;
47
48/// Person sensor driver.
49///
50/// The sensor can be used in two modes: continuous capture and standby. In continuous capture
51/// mode, the sensor continuously captures frames and sends the results over I2C. In standby mode,
52/// the sensor is in a low-power state and only captures a single frame when requested.
53///
54/// To create the sensor, use a `PersonSensorBuilder`. The builder allows you to set the mode
55/// and interrupt pin.
56///
57/// Example:
58///
59/// ```ignore
60/// let sda = p.PIN_2;
61/// let scl = p.PIN_3;
62/// let i2c = I2c::new_async(p.I2C1, scl, sda, Irqs, Config::default());
63/// let interrupt = p.PIN_4;
64///
65/// let mut person_sensor = PersonSensorBuilder::new_continuous(i2c, true)
66///     .with_interrupt(interrupt)
67///     .build()
68///     .await
69///     .unwrap();
70///
71/// loop {
72///    if let Ok(faces) = person_sensor.get_detections().await {
73///        // Do something with the results
74///    }
75/// }
76/// ```
77#[derive(Debug)]
78pub struct PersonSensor<I2C, INT, MODE> {
79    pub(crate) i2c: I2C,
80    pub(crate) interrupt: INT,
81    pub(crate) mode: PhantomData<MODE>,
82    pub(crate) validate_checksum: bool,
83}
84
85impl<I2C, INT, MODE> PersonSensor<I2C, INT, MODE>
86where
87    I2C: I2c,
88{
89    /// Returns the latest results from the sensor.
90    async fn latest_results(
91        &mut self,
92    ) -> Result<heapless::Vec<Face, MAX_DETECTIONS>, ReadError<I2C::Error>> {
93        let mut buffer = [0u8; 39];
94        self.i2c
95            .read(PERSON_SENSOR_I2C_ADDRESS, &mut buffer)
96            .await?;
97
98        if self.validate_checksum {
99            let checksum = crc16::State::<MCRF4XX>::calculate(&buffer[..37]);
100            if u16::from_le_bytes([buffer[37], buffer[38]]) != checksum {
101                return Err(ReadError::<I2C::Error>::ChecksumMismatch);
102            }
103        }
104
105        let mut faces = heapless::Vec::<Face, MAX_DETECTIONS>::new();
106
107        let num_faces = buffer[4];
108
109        #[expect(clippy::cast_possible_wrap)]
110        for face_num in 0..num_faces {
111            let face_start_offset = 5 + face_num as usize * 8;
112
113            let id_confidence = buffer[face_start_offset + 5] as i8;
114            let person_id = match id_confidence {
115                0 => None,
116                _ => Some(PersonID::new_unchecked(buffer[face_start_offset + 6])),
117            };
118
119            let face = Face {
120                box_confidence: buffer[face_start_offset],
121                box_left: buffer[face_start_offset + 1],
122                box_top: buffer[face_start_offset + 2],
123                box_right: buffer[face_start_offset + 3],
124                box_bottom: buffer[face_start_offset + 4],
125                id_confidence,
126                id: person_id,
127                is_facing: buffer[face_start_offset + 7] > 0,
128            };
129
130            match faces.push(face) {
131                Ok(()) => {}
132                Err(_) => break,
133            };
134        }
135
136        Ok(faces)
137    }
138
139    /// Sets the mode of the sensor.
140    pub(crate) async fn set_mode(&mut self, mode: PersonSensorMode) -> Result<(), I2C::Error> {
141        self.i2c
142            .write(PERSON_SENSOR_I2C_ADDRESS, &[0x01, mode as u8])
143            .await
144    }
145
146    /// Enable / Disable the ID model. Greater performance can be achieved by disabling ID labeling.
147    pub async fn set_id_mode(&mut self, mode: IDMode) -> Result<(), I2C::Error> {
148        self.i2c
149            .write(PERSON_SENSOR_I2C_ADDRESS, &[0x02, mode as u8])
150            .await
151    }
152
153    /// Enable / Disable the ID model.
154    ///
155    /// With this flag set to false, only bounding boxes are captured and the framerate is increased.
156    #[deprecated(
157        since = "0.3.1",
158        note = "Please use `set_id_mode` instead. This method will be removed in a future release."
159    )]
160    pub async fn enable_id_model(&mut self, enable: bool) -> Result<(), I2C::Error> {
161        self.i2c
162            .write(PERSON_SENSOR_I2C_ADDRESS, &[0x02, u8::from(enable)])
163            .await
164    }
165
166    /// Validate the checksum of the data received from the sensor. On by default.
167    ///
168    /// Checksum validation may be useful for diagnosing I2C communication issues,
169    /// but is not necessary for normal operation.
170    pub fn set_checksum_enabled(&mut self, enable: bool) {
171        self.validate_checksum = enable;
172    }
173
174    /// Calibrate the next identified frame as person N, from 0 to 7.
175    /// If two frames pass with no person, this label is discarded.
176    ///
177    /// This will not return the result of the calibration. The only failure
178    /// is if the I2C write fails. You may wish to follow calibration with a
179    /// check for detections containing the ID you just attempted to label.
180    pub async fn label_next_id(&mut self, id: PersonID) -> Result<(), I2C::Error> {
181        self.i2c
182            .write(PERSON_SENSOR_I2C_ADDRESS, &[0x04, id.into()])
183            .await
184    }
185
186    /// Store any recognized IDs even when unpowered.
187    ///
188    /// Both current and future IDs will be retained when this is set to true.
189    pub async fn set_persist_ids(&mut self, persist: bool) -> Result<(), I2C::Error> {
190        self.i2c
191            .write(PERSON_SENSOR_I2C_ADDRESS, &[0x05, u8::from(persist)])
192            .await
193    }
194
195    /// Wipe any recognized IDs from storage.
196    pub async fn erase_ids(&mut self) -> Result<(), I2C::Error> {
197        self.i2c
198            .write(PERSON_SENSOR_I2C_ADDRESS, &[0x06, 0x00])
199            .await
200    }
201
202    /// Whether to enable the LED indicator on the sensor.
203    pub async fn set_indicator(&mut self, enabled: bool) -> Result<(), I2C::Error> {
204        self.i2c
205            .write(PERSON_SENSOR_I2C_ADDRESS, &[0x07, u8::from(enabled)])
206            .await
207    }
208}
209
210impl<I2C, INT> PersonSensor<I2C, INT, StandbyMode>
211where
212    I2C: I2c,
213{
214    /// Capture a single frame and reads the results
215    pub async fn capture_once(
216        &mut self,
217    ) -> Result<heapless::Vec<Face, MAX_DETECTIONS>, ReadError<I2C::Error>> {
218        self.i2c
219            .write(PERSON_SENSOR_I2C_ADDRESS, &[0x03, 0x00])
220            .await?;
221
222        self.latest_results().await
223    }
224
225    /// Switches the sensor to continuous capture mode
226    pub async fn into_continuous_mode(
227        self,
228    ) -> Result<PersonSensor<I2C, INT, ContinuousCaptureMode>, I2C::Error> {
229        let mut sensor = self;
230        sensor.set_mode(PersonSensorMode::Continuous).await?;
231        Ok(PersonSensor {
232            i2c: sensor.i2c,
233            interrupt: sensor.interrupt,
234            mode: PhantomData,
235            validate_checksum: sensor.validate_checksum,
236        })
237    }
238}
239
240impl<I2C, INT> PersonSensor<I2C, INT, ContinuousCaptureMode>
241where
242    I2C: I2c,
243{
244    /// Switches the sensor into a lower power standby mode. Only single-shot capture is possible
245    /// in this mode.
246    pub async fn into_standby_mode(
247        self,
248    ) -> Result<PersonSensor<I2C, INT, StandbyMode>, I2C::Error> {
249        let mut sensor = self;
250        sensor.set_mode(PersonSensorMode::Standby).await?;
251        Ok(PersonSensor {
252            i2c: sensor.i2c,
253            interrupt: sensor.interrupt,
254            mode: PhantomData,
255            validate_checksum: sensor.validate_checksum,
256        })
257    }
258
259    /// Returns the latest results from the sensor.
260    ///
261    /// Depending on the device version and configuration, detections are updated at different
262    /// rates. This method does not wait for _new_ detections to be available. The last detections
263    /// will repeatedly read until the device produces new ones.
264    ///
265    /// It is your responsibility to either sensibly rate-limit fetching results to the maximum rate
266    /// of the sensor or initialize the driver with an interrupt pin to be notified when new
267    /// results are available.
268    pub async fn get_detections(
269        &mut self,
270    ) -> Result<heapless::Vec<Face, MAX_DETECTIONS>, ReadError<I2C::Error>> {
271        self.latest_results().await
272    }
273}
274
275impl<I2C, INT> PersonSensor<I2C, INT, ContinuousCaptureMode>
276where
277    INT: Wait,
278{
279    /// Wait for the person sensor to trigger an interrupt indicating a person has been detected.
280    /// Returns immediately if a person is currently detected.
281    pub async fn wait_for_person(&mut self) -> Result<(), INT::Error> {
282        self.interrupt.wait_for_high().await
283    }
284}