pokeys_lib/
sensors.rs

1//! EasySensors support
2
3use crate::device::PoKeysDevice;
4use crate::error::{PoKeysError, Result};
5use serde::{Deserialize, Serialize};
6
7/// EasySensor data structure
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct EasySensor {
10    pub sensor_value: i32,
11    pub sensor_type: u8,
12    pub sensor_refresh_period: u8,
13    pub sensor_failsafe_config: u8,
14    pub sensor_reading_id: u8,
15    pub sensor_id: [u8; 8],
16    pub sensor_ok_status: u8,
17}
18
19impl EasySensor {
20    pub fn new() -> Self {
21        Self {
22            sensor_value: 0,
23            sensor_type: 0,
24            sensor_refresh_period: 0,
25            sensor_failsafe_config: 0,
26            sensor_reading_id: 0,
27            sensor_id: [0; 8],
28            sensor_ok_status: 0,
29        }
30    }
31
32    pub fn is_ok(&self) -> bool {
33        self.sensor_ok_status != 0
34    }
35
36    pub fn get_refresh_period_seconds(&self) -> f32 {
37        self.sensor_refresh_period as f32 * 0.1
38    }
39
40    pub fn set_refresh_period_seconds(&mut self, seconds: f32) {
41        self.sensor_refresh_period = (seconds * 10.0) as u8;
42    }
43
44    pub fn get_failsafe_timeout(&self) -> u8 {
45        self.sensor_failsafe_config & 0x3F
46    }
47
48    pub fn set_failsafe_timeout(&mut self, timeout_seconds: u8) {
49        self.sensor_failsafe_config =
50            (self.sensor_failsafe_config & 0xC0) | (timeout_seconds & 0x3F);
51    }
52
53    pub fn is_failsafe_invalid_zero(&self) -> bool {
54        (self.sensor_failsafe_config & 0x40) != 0
55    }
56
57    pub fn set_failsafe_invalid_zero(&mut self, enable: bool) {
58        if enable {
59            self.sensor_failsafe_config |= 0x40;
60        } else {
61            self.sensor_failsafe_config &= !0x40;
62        }
63    }
64
65    pub fn is_failsafe_invalid_max(&self) -> bool {
66        (self.sensor_failsafe_config & 0x80) != 0
67    }
68
69    pub fn set_failsafe_invalid_max(&mut self, enable: bool) {
70        if enable {
71            self.sensor_failsafe_config |= 0x80;
72        } else {
73            self.sensor_failsafe_config &= !0x80;
74        }
75    }
76}
77
78impl Default for EasySensor {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84/// Custom sensor unit descriptor
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct CustomSensorUnit {
87    pub html_code: [u8; 32],
88    pub simple_text: [u8; 8],
89}
90
91impl CustomSensorUnit {
92    pub fn new() -> Self {
93        Self {
94            html_code: [0; 32],
95            simple_text: [0; 8],
96        }
97    }
98
99    pub fn set_html_code(&mut self, code: &str) -> Result<()> {
100        if code.len() > 32 {
101            return Err(PoKeysError::Parameter("HTML code too long".to_string()));
102        }
103
104        self.html_code.fill(0);
105        let code_bytes = code.as_bytes();
106        self.html_code[..code_bytes.len()].copy_from_slice(code_bytes);
107        Ok(())
108    }
109
110    pub fn get_html_code(&self) -> String {
111        let end = self.html_code.iter().position(|&b| b == 0).unwrap_or(32);
112        String::from_utf8_lossy(&self.html_code[..end]).to_string()
113    }
114
115    pub fn set_simple_text(&mut self, text: &str) -> Result<()> {
116        if text.len() > 8 {
117            return Err(PoKeysError::Parameter("Simple text too long".to_string()));
118        }
119
120        self.simple_text.fill(0);
121        let text_bytes = text.as_bytes();
122        self.simple_text[..text_bytes.len()].copy_from_slice(text_bytes);
123        Ok(())
124    }
125
126    pub fn get_simple_text(&self) -> String {
127        let end = self.simple_text.iter().position(|&b| b == 0).unwrap_or(8);
128        String::from_utf8_lossy(&self.simple_text[..end]).to_string()
129    }
130}
131
132impl Default for CustomSensorUnit {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138impl PoKeysDevice {
139    /// Configure EasySensor
140    pub fn configure_easy_sensor(
141        &mut self,
142        sensor_index: usize,
143        sensor_type: u8,
144        sensor_id: &[u8; 8],
145        refresh_period_seconds: f32,
146        reading_id: u8,
147    ) -> Result<()> {
148        if sensor_index >= self.easy_sensors.len() {
149            return Err(PoKeysError::Parameter("Invalid sensor index".to_string()));
150        }
151
152        let sensor = &mut self.easy_sensors[sensor_index];
153        sensor.sensor_type = sensor_type;
154        sensor.sensor_id = *sensor_id;
155        sensor.set_refresh_period_seconds(refresh_period_seconds);
156        sensor.sensor_reading_id = reading_id;
157
158        // Send sensor configuration to device
159        self.send_easy_sensor_configuration(sensor_index)?;
160        Ok(())
161    }
162
163    /// Enable EasySensor
164    pub fn enable_easy_sensor(&mut self, sensor_index: usize, enable: bool) -> Result<()> {
165        if sensor_index >= self.easy_sensors.len() {
166            return Err(PoKeysError::Parameter("Invalid sensor index".to_string()));
167        }
168
169        // Implementation would enable/disable the sensor
170        self.send_request(0xF0, sensor_index as u8, if enable { 1 } else { 0 }, 0, 0)?;
171        Ok(())
172    }
173
174    /// Read EasySensor value
175    pub fn read_easy_sensor(&mut self, sensor_index: usize) -> Result<i32> {
176        if sensor_index >= self.easy_sensors.len() {
177            return Err(PoKeysError::Parameter("Invalid sensor index".to_string()));
178        }
179
180        // Read all sensor values
181        self.read_all_easy_sensors()?;
182
183        Ok(self.easy_sensors[sensor_index].sensor_value)
184    }
185
186    /// Read all EasySensor values
187    pub fn read_all_easy_sensors(&mut self) -> Result<()> {
188        let response = self.send_request(0xF1, 0, 0, 0, 0)?;
189
190        // Parse sensor values from response
191        let mut data_index = 8;
192        for sensor in &mut self.easy_sensors {
193            if data_index + 3 < response.len() {
194                sensor.sensor_value = i32::from_le_bytes([
195                    response[data_index],
196                    response[data_index + 1],
197                    response[data_index + 2],
198                    response[data_index + 3],
199                ]);
200                data_index += 4;
201
202                // Read sensor status
203                if data_index < response.len() {
204                    sensor.sensor_ok_status = response[data_index];
205                    data_index += 1;
206                }
207            }
208        }
209
210        Ok(())
211    }
212
213    /// Configure sensor failsafe settings
214    pub fn configure_sensor_failsafe(
215        &mut self,
216        sensor_index: usize,
217        timeout_seconds: u8,
218        invalid_value_zero: bool,
219        invalid_value_max: bool,
220    ) -> Result<()> {
221        if sensor_index >= self.easy_sensors.len() {
222            return Err(PoKeysError::Parameter("Invalid sensor index".to_string()));
223        }
224
225        self.easy_sensors[sensor_index].set_failsafe_timeout(timeout_seconds);
226        self.easy_sensors[sensor_index].set_failsafe_invalid_zero(invalid_value_zero);
227        self.easy_sensors[sensor_index].set_failsafe_invalid_max(invalid_value_max);
228
229        let failsafe_config = self.easy_sensors[sensor_index].sensor_failsafe_config;
230
231        // Send failsafe configuration to device
232        self.send_request(0xF2, sensor_index as u8, failsafe_config, 0, 0)?;
233
234        Ok(())
235    }
236
237    /// Get sensor status
238    pub fn get_sensor_status(&self, sensor_index: usize) -> Result<bool> {
239        if sensor_index >= self.easy_sensors.len() {
240            return Err(PoKeysError::Parameter("Invalid sensor index".to_string()));
241        }
242
243        Ok(self.easy_sensors[sensor_index].is_ok())
244    }
245
246    /// Set custom sensor unit
247    pub fn set_custom_sensor_unit(
248        &mut self,
249        unit_index: usize,
250        unit: &CustomSensorUnit,
251    ) -> Result<()> {
252        if unit_index >= 16 {
253            return Err(PoKeysError::Parameter("Invalid unit index".to_string()));
254        }
255
256        // Send HTML code
257        self.send_request(
258            0xF3,
259            unit_index as u8,
260            unit.html_code[0],
261            unit.html_code[1],
262            unit.html_code[2],
263        )?;
264
265        // Send remaining HTML code in chunks
266        // Implementation would continue for all 32 bytes
267
268        // Send simple text
269        self.send_request(
270            0xF4,
271            unit_index as u8,
272            unit.simple_text[0],
273            unit.simple_text[1],
274            unit.simple_text[2],
275        )?;
276
277        Ok(())
278    }
279
280    /// Send sensor configuration to device
281    fn send_easy_sensor_configuration(&mut self, sensor_index: usize) -> Result<()> {
282        // Copy sensor data to avoid borrow checker issues
283        let sensor_type = self.easy_sensors[sensor_index].sensor_type;
284        let sensor_refresh_period = self.easy_sensors[sensor_index].sensor_refresh_period;
285        let sensor_reading_id = self.easy_sensors[sensor_index].sensor_reading_id;
286        let sensor_id = self.easy_sensors[sensor_index].sensor_id;
287
288        // Send basic configuration
289        self.send_request(
290            0xF5,
291            sensor_index as u8,
292            sensor_type,
293            sensor_refresh_period,
294            sensor_reading_id,
295        )?;
296
297        // Send sensor ID
298        self.send_request(
299            0xF6,
300            sensor_index as u8,
301            sensor_id[0],
302            sensor_id[1],
303            sensor_id[2],
304        )?;
305
306        self.send_request(
307            0xF7,
308            sensor_index as u8,
309            sensor_id[3],
310            sensor_id[4],
311            sensor_id[5],
312        )?;
313
314        self.send_request(0xF8, sensor_index as u8, sensor_id[6], sensor_id[7], 0)?;
315
316        Ok(())
317    }
318}
319
320// Sensor type constants
321pub mod sensor_types {
322    pub const TEMPERATURE_DS18B20: u8 = 1;
323    pub const HUMIDITY_DHT22: u8 = 2;
324    pub const PRESSURE_BMP180: u8 = 3;
325    pub const LIGHT_BH1750: u8 = 4;
326    pub const DISTANCE_HC_SR04: u8 = 5;
327    pub const ANALOG_VOLTAGE: u8 = 10;
328    pub const ANALOG_CURRENT: u8 = 11;
329    pub const DIGITAL_COUNTER: u8 = 20;
330    pub const ENCODER_POSITION: u8 = 21;
331}
332
333// Convenience functions for common sensor operations
334
335/// Configure DS18B20 temperature sensor
336pub fn configure_ds18b20_sensor(
337    device: &mut PoKeysDevice,
338    sensor_index: usize,
339    sensor_id: &[u8; 8],
340    refresh_period: f32,
341) -> Result<()> {
342    device.configure_easy_sensor(
343        sensor_index,
344        sensor_types::TEMPERATURE_DS18B20,
345        sensor_id,
346        refresh_period,
347        0, // Default reading ID for temperature
348    )
349}
350
351/// Configure analog voltage sensor
352pub fn configure_analog_voltage_sensor(
353    device: &mut PoKeysDevice,
354    sensor_index: usize,
355    pin_id: u8,
356    refresh_period: f32,
357) -> Result<()> {
358    let mut sensor_id = [0u8; 8];
359    sensor_id[0] = pin_id;
360
361    device.configure_easy_sensor(
362        sensor_index,
363        sensor_types::ANALOG_VOLTAGE,
364        &sensor_id,
365        refresh_period,
366        0,
367    )
368}
369
370/// Read temperature from DS18B20 sensor
371pub fn read_temperature_celsius(device: &mut PoKeysDevice, sensor_index: usize) -> Result<f32> {
372    let raw_value = device.read_easy_sensor(sensor_index)?;
373    // DS18B20 returns temperature in 0.0625°C units
374    Ok(raw_value as f32 * 0.0625)
375}
376
377/// Read voltage from analog sensor
378pub fn read_voltage(
379    device: &mut PoKeysDevice,
380    sensor_index: usize,
381    reference_voltage: f32,
382) -> Result<f32> {
383    let raw_value = device.read_easy_sensor(sensor_index)?;
384    // Convert ADC value to voltage (assuming 12-bit ADC)
385    Ok((raw_value as f32 / 4095.0) * reference_voltage)
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391
392    #[test]
393    fn test_easy_sensor_creation() {
394        let sensor = EasySensor::new();
395        assert_eq!(sensor.sensor_value, 0);
396        assert!(!sensor.is_ok());
397        assert_eq!(sensor.get_refresh_period_seconds(), 0.0);
398    }
399
400    #[test]
401    fn test_refresh_period_conversion() {
402        let mut sensor = EasySensor::new();
403
404        sensor.set_refresh_period_seconds(1.5);
405        assert_eq!(sensor.sensor_refresh_period, 15);
406        assert_eq!(sensor.get_refresh_period_seconds(), 1.5);
407
408        sensor.set_refresh_period_seconds(0.1);
409        assert_eq!(sensor.sensor_refresh_period, 1);
410        assert_eq!(sensor.get_refresh_period_seconds(), 0.1);
411    }
412
413    #[test]
414    fn test_failsafe_configuration() {
415        let mut sensor = EasySensor::new();
416
417        sensor.set_failsafe_timeout(30);
418        assert_eq!(sensor.get_failsafe_timeout(), 30);
419
420        sensor.set_failsafe_invalid_zero(true);
421        assert!(sensor.is_failsafe_invalid_zero());
422        assert!(!sensor.is_failsafe_invalid_max());
423
424        sensor.set_failsafe_invalid_max(true);
425        assert!(sensor.is_failsafe_invalid_zero());
426        assert!(sensor.is_failsafe_invalid_max());
427
428        sensor.set_failsafe_invalid_zero(false);
429        assert!(!sensor.is_failsafe_invalid_zero());
430        assert!(sensor.is_failsafe_invalid_max());
431    }
432
433    #[test]
434    fn test_custom_sensor_unit() {
435        let mut unit = CustomSensorUnit::new();
436
437        assert!(unit.set_html_code("&deg;C").is_ok());
438        assert_eq!(unit.get_html_code(), "&deg;C");
439
440        assert!(unit.set_simple_text("°C").is_ok());
441        assert_eq!(unit.get_simple_text(), "°C");
442
443        // Test length limits
444        assert!(unit.set_html_code(&"x".repeat(33)).is_err());
445        assert!(unit.set_simple_text(&"x".repeat(9)).is_err());
446    }
447
448    #[test]
449    fn test_temperature_conversion() {
450        // Test DS18B20 temperature conversion
451        let raw_value = 400; // 25.0°C in DS18B20 format
452        let temperature = raw_value as f32 * 0.0625;
453        assert_eq!(temperature, 25.0);
454
455        let raw_value = -160; // -10.0°C in DS18B20 format
456        let temperature = raw_value as f32 * 0.0625;
457        assert_eq!(temperature, -10.0);
458    }
459
460    #[test]
461    fn test_voltage_conversion() {
462        // Test 12-bit ADC voltage conversion
463        let raw_value = 2048; // Half scale
464        let reference_voltage = 5.0;
465        let voltage = (raw_value as f32 / 4095.0) * reference_voltage;
466        assert!((voltage - 2.5).abs() < 0.01);
467
468        let raw_value = 4095; // Full scale
469        let voltage = (raw_value as f32 / 4095.0) * reference_voltage;
470        assert!((voltage - 5.0).abs() < 0.01);
471    }
472}