pir_motion_sensor/sensor/
motion.rs

1use log::info;
2use rppal::gpio::Mode::Input;
3use rppal::gpio::{Gpio, IoPin};
4use std::time::{Instant, SystemTime};
5use tokio::sync::mpsc::{self, Receiver, Sender};
6
7const DETECTION_MARGIN_MILISECS: u64 = 10;
8
9use super::config::SensorConfig;
10
11#[derive(Debug)]
12pub struct MotionSensor {
13    // config
14    pub config: SensorConfig,
15    pub detection_channel: Sender<(String, SystemTime)>,
16    // last "valid" detection time
17    pub last_detection_time: Option<SystemTime>,
18    // last moment when gpio PIN was set to High (it may not mean "valid" detection - depends on configuration)
19    pub last_any_detection_time: Option<Instant>,
20    // additional settings for the future
21    pub additional_settings: SensorAdditionalSettings,
22}
23
24#[derive(Debug)]
25pub struct SensorAdditionalSettings {
26    pub stop: bool,
27    pub sensor_test_data: Option<Vec<u64>>,
28    pub sensor_test_time: Option<Instant>,
29    pub sensor_test_index: usize,
30    pub pin: Option<IoPin>,
31    pub detection_stream_channel: Option<Sender<bool>>,
32    pub detections_receiver: Receiver<bool>,
33}
34
35impl MotionSensor {
36    pub fn new(
37        sensor_name: String,
38        sensor_pin_number: u8,
39        sensor_refresh_rate_milisecs: u64,
40        sensor_motion_time_period_milisecs: u64,
41        sensor_minimal_triggering_number: i16,
42        sensor_transmission_channel: Sender<(String, SystemTime)>,
43        sensor_test_data: Option<Vec<u64>>,
44    ) -> Self {
45        let config = SensorConfig {
46            name: sensor_name,
47            pin_number: sensor_pin_number,
48            refresh_rate_milisecs: sensor_refresh_rate_milisecs,
49            motion_time_period_milisecs: sensor_motion_time_period_milisecs,
50            minimal_triggering_number: sensor_minimal_triggering_number,
51        };
52
53        // default values
54        let mut pin_init = None;
55        let detection_stream_channel_init;
56
57        if sensor_test_data.is_none() {
58            let gpio = Gpio::new().unwrap();
59            let pin: IoPin;
60            loop {
61                pin = match gpio.get(sensor_pin_number) {
62                    Ok(p) => p.into_io(Input),
63                    Err(_) => {
64                        continue;
65                    }
66                };
67                break;
68            }
69
70            pin_init = Some(pin);
71        }
72
73        let (detections_stream, detections_receiver) = mpsc::channel(10);
74
75        detection_stream_channel_init = Some(detections_stream);
76
77        //
78        // initialization
79        //
80        info!("Starting sensor: {:#?}", config.name);
81
82        // by default it's None when sensor is initialized
83        // for testing it will be initialized with current time
84        let sensor_test_time = None;
85        let sensor_test_index = 0;
86
87        let additional_settings = SensorAdditionalSettings {
88            stop: false,
89            sensor_test_data,
90            sensor_test_time,
91            sensor_test_index,
92            pin: pin_init,
93            detection_stream_channel: detection_stream_channel_init,
94            detections_receiver,
95        };
96
97        Self {
98            config,
99            detection_channel: sensor_transmission_channel,
100            last_detection_time: None,
101            last_any_detection_time: None,
102            additional_settings,
103        }
104    }
105
106    pub async fn reading_from_sensor(&mut self) {
107        //
108        let detection_stream_channel = self.additional_settings.detection_stream_channel.clone();
109
110        //
111        // BEGIN: real detections from GPIO
112        //
113        if self.additional_settings.sensor_test_data.is_none() {
114            let pin = self.additional_settings.pin.as_mut().expect(
115                "sensor not initialized - this method should be called AFTER start_detector()",
116            );
117            //
118            if pin.is_high() {
119                // try to send as many as possible but if the channel is full, we just ignore it
120                // that's why try_send() is used here
121                // unwrap_or_default() - because we don't care if each single detection is successfully
122                //                       sent
123                if let Some(detection_stream_channel) = detection_stream_channel.as_ref() {
124                    detection_stream_channel
125                        .try_send(true)
126                        .unwrap_or_default()
127                }
128            }
129        }
130        //
131        // END: real detections from GPIO
132        //
133
134        //
135        // BEGIN: testing detections logic: we take detections from Vec<u64> - each such detection
136        //        invokes same actions as normal GPIO pin
137        //
138        if self.additional_settings.sensor_test_data.is_some() {
139            if self.additional_settings.sensor_test_time.is_none() {
140                // starting internal timer which will be used as a reference for testing
141                self.additional_settings.sensor_test_time = Some(Instant::now());
142            }
143
144            let detections_time_list = self.additional_settings.sensor_test_data.clone().unwrap();
145            let detections_time_list_length = detections_time_list.len();
146
147            let current_index = self.additional_settings.sensor_test_index;
148
149            if current_index < detections_time_list_length {
150                let milisecs_now = self
151                    .additional_settings
152                    .sensor_test_time
153                    .unwrap()
154                    .elapsed()
155                    .as_millis() as u64;
156
157                // taking detection time from the list
158                let testing_detection_time_milisecs = detections_time_list[current_index];
159
160                // this +10 milisec is added as a precaution - sometimes we want to have detection at 500 milisec,
161                // but it happens at 501 or 502 milisecond which is "too late". Normal condition here (==) would fail so
162                // we check broader condition ">=" but using only this this would be not sufficient here (each subsequent detection
163                // will happen in >= time - obvious). So additionaly we check a small margin of +10 additional miliseconds which
164                // is here as DETECTION_MARGIN_MILISECS constant
165                if milisecs_now >= testing_detection_time_milisecs
166                    && milisecs_now + DETECTION_MARGIN_MILISECS > testing_detection_time_milisecs
167                {
168                    // updating index - next time we will take next detection from the list
169                    self.additional_settings.sensor_test_index += 1;
170
171                    // sending testing detection to the channel which looks like "real"
172                    if let Some(detection_channel) = detection_stream_channel {
173                        detection_channel
174                            .try_send(true)
175                            .expect("cannot use channel for detection stream");
176                    }
177                }
178            }
179        }
180        //
181        // END: testing detections logic: we take detections from Vec<u64> - each such detection
182        //      invokes same actions as normal GPIO pin
183        //
184    }
185
186    //
187    // processing detections, they may be real from GPIO or from testing code.
188    //
189    pub async fn process_detections(
190        &mut self,
191        last_sensor_trigger_count: i16,
192        last_check_time: Instant,
193    ) -> (i16, Instant) {
194        let mut sensor_trigger_count = last_sensor_trigger_count;
195
196        if last_check_time.elapsed().as_millis() as u64 <= self.config.refresh_rate_milisecs {
197            // "sensor refresh rate" - if it's too early to check, then we return instantly
198            // but we don't modify "last_check_time" - in another function await this time still
199            // will be used to determine if it's time to check internal channel for detections
200            return (last_sensor_trigger_count, last_check_time);
201        }
202
203        // sensor refresh rate is larger or equal the actual timer - now we can read state of the
204        // channel and proceed with the logic. In that sense, short "refresh_rate_milisec" values
205        // just means we read detection channel more often and we can count more detections.
206
207        //
208        // reading detections from channel - these detections may come from real gpio
209        // pin or from tests without gpio involved
210        //
211        // try_recv() because this is an async func - we don't care if there is no detection data
212        // in the channel and moving forward asap. If there are detections in the channel then we
213        // will proceed them normally, but the highest priority of this function is to don't block it.
214        //
215        if self
216            .additional_settings
217            .detections_receiver
218            .try_recv()
219            .is_ok()
220        {
221            // this func is async so we increment counter (or not)
222            sensor_trigger_count += 1;
223
224            // because we use Instant::now, the real time difference needs to be multiply by counts to
225            // reflect real motion time period time
226            let time_difference = self.config.refresh_rate_milisecs * sensor_trigger_count as u64;
227
228            if time_difference > self.config.motion_time_period_milisecs {
229                // this is a new detection - reset counter
230                sensor_trigger_count = 1;
231            }
232
233            if sensor_trigger_count >= self.config.minimal_triggering_number {
234                //
235                // minimal_triggering_number is reached - this is valid detection so send it to the main channel
236                //
237                let t = SystemTime::now();
238                self.last_detection_time = Some(t);
239
240                // sending real (VALID) detection to the main channel as we reached suitable "minimal_triggering_number"
241                self.detection_channel
242                    .try_send((self.config.name.clone(), t))
243                    .unwrap_or_default();
244
245                // reset counter - next detection will be counted as different one from zero again
246                sensor_trigger_count = 0;
247            }
248        }
249
250        // return current counter and time which later will be used to determine another detections (valid or pre-detections)
251        (sensor_trigger_count, Instant::now())
252    }
253}