pamoja_core/adapt.rs
1//! Adapters that reshape what a sensor reads and what an actuator is told.
2//!
3//! A driver's reading is whatever the part measures, often several channels at once,
4//! and a driver's command is whatever the part accepts. The pieces that consume a
5//! sensor or drive an actuator usually want one plain value: a controller takes a
6//! single `f32`, a profile drives a `bool`. [`Map`] and [`MapCommand`] bridge the two
7//! without a wrapper type per part: [`Sensor::map`] selects or converts each reading
8//! as it is taken, and [`Actuator::map_command`] converts each command before it
9//! reaches the part.
10
11use core::marker::PhantomData;
12
13use crate::device::{Actuator, Sensor};
14use crate::error::Result;
15
16/// A sensor whose readings pass through a function before they are returned.
17///
18/// Created by [`Sensor::map`]. The function runs on every read, so it can select one
19/// channel of a multi-channel measurement, convert a unit, or fold several fields into
20/// one value.
21///
22/// # Examples
23///
24/// ```
25/// use pamoja_core::{Result, Sensor};
26///
27/// struct Climate;
28/// impl Sensor for Climate {
29/// type Reading = (f32, f32);
30/// async fn read(&mut self) -> Result<(f32, f32)> {
31/// Ok((21.5, 48.0))
32/// }
33/// }
34///
35/// # async fn run() -> Result<()> {
36/// let mut humidity = Climate.map(|(_, humidity)| humidity);
37/// assert_eq!(humidity.read().await?, 48.0);
38/// # Ok(())
39/// # }
40/// ```
41pub struct Map<S, F> {
42 inner: S,
43 select: F,
44}
45
46impl<S, F> Map<S, F> {
47 /// Wraps `inner` so each reading passes through `select`.
48 ///
49 /// # Arguments
50 ///
51 /// * `inner` - the sensor being adapted.
52 /// * `select` - the function applied to each reading.
53 ///
54 /// # Returns
55 ///
56 /// The adapted sensor.
57 pub fn new(inner: S, select: F) -> Map<S, F> {
58 Map { inner, select }
59 }
60
61 /// Borrows the wrapped sensor.
62 ///
63 /// # Returns
64 ///
65 /// The sensor underneath the adapter.
66 pub fn inner(&self) -> &S {
67 &self.inner
68 }
69
70 /// Mutably borrows the wrapped sensor.
71 ///
72 /// # Returns
73 ///
74 /// The sensor underneath the adapter.
75 pub fn inner_mut(&mut self) -> &mut S {
76 &mut self.inner
77 }
78
79 /// Unwraps the sensor, discarding the adapter.
80 ///
81 /// # Returns
82 ///
83 /// The sensor underneath the adapter.
84 pub fn into_inner(self) -> S {
85 self.inner
86 }
87}
88
89impl<S, F, T> Sensor for Map<S, F>
90where
91 S: Sensor + Send,
92 F: FnMut(S::Reading) -> T + Send,
93{
94 type Reading = T;
95
96 async fn read(&mut self) -> Result<T> {
97 let reading = self.inner.read().await?;
98 Ok((self.select)(reading))
99 }
100}
101
102/// An actuator whose commands are converted before they reach the part.
103///
104/// Created by [`Actuator::map_command`]. The function runs on every command, so a part
105/// that takes a pulse width or a duty can be driven by a `bool`, an angle, or a
106/// percentage, whichever the caller has.
107///
108/// # Examples
109///
110/// ```
111/// use pamoja_core::{Actuator, Result};
112///
113/// struct Pump {
114/// duty: u8,
115/// }
116/// impl Actuator for Pump {
117/// type Command = u8;
118/// async fn apply(&mut self, duty: u8) -> Result<()> {
119/// self.duty = duty;
120/// Ok(())
121/// }
122/// }
123///
124/// # async fn run() -> Result<()> {
125/// // A profile drives the pump as on or off; on means full speed.
126/// let mut switch = Pump { duty: 0 }.map_command(|on: bool| if on { 100 } else { 0 });
127/// switch.apply(true).await?;
128/// assert_eq!(switch.inner().duty, 100);
129/// # Ok(())
130/// # }
131/// ```
132pub struct MapCommand<A, F, C> {
133 inner: A,
134 convert: F,
135 command: PhantomData<fn(C)>,
136}
137
138impl<A, F, C> MapCommand<A, F, C> {
139 /// Wraps `inner` so each command passes through `convert` first.
140 ///
141 /// # Arguments
142 ///
143 /// * `inner` - the actuator being adapted.
144 /// * `convert` - the function turning the caller's command into the part's.
145 ///
146 /// # Returns
147 ///
148 /// The adapted actuator.
149 pub fn new(inner: A, convert: F) -> MapCommand<A, F, C> {
150 MapCommand {
151 inner,
152 convert,
153 command: PhantomData,
154 }
155 }
156
157 /// Borrows the wrapped actuator.
158 ///
159 /// # Returns
160 ///
161 /// The actuator underneath the adapter.
162 pub fn inner(&self) -> &A {
163 &self.inner
164 }
165
166 /// Mutably borrows the wrapped actuator.
167 ///
168 /// # Returns
169 ///
170 /// The actuator underneath the adapter.
171 pub fn inner_mut(&mut self) -> &mut A {
172 &mut self.inner
173 }
174
175 /// Unwraps the actuator, discarding the adapter.
176 ///
177 /// # Returns
178 ///
179 /// The actuator underneath the adapter.
180 pub fn into_inner(self) -> A {
181 self.inner
182 }
183}
184
185impl<A, F, C> Actuator for MapCommand<A, F, C>
186where
187 A: Actuator + Send,
188 F: FnMut(C) -> A::Command + Send,
189 C: Send,
190{
191 type Command = C;
192
193 async fn apply(&mut self, command: C) -> Result<()> {
194 let converted = (self.convert)(command);
195 self.inner.apply(converted).await
196 }
197}