Skip to main content

pamoja_gpio/
switch.rs

1//! The parts that are one line: a switch driven, and a contact read, through a pin.
2//!
3//! A relay, a solenoid valve, a pump through a transistor, an indicator lamp: each is
4//! an output line that is either asserted or not, and whether "asserted" is the high
5//! or the low level depends on the board. A button, a float switch, a reed contact, a
6//! motion detector: each is an input line read the same way. [`Switch`] and
7//! [`Contact`] carry the [`Polarity`] so a caller says `on` and `off` and never
8//! inverts by hand, and they implement the core [`Actuator`] and [`Sensor`] traits so
9//! a valve takes a profile's `bool` and a float switch feeds a rule directly.
10
11use embedded_hal::digital::{InputPin, OutputPin, PinState};
12use pamoja_core::{Actuator, Error, Result, Sensor};
13
14use crate::pin::{Level, Polarity};
15
16fn state(level: Level) -> PinState {
17    if level.is_high() {
18        PinState::High
19    } else {
20        PinState::Low
21    }
22}
23
24fn level(high: bool) -> Level {
25    Level::from_bool(high)
26}
27
28/// An on/off part on an output line: a relay, a valve, a lamp.
29///
30/// # Examples
31///
32/// ```
33/// use pamoja_core::Actuator;
34/// use pamoja_gpio::pin::Polarity;
35/// use pamoja_gpio::switch::Switch;
36/// use pamoja_hal::digital::PinState;
37/// use pamoja_hal::script::{block_on, PinScript};
38///
39/// // A relay board whose inputs are pulled low to energize the coil.
40/// let mut valve = Switch::new(PinScript::new([]), Polarity::ActiveLow);
41/// block_on(valve.apply(true))?;
42/// block_on(valve.apply(false))?;
43/// assert_eq!(valve.release().driven(), [PinState::Low, PinState::High]);
44/// # Ok::<(), pamoja_core::Error>(())
45/// ```
46#[derive(Debug)]
47pub struct Switch<P> {
48    pin: P,
49    polarity: Polarity,
50    asserted: bool,
51}
52
53impl<P> Switch<P> {
54    /// Wraps an output line, without driving it.
55    ///
56    /// # Arguments
57    ///
58    /// * `pin` - the output line.
59    /// * `polarity` - which level asserts the part.
60    ///
61    /// # Returns
62    ///
63    /// The switch, recorded as not asserted until it is first driven.
64    pub fn new(pin: P, polarity: Polarity) -> Switch<P> {
65        Switch {
66            pin,
67            polarity,
68            asserted: false,
69        }
70    }
71
72    /// Wraps a line that asserts high.
73    ///
74    /// # Arguments
75    ///
76    /// * `pin` - the output line.
77    ///
78    /// # Returns
79    ///
80    /// The switch.
81    pub fn active_high(pin: P) -> Switch<P> {
82        Switch::new(pin, Polarity::ActiveHigh)
83    }
84
85    /// Wraps a line that asserts low, the shape of most relay boards.
86    ///
87    /// # Arguments
88    ///
89    /// * `pin` - the output line.
90    ///
91    /// # Returns
92    ///
93    /// The switch.
94    pub fn active_low(pin: P) -> Switch<P> {
95        Switch::new(pin, Polarity::ActiveLow)
96    }
97
98    /// Returns the polarity the switch drives with.
99    pub fn polarity(&self) -> Polarity {
100        self.polarity
101    }
102
103    /// Reports whether the part was last driven asserted.
104    ///
105    /// # Returns
106    ///
107    /// `true` after a successful `set(true)`, `false` after `set(false)` or before
108    /// the line is first driven.
109    pub fn is_asserted(&self) -> bool {
110        self.asserted
111    }
112
113    /// Gives back the line.
114    ///
115    /// # Returns
116    ///
117    /// The pin the switch was built from.
118    pub fn release(self) -> P {
119        self.pin
120    }
121}
122
123impl<P: OutputPin> Switch<P> {
124    /// Drives the part asserted or not, at the level its polarity maps to.
125    ///
126    /// # Arguments
127    ///
128    /// * `asserted` - `true` to energize, `false` to release.
129    ///
130    /// # Errors
131    ///
132    /// Returns the pin's error if the line cannot be driven; the recorded state is
133    /// left unchanged.
134    pub fn set(&mut self, asserted: bool) -> core::result::Result<(), P::Error> {
135        self.pin.set_state(state(self.polarity.level(asserted)))?;
136        self.asserted = asserted;
137        Ok(())
138    }
139}
140
141impl<P> Actuator for Switch<P>
142where
143    P: OutputPin + Send,
144    P::Error: core::fmt::Debug,
145{
146    type Command = bool;
147
148    async fn apply(&mut self, asserted: bool) -> Result<()> {
149        self.set(asserted)
150            .map_err(|error| Error::Io(alloc::format!("pin error: {error:?}")))
151    }
152}
153
154/// An on/off part on an input line: a button, a float switch, a motion detector.
155///
156/// # Examples
157///
158/// ```
159/// use pamoja_core::Sensor;
160/// use pamoja_gpio::pin::Polarity;
161/// use pamoja_gpio::switch::Contact;
162/// use pamoja_hal::digital::PinState;
163/// use pamoja_hal::script::{block_on, PinScript};
164///
165/// // A float switch pulled up, which closes to ground when the tank is full.
166/// let line = PinScript::new([PinState::High, PinState::Low]);
167/// let mut full = Contact::new(line, Polarity::ActiveLow);
168/// assert!(!block_on(full.read())?);
169/// assert!(block_on(full.read())?);
170/// # Ok::<(), pamoja_core::Error>(())
171/// ```
172#[derive(Debug)]
173pub struct Contact<P> {
174    pin: P,
175    polarity: Polarity,
176}
177
178impl<P> Contact<P> {
179    /// Wraps an input line.
180    ///
181    /// # Arguments
182    ///
183    /// * `pin` - the input line.
184    /// * `polarity` - which level means the contact is asserted.
185    ///
186    /// # Returns
187    ///
188    /// The contact.
189    pub fn new(pin: P, polarity: Polarity) -> Contact<P> {
190        Contact { pin, polarity }
191    }
192
193    /// Wraps a line that reads high when asserted.
194    ///
195    /// # Arguments
196    ///
197    /// * `pin` - the input line.
198    ///
199    /// # Returns
200    ///
201    /// The contact.
202    pub fn active_high(pin: P) -> Contact<P> {
203        Contact::new(pin, Polarity::ActiveHigh)
204    }
205
206    /// Wraps a line pulled up that a closing contact takes low, the common wiring for
207    /// a button or a float switch.
208    ///
209    /// # Arguments
210    ///
211    /// * `pin` - the input line.
212    ///
213    /// # Returns
214    ///
215    /// The contact.
216    pub fn active_low(pin: P) -> Contact<P> {
217        Contact::new(pin, Polarity::ActiveLow)
218    }
219
220    /// Returns the polarity the contact reads with.
221    pub fn polarity(&self) -> Polarity {
222        self.polarity
223    }
224
225    /// Gives back the line.
226    ///
227    /// # Returns
228    ///
229    /// The pin the contact was built from.
230    pub fn release(self) -> P {
231        self.pin
232    }
233}
234
235impl<P: InputPin> Contact<P> {
236    /// Reads the physical level of the line.
237    ///
238    /// # Returns
239    ///
240    /// The level, before the polarity is applied.
241    ///
242    /// # Errors
243    ///
244    /// Returns the pin's error if the line cannot be read.
245    pub fn level(&mut self) -> core::result::Result<Level, P::Error> {
246        self.pin.is_high().map(level)
247    }
248
249    /// Reads whether the contact is asserted.
250    ///
251    /// # Returns
252    ///
253    /// `true` when the line sits at the polarity's asserted level.
254    ///
255    /// # Errors
256    ///
257    /// Returns the pin's error if the line cannot be read.
258    pub fn is_asserted(&mut self) -> core::result::Result<bool, P::Error> {
259        self.level().map(|level| self.polarity.is_asserted(level))
260    }
261}
262
263impl<P> Sensor for Contact<P>
264where
265    P: InputPin + Send,
266    P::Error: core::fmt::Debug,
267{
268    type Reading = bool;
269
270    async fn read(&mut self) -> Result<bool> {
271        self.is_asserted()
272            .map_err(|error| Error::Io(alloc::format!("pin error: {error:?}")))
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279    use pamoja_hal::script::{block_on, PinScript};
280
281    #[test]
282    fn a_switch_drives_the_level_its_polarity_maps_to_and_remembers_it() {
283        let mut lamp = Switch::active_high(PinScript::new([]));
284        assert!(!lamp.is_asserted());
285        lamp.set(true).unwrap();
286        assert!(lamp.is_asserted());
287        lamp.set(false).unwrap();
288        assert_eq!(lamp.release().driven(), [PinState::High, PinState::Low]);
289
290        let mut relay = Switch::active_low(PinScript::new([]));
291        relay.set(true).unwrap();
292        assert_eq!(relay.polarity(), Polarity::ActiveLow);
293        assert_eq!(relay.release().driven(), [PinState::Low]);
294    }
295
296    #[test]
297    fn a_switch_is_an_actuator_taking_a_bool() {
298        let mut valve = Switch::new(PinScript::new([]), Polarity::ActiveLow);
299        block_on(valve.apply(true)).unwrap();
300        assert!(valve.is_asserted());
301        block_on(valve.apply(false)).unwrap();
302        assert_eq!(valve.release().driven(), [PinState::Low, PinState::High]);
303    }
304
305    #[test]
306    fn a_contact_applies_its_polarity_to_what_it_reads() {
307        let line = PinScript::new([PinState::High, PinState::Low, PinState::Low]);
308        let mut button = Contact::active_low(line);
309        assert!(!button.is_asserted().unwrap());
310        assert!(button.is_asserted().unwrap());
311        assert_eq!(button.level().unwrap(), Level::Low);
312        assert_eq!(button.polarity(), Polarity::ActiveLow);
313
314        let mut motion = Contact::active_high(PinScript::new([PinState::High]));
315        assert!(block_on(motion.read()).unwrap());
316        assert!(motion.release().driven().is_empty());
317    }
318}