Skip to main content

pamoja_core/
device.rs

1//! Device-model traits implemented by capability crates.
2//!
3//! These traits describe the roles a piece of hardware can play in an
4//! application: a connectable [`Device`], a [`Sensor`] that produces readings, an
5//! [`Actuator`] that accepts commands, and a [`Telemetry`] source that streams
6//! frames. A single type may implement more than one of them.
7//!
8//! The futures these traits return are `Send`, as a [`Transport`](crate::Transport)'s
9//! are, so a node built from any sensor and actuator can be driven from a task on a
10//! multi-threaded runtime and erased behind a trait object that is. An implementation
11//! written as `async fn` satisfies this as long as everything it holds across an await
12//! is `Send`; a driver generic over a bus says so with a `Send` bound on the bus.
13
14use core::future::Future;
15
16use crate::adapt::{Map, MapCommand};
17use crate::error::Result;
18
19/// A connectable physical or virtual device.
20///
21/// Implementors manage the lifecycle of an underlying resource such as a serial
22/// port, a network socket, or a vehicle link.
23pub trait Device {
24    /// Returns the stable identifier for this device.
25    ///
26    /// The identifier is expected to remain constant for the lifetime of the
27    /// device, for example a serial number, MAC address, or vehicle URI.
28    ///
29    /// # Returns
30    ///
31    /// A string slice borrowing the device's identifier.
32    fn id(&self) -> &str;
33
34    /// Opens the device and prepares it for use.
35    ///
36    /// # Returns
37    ///
38    /// `Ok(())` once the device is connected and ready.
39    ///
40    /// # Errors
41    ///
42    /// Returns [`Error::Io`](crate::Error::Io) if the underlying resource cannot
43    /// be opened, or [`Error::Transport`](crate::Error::Transport) if a link to
44    /// the device cannot be established.
45    fn connect(&mut self) -> impl Future<Output = Result<()>> + Send;
46
47    /// Releases the device and any resources it holds.
48    ///
49    /// # Returns
50    ///
51    /// `Ok(())` once the device has been disconnected and its resources freed.
52    ///
53    /// # Errors
54    ///
55    /// Returns [`Error::Io`](crate::Error::Io) if the underlying resource cannot
56    /// be released cleanly.
57    fn disconnect(&mut self) -> impl Future<Output = Result<()>> + Send;
58}
59
60/// A source of typed readings, such as a thermometer, GPS receiver, or lidar.
61pub trait Sensor {
62    /// The value produced by a single read, for example a temperature or a fix.
63    type Reading;
64
65    /// Takes a single reading from the sensor.
66    ///
67    /// # Returns
68    ///
69    /// The next [`Reading`](Self::Reading) sampled from the sensor.
70    ///
71    /// # Errors
72    ///
73    /// Returns [`Error::Io`](crate::Error::Io) if the sensor cannot be read, or
74    /// [`Error::Closed`](crate::Error::Closed) if the sensor has been
75    /// disconnected.
76    fn read(&mut self) -> impl Future<Output = Result<Self::Reading>> + Send;
77
78    /// Wraps this sensor so every reading passes through `select` first.
79    ///
80    /// This is how a driver that measures several channels feeds a consumer that
81    /// wants one number: `select` picks the channel, converts the unit, or combines
82    /// fields, and the result is a [`Sensor`] whose reading is whatever `select`
83    /// returns. See [`Map`] for an example.
84    ///
85    /// # Arguments
86    ///
87    /// * `select` - the function applied to each reading as it is taken.
88    ///
89    /// # Returns
90    ///
91    /// The adapted sensor.
92    fn map<F, T>(self, select: F) -> Map<Self, F>
93    where
94        Self: Sized,
95        F: FnMut(Self::Reading) -> T,
96    {
97        Map::new(self, select)
98    }
99}
100
101/// A sink that accepts typed commands, such as a motor or a valve.
102pub trait Actuator {
103    /// The command accepted by a single application, for example a setpoint.
104    type Command;
105
106    /// Applies a command to the actuator.
107    ///
108    /// # Arguments
109    ///
110    /// * `command` - the command to apply, consumed by the call.
111    ///
112    /// # Returns
113    ///
114    /// `Ok(())` once the command has been accepted by the actuator.
115    ///
116    /// # Errors
117    ///
118    /// Returns [`Error::Io`](crate::Error::Io) if the command cannot be
119    /// delivered, or [`Error::Closed`](crate::Error::Closed) if the actuator has
120    /// been disconnected.
121    fn apply(&mut self, command: Self::Command) -> impl Future<Output = Result<()>> + Send;
122
123    /// Wraps this actuator so every command passes through `convert` first.
124    ///
125    /// This is how a part that takes its own command shape is driven by a consumer
126    /// that has a simpler one: a profile's `bool` becomes a pulse width, a percentage
127    /// becomes a duty. The result is an [`Actuator`] whose command is whatever
128    /// `convert` accepts. See [`MapCommand`] for an example.
129    ///
130    /// # Arguments
131    ///
132    /// * `convert` - the function turning the caller's command into this part's.
133    ///
134    /// # Returns
135    ///
136    /// The adapted actuator.
137    fn map_command<F, C>(self, convert: F) -> MapCommand<Self, F, C>
138    where
139        Self: Sized,
140        F: FnMut(C) -> Self::Command,
141    {
142        MapCommand::new(self, convert)
143    }
144}
145
146/// A device that emits a continuous stream of telemetry frames.
147pub trait Telemetry {
148    /// A single telemetry frame, for example a status or position report.
149    type Frame;
150
151    /// Awaits the next telemetry frame.
152    ///
153    /// # Returns
154    ///
155    /// `Some(frame)` when a frame is available, or `None` once the telemetry
156    /// stream has ended.
157    ///
158    /// # Errors
159    ///
160    /// Returns [`Error::Transport`](crate::Error::Transport) if the telemetry
161    /// link fails while waiting.
162    fn next_frame(&mut self) -> impl Future<Output = Result<Option<Self::Frame>>> + Send;
163}