Skip to main content

hermes_five/io/
data.rs

1use std::collections::HashMap;
2use std::fmt::{Debug, Display, Formatter};
3
4use crate::errors::HardwareError::{IncompatiblePin, UnknownPin};
5use crate::errors::*;
6
7/// Represents the internal data that a [`IoProtocol`](crate::io::IoProtocol) handles.
8///
9/// This struct is hidden behind an `Arc<RwLock<IoData>>` to allow safe concurrent access
10/// and modification through the `IoData` type. It encapsulates data relevant
11/// to the protocol, such as pins and I2C communication data.
12#[derive(Clone, Debug, Default)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct IoData {
15    /// All `Pin` instances, representing the hardware's pins.
16    #[cfg_attr(feature = "serde", serde(skip))]
17    pub pins: HashMap<u8, Pin>,
18    /// A vector of `I2CReply` instances, representing I2C communication data.
19    #[cfg_attr(feature = "serde", serde(skip))]
20    pub i2c_data: Vec<I2CReply>,
21    /// List pins with digital reporting activated.
22    pub digital_reported_pins: Vec<u8>,
23    /// List pins with analog reporting activated.
24    pub analog_reported_channels: Vec<u8>,
25    /// A string indicating the version of the protocol.
26    pub protocol_version: String,
27    /// A string representing the name of the firmware.
28    pub firmware_name: String,
29    /// A string representing the version of the firmware.
30    pub firmware_version: String,
31    /// A boolean indicating whether the IoProtocol is connected.
32    pub connected: bool,
33}
34
35impl IoData {
36    /// Returns  a reference to a pin by its id or name.
37    ///
38    /// # Errors
39    /// * `UnknownPin` - An `Error` returned if the pin index is out of bounds.
40    pub fn get_pin<T: Into<PinIdOrName>>(&self, pin: T) -> Result<&Pin, Error> {
41        let pin = pin.into();
42        match &pin {
43            PinIdOrName::Id(id) => self.pins.get(id).ok_or(Error::from(UnknownPin { pin })),
44            PinIdOrName::Name(name) => Ok(self
45                .pins
46                .iter()
47                .find(|(_, pin)| pin.name == *name)
48                .ok_or(Error::from(UnknownPin { pin }))?
49                .1),
50        }
51    }
52
53    /// Returns  a mutable reference to a pin by its id or name.
54    ///
55    /// # Errors
56    /// * `UnknownPin` - An `Error` returned if the pin index is out of bounds.
57    pub fn get_pin_mut<T: Into<PinIdOrName>>(&mut self, pin: T) -> Result<&mut Pin, Error> {
58        let pin = pin.into();
59        match &pin {
60            PinIdOrName::Id(id) => self.pins.get_mut(id).ok_or(Error::from(UnknownPin { pin })),
61            PinIdOrName::Name(name) => Ok(self
62                .pins
63                .iter_mut()
64                .find(|(_, &mut ref pin)| pin.name == *name)
65                .ok_or(Error::from(UnknownPin { pin }))?
66                .1),
67        }
68    }
69}
70
71/// Defines an I2C reply.
72#[derive(Default, Debug, Clone)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
74pub struct I2CReply {
75    pub address: u8,
76    pub register: u8,
77    pub data: Vec<u8>,
78}
79
80/// Represents the current state and configuration of a pin.
81#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82#[derive(Clone, Default)]
83pub struct Pin {
84    /// The pin ID, which also corresponds to the index of the [`IoData::pins`] hashmap.
85    pub id: u8,
86    /// The pin name: an alternative String representation of the pin name: 'D13', 'A0', 'GPIO13' for instance.
87    pub name: String,
88    /// Currently configured mode.
89    pub mode: PinMode,
90    /// All pin supported modes.
91    pub supported_modes: Vec<PinMode>,
92    /// For analog pin, this is the channel number ie "A0"=>0, "A1"=>1, etc...
93    pub channel: Option<u8>,
94    /// Pin value.
95    pub value: u16,
96}
97
98impl Pin {
99    /// Verifies if a pin supports the given mode and returns it if it does.
100    pub fn supports_mode(&self, mode: PinModeId) -> Option<PinMode> {
101        self.supported_modes.iter().find(|m| m.id == mode).copied()
102    }
103
104    /// Validates that the pin is in the given mode.
105    pub fn validate_current_mode(&self, mode: PinModeId) -> Result<(), Error> {
106        match self.mode.id == mode {
107            true => Ok(()),
108            false => Err(IncompatiblePin {
109                mode: self.mode.id,
110                pin: self.id,
111                context: "check_current_mode",
112            }),
113        }?;
114        Ok(())
115    }
116
117    /// Get the max value this pin can reach.
118    ///
119    /// This is defined by the resolution of the current pin mode.
120    pub fn get_max_possible_value(&self) -> u16 {
121        self.mode.get_max_possible_value()
122    }
123}
124
125impl Debug for Pin {
126    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
127        // Transformer for "resolution"
128        let mode_str = format!("{}", self.mode);
129
130        let mut debug_struct = f.debug_struct("Pin");
131        debug_struct
132            .field("id", &self.id)
133            .field("name", &self.name)
134            .field("mode", &mode_str)
135            .field("supported modes", &self.supported_modes);
136        if let Some(channel) = self.channel {
137            debug_struct.field("channel", &channel);
138        } else {
139            debug_struct.field("channel", &None::<u8>);
140        }
141        debug_struct.field("value", &self.value).finish()
142    }
143}
144
145// ########################################
146
147/// Defines a structure to receive either an id or a name for a pin: 1, 'D1' or 'A1' for instance.
148#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
149#[derive(Clone, PartialEq, Debug)]
150pub enum PinIdOrName {
151    Id(u8),
152    Name(String),
153}
154
155impl From<u8> for PinIdOrName {
156    fn from(n: u8) -> Self {
157        PinIdOrName::Id(n)
158    }
159}
160
161impl From<&str> for PinIdOrName {
162    fn from(s: &str) -> Self {
163        PinIdOrName::Name(s.to_string())
164    }
165}
166
167impl From<String> for PinIdOrName {
168    fn from(s: String) -> Self {
169        PinIdOrName::Name(s)
170    }
171}
172
173impl Display for PinIdOrName {
174    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
175        match self {
176            PinIdOrName::Id(n) => write!(f, "{}", n),
177            PinIdOrName::Name(s) => write!(f, "{:?}", s),
178        }
179    }
180}
181
182// ########################################
183
184/// Represents a mode configuration for a pin.
185#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
186#[derive(Clone, Default, Copy)]
187pub struct PinMode {
188    /// Currently configured mode.
189    pub id: PinModeId,
190    /// Resolution (number of bits) this mode uses.
191    pub resolution: u8,
192}
193
194impl PinMode {
195    /// Get the max value this pinMode can reach according to its resolution.
196    pub fn get_max_possible_value(&self) -> u16 {
197        (1 << self.resolution) - 1
198    }
199}
200
201impl Display for PinMode {
202    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
203        write!(f, "{}", self.id)
204    }
205}
206
207impl Debug for PinMode {
208    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
209        match self.id {
210            PinModeId::UNSUPPORTED => write!(f, "[{}]", self.id),
211            _ => write!(f, "[id: {}, resolution: {}]", self.id, self.resolution),
212        }
213    }
214}
215
216// ########################################
217
218/// Enumerates the possible modes for a pin.
219#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
220#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
221#[repr(u8)]
222pub enum PinModeId {
223    /// Same as INPUT defined in Arduino.
224    INPUT = 0,
225    /// Same as OUTPUT defined in Arduino.h
226    OUTPUT = 1,
227    /// Analog pin in analogInput mode
228    ANALOG = 2,
229    /// Digital pin in PWM output mode
230    PWM = 3,
231    /// Digital pin in Servo output mode
232    SERVO = 4,
233    /// shiftIn/shiftOut mode
234    SHIFT = 5,
235    /// Pin included in I2C setup
236    I2C = 6,
237    /// Pin configured for 1-wire
238    ONEWIRE = 7,
239    /// Pin configured for stepper motor
240    STEPPER = 8,
241    /// Pin configured for rotary encoders
242    ENCODER = 9,
243    /// Pin configured for serial communication
244    SERIAL = 0x0A,
245    /// Enable internal pull-up resistor for pin
246    PULLUP = 0x0B,
247    /// Pin configured for SPI
248    SPI = 0x0C,
249    /// Pin configured for proximity sensors
250    SONAR = 0x0D,
251    /// Pin configured for piezzo buzzer tone generation
252    TONE = 0x0E,
253    /// Pin configured for DHT humidity and temperature sensors
254    DHT = 0x0F,
255    /// Pin configured to be ignored by digitalWrite and capabilityResponse
256    #[default]
257    UNSUPPORTED = 0x7F,
258}
259
260impl PinModeId {
261    /// Converts a `u8` byte value into a `PinModeId`.
262    ///
263    /// # Arguments
264    /// * `value`: The `u8` value representing the pin mode.
265    ///
266    /// # Errors
267    /// * `Unknown`: The value does not match any known pin mode.
268    ///
269    /// # Returns
270    /// The corresponding `PinModeId` if the value is valid, otherwise returns an error.
271    pub fn from_u8(value: u8) -> Result<PinModeId, Error> {
272        match value {
273            0 => Ok(PinModeId::INPUT),
274            1 => Ok(PinModeId::OUTPUT),
275            2 => Ok(PinModeId::ANALOG),
276            3 => Ok(PinModeId::PWM),
277            4 => Ok(PinModeId::SERVO),
278            5 => Ok(PinModeId::SHIFT),
279            6 => Ok(PinModeId::I2C),
280            7 => Ok(PinModeId::ONEWIRE),
281            8 => Ok(PinModeId::STEPPER),
282            9 => Ok(PinModeId::ENCODER),
283            0x0A => Ok(PinModeId::SERIAL),
284            0x0B => Ok(PinModeId::PULLUP),
285            0x0C => Ok(PinModeId::SPI),
286            0x0D => Ok(PinModeId::SONAR),
287            0x0E => Ok(PinModeId::TONE),
288            0x0F => Ok(PinModeId::DHT),
289            0x7F => Ok(PinModeId::UNSUPPORTED),
290            x => Err(UnknownError {
291                info: format!("PinMode not found with value: {}", x),
292            }),
293        }
294    }
295}
296
297impl From<PinModeId> for u8 {
298    fn from(mode: PinModeId) -> u8 {
299        mode as u8
300    }
301}
302
303impl Display for PinModeId {
304    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
305        write!(f, "{:?}", self)
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use crate::io::{Pin, PinIdOrName, PinMode, PinModeId};
312    use crate::mocks::create_test_plugin_io_data;
313
314    #[test]
315    fn test_get_pin_success() {
316        assert_eq!(create_test_plugin_io_data().get_pin(3).unwrap().value, 3);
317        assert_eq!(create_test_plugin_io_data().get_pin(11).unwrap().value, 11);
318        assert_eq!(
319            create_test_plugin_io_data().get_pin_mut(3).unwrap().value,
320            3
321        );
322        assert_eq!(
323            create_test_plugin_io_data().get_pin_mut(11).unwrap().value,
324            11
325        );
326    }
327
328    #[test]
329    fn test_get_pin_error() {
330        assert!(create_test_plugin_io_data().get_pin(66).is_err());
331        assert!(create_test_plugin_io_data().get_pin_mut(66).is_err());
332    }
333
334    #[test]
335    fn test_mutate_pin() {
336        let mut hardware = create_test_plugin_io_data();
337        assert_eq!(hardware.get_pin_mut(11).unwrap().value, 11);
338        hardware.get_pin_mut(11).unwrap().value = 255;
339        assert_eq!(hardware.get_pin_mut(11).unwrap().value, 255);
340    }
341
342    #[test]
343    fn test_pin_supports_mode() {
344        let pin = Pin {
345            supported_modes: vec![
346                PinMode {
347                    id: PinModeId::INPUT,
348                    resolution: 0,
349                },
350                PinMode {
351                    id: PinModeId::OUTPUT,
352                    resolution: 0,
353                },
354            ],
355            ..Default::default()
356        };
357
358        // Mode is supported
359        let supported_mode = pin.supports_mode(PinModeId::INPUT);
360        assert!(supported_mode.is_some());
361
362        // Mode is not supported
363        assert!(pin.supports_mode(PinModeId::PWM).is_none());
364    }
365
366    #[test]
367    fn test_pin_mode_max_value() {
368        let pin_mode = PinMode {
369            id: PinModeId::INPUT,
370            resolution: 8,
371        };
372
373        assert_eq!(pin_mode.get_max_possible_value(), 255);
374    }
375
376    #[test]
377    fn test_check_current_mode_success() {
378        let pin = Pin {
379            mode: PinMode {
380                id: PinModeId::PWM,
381                resolution: 10,
382            },
383            ..Default::default()
384        };
385
386        assert!(pin.validate_current_mode(PinModeId::PWM).is_ok());
387        assert!(pin.validate_current_mode(PinModeId::SHIFT).is_err());
388        assert_eq!(pin.get_max_possible_value(), 1023);
389    }
390
391    #[test]
392    fn test_pin_display() {
393        let mut pin = Pin {
394            supported_modes: vec![
395                PinMode {
396                    id: PinModeId::INPUT,
397                    resolution: 0,
398                },
399                PinMode {
400                    id: PinModeId::OUTPUT,
401                    resolution: 1,
402                },
403                PinMode {
404                    id: PinModeId::ANALOG,
405                    resolution: 8,
406                },
407            ],
408            channel: Some(1),
409            ..Default::default()
410        };
411        assert_eq!(format!("{:?}", pin), String::from("Pin { id: 0, name: \"\", mode: \"UNSUPPORTED\", supported modes: [[id: INPUT, resolution: 0], [id: OUTPUT, resolution: 1], [id: ANALOG, resolution: 8]], channel: 1, value: 0 }"));
412        pin.mode = PinMode {
413            id: PinModeId::INPUT,
414            resolution: 0,
415        };
416        pin.channel = None;
417        assert_eq!(format!("{:?}", pin), String::from("Pin { id: 0, name: \"\", mode: \"INPUT\", supported modes: [[id: INPUT, resolution: 0], [id: OUTPUT, resolution: 1], [id: ANALOG, resolution: 8]], channel: None, value: 0 }"));
418    }
419
420    #[test]
421    fn test_pin_mode_display() {
422        let mode = PinMode {
423            id: PinModeId::PWM,
424            resolution: 8,
425        };
426        assert_eq!(format!("{}", mode), "PWM");
427    }
428
429    #[test]
430    fn test_pin_mode_debug() {
431        let mode = PinMode {
432            id: PinModeId::PWM,
433            resolution: 8,
434        };
435        assert_eq!(format!("{:?}", mode), "[id: PWM, resolution: 8]");
436        let unsupported = PinMode {
437            id: PinModeId::UNSUPPORTED,
438            resolution: 0,
439        };
440        assert_eq!(format!("{:?}", unsupported), "[UNSUPPORTED]");
441    }
442
443    #[test]
444    fn test_pin_mode_id_conversions() {
445        // From u8 to PinModeId: success
446        let mode = PinModeId::from_u8(0x0F);
447        assert!(mode.is_ok());
448        assert_eq!(PinModeId::from_u8(0).unwrap(), PinModeId::INPUT);
449        assert_eq!(PinModeId::from_u8(1).unwrap(), PinModeId::OUTPUT);
450        assert_eq!(PinModeId::from_u8(2).unwrap(), PinModeId::ANALOG);
451        assert_eq!(PinModeId::from_u8(3).unwrap(), PinModeId::PWM);
452        assert_eq!(PinModeId::from_u8(4).unwrap(), PinModeId::SERVO);
453        assert_eq!(PinModeId::from_u8(5).unwrap(), PinModeId::SHIFT);
454        assert_eq!(PinModeId::from_u8(6).unwrap(), PinModeId::I2C);
455        assert_eq!(PinModeId::from_u8(7).unwrap(), PinModeId::ONEWIRE);
456        assert_eq!(PinModeId::from_u8(8).unwrap(), PinModeId::STEPPER);
457        assert_eq!(PinModeId::from_u8(9).unwrap(), PinModeId::ENCODER);
458        assert_eq!(PinModeId::from_u8(0x0A).unwrap(), PinModeId::SERIAL);
459        assert_eq!(PinModeId::from_u8(0x0B).unwrap(), PinModeId::PULLUP);
460        assert_eq!(PinModeId::from_u8(0x0C).unwrap(), PinModeId::SPI);
461        assert_eq!(PinModeId::from_u8(0x0D).unwrap(), PinModeId::SONAR);
462        assert_eq!(PinModeId::from_u8(0x0E).unwrap(), PinModeId::TONE);
463        assert_eq!(PinModeId::from_u8(0x0F).unwrap(), PinModeId::DHT);
464        assert_eq!(PinModeId::from_u8(0x7F).unwrap(), PinModeId::UNSUPPORTED);
465
466        // From u8 to PinModeId: error
467        let error_mode = PinModeId::from_u8(100);
468        assert!(error_mode.is_err());
469        assert_eq!(
470            error_mode.err().unwrap().to_string(),
471            "Unknown error: PinMode not found with value: 100."
472        );
473
474        // From PinModeId to u8
475        assert_eq!(u8::from(PinModeId::SHIFT), 5);
476    }
477
478    #[test]
479    fn test_pin_mode_id_display() {
480        assert_eq!(format!("{}", PinModeId::PWM), "PWM");
481    }
482
483    #[test]
484    fn test_pin_id_from() {
485        let pin = PinIdOrName::from(42);
486        assert_eq!(pin, PinIdOrName::Id(42));
487        let pin: PinIdOrName = 4.into();
488        assert_eq!(pin, PinIdOrName::Id(4));
489        let pin = PinIdOrName::from("D1");
490        assert_eq!(pin, PinIdOrName::Name("D1".to_string()));
491        let pin = PinIdOrName::from("A1".to_string());
492        assert_eq!(pin, PinIdOrName::Name("A1".to_string()));
493    }
494
495    #[test]
496    fn test_pin_id_display() {
497        let pin = PinIdOrName::Id(42);
498        assert_eq!(pin.to_string(), "42");
499        let pin = PinIdOrName::Name(String::from("A0"));
500        assert_eq!(pin.to_string(), "\"A0\"");
501    }
502}