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}