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}