drmem_api/driver/
mod.rs

1//! Defines types and interfaces that drivers use to interact with the
2//! core of DrMem.
3
4use crate::types::{device, Error};
5use std::future::Future;
6use std::{convert::Infallible, pin::Pin, sync::Arc};
7use tokio::sync::{mpsc, oneshot, Mutex};
8use toml::value;
9
10use super::Result;
11
12/// Represents the type used to specify the name of a driver.
13pub type Name = Arc<str>;
14
15/// Represents how configuration information is given to a driver.
16/// Since each driver can have vastly different requirements, the
17/// config structure needs to be as general as possible. A
18/// `DriverConfig` type is a map with `String` keys and `toml::Value`
19/// values.
20pub type DriverConfig = value::Table;
21
22mod ro_device;
23mod rw_device;
24
25pub use ro_device::{ReadOnlyDevice, ReportReading};
26pub use rw_device::{
27    ReadWriteDevice, RxDeviceSetting, SettingReply, SettingRequest,
28    TxDeviceSetting,
29};
30
31/// Defines the requests that can be sent to core. Drivers don't use
32/// this type directly. They are indirectly used by `RequestChan`.
33pub enum Request {
34    /// Registers a read-only device with core.
35    ///
36    /// The reply is a pair where the first element is a channel to
37    /// report updated values of the device. The second element, if
38    /// not `None`, is the last saved value of the device.
39    AddReadonlyDevice {
40        driver_name: Name,
41        dev_name: device::Name,
42        dev_units: Option<String>,
43        max_history: Option<usize>,
44        rpy_chan: oneshot::Sender<Result<ReportReading>>,
45    },
46
47    /// Registers a writable device with core.
48    ///
49    /// The reply is a 3-tuple where the first element is a channel to
50    /// report updated values of the device. The second element is a
51    /// stream that yileds incoming settings to the device. The last
52    /// element, if not `None`, is the last saved value of the device.
53    AddReadWriteDevice {
54        driver_name: Name,
55        dev_name: device::Name,
56        dev_units: Option<String>,
57        max_history: Option<usize>,
58        rpy_chan: oneshot::Sender<
59            Result<(ReportReading, RxDeviceSetting, Option<device::Value>)>,
60        >,
61    },
62}
63
64/// A handle which is used to communicate with the core of DrMem.
65/// When a driver is created, it will be given a handle to be used
66/// throughout its life.
67///
68/// This type wraps the `mpsc::Sender<>` and defines a set of helper
69/// methods to send requests and receive replies with the core.
70#[derive(Clone)]
71pub struct RequestChan {
72    driver_name: Name,
73    prefix: device::Path,
74    req_chan: mpsc::Sender<Request>,
75}
76
77impl RequestChan {
78    pub fn new(
79        driver_name: Name,
80        prefix: &device::Path,
81        req_chan: &mpsc::Sender<Request>,
82    ) -> Self {
83        RequestChan {
84            driver_name,
85            prefix: prefix.clone(),
86            req_chan: req_chan.clone(),
87        }
88    }
89
90    /// Registers a read-only device with the framework. `name` is the
91    /// last section of the full device name. Typically a driver will
92    /// register several devices, each representing a portion of the
93    /// hardware being controlled. All devices for a given driver
94    /// instance will have the same prefix; the `name` parameter is
95    /// appended to it.
96    ///
97    /// If it returns `Ok()`, the value is a broadcast channel that
98    /// the driver uses to announce new values of the associated
99    /// hardware.
100    ///
101    /// If it returns `Err()`, the underlying value could be `InUse`,
102    /// meaning the device name is already registered. If the error is
103    /// `InternalError`, then the core has exited and the
104    /// `RequestChan` has been closed. Since the driver can't report
105    /// any more updates, it may as well shutdown.
106    pub async fn add_ro_device<
107        T: Into<device::Value> + TryFrom<device::Value> + Clone,
108    >(
109        &self,
110        name: device::Base,
111        units: Option<&str>,
112        max_history: Option<usize>,
113    ) -> super::Result<ReadOnlyDevice<T>> {
114        // Create a location for the reply.
115
116        let (tx, rx) = oneshot::channel();
117
118        // Send a request to Core to register the given name.
119
120        let result = self
121            .req_chan
122            .send(Request::AddReadonlyDevice {
123                driver_name: self.driver_name.clone(),
124                dev_name: device::Name::build(self.prefix.clone(), name),
125                dev_units: units.map(String::from),
126                max_history,
127                rpy_chan: tx,
128            })
129            .await;
130
131        // If the request was sent successfully and we successfully
132        // received a reply, process the payload.
133
134        if result.is_ok() {
135            if let Ok(v) = rx.await {
136                return v.map(ReadOnlyDevice::new);
137            }
138        }
139
140        Err(Error::MissingPeer(String::from(
141            "can't communicate with core",
142        )))
143    }
144
145    /// Registers a read-write device with the framework. `name` is the
146    /// last section of the full device name. Typically a driver will
147    /// register several devices, each representing a portion of the
148    /// hardware being controlled. All devices for a given driver
149    /// instance will have the same prefix; the `name` parameter is
150    /// appended to it.
151    ///
152    /// If it returns `Ok()`, the value is a pair containing a
153    /// broadcast channel that the driver uses to announce new values
154    /// of the associated hardware and a receive channel for incoming
155    /// settings to be applied to the hardware.
156    ///
157    /// If it returns `Err()`, the underlying value could be `InUse`,
158    /// meaning the device name is already registered. If the error is
159    /// `InternalError`, then the core has exited and the
160    /// `RequestChan` has been closed. Since the driver can't report
161    /// any more updates or accept new settings, it may as well shutdown.
162    pub async fn add_rw_device<T>(
163        &self,
164        name: device::Base,
165        units: Option<&str>,
166        max_history: Option<usize>,
167    ) -> Result<ReadWriteDevice<T>>
168    where
169        T: Into<device::Value> + TryFrom<device::Value> + Clone,
170    {
171        let (tx, rx) = oneshot::channel();
172        let result = self
173            .req_chan
174            .send(Request::AddReadWriteDevice {
175                driver_name: self.driver_name.clone(),
176                dev_name: device::Name::build(self.prefix.clone(), name),
177                dev_units: units.map(String::from),
178                max_history,
179                rpy_chan: tx,
180            })
181            .await;
182
183        if result.is_ok() {
184            if let Ok(v) = rx.await {
185                return v.map(|(rr, rs, prev)| {
186                    ReadWriteDevice::new(
187                        rr,
188                        rs,
189                        prev.and_then(|v| T::try_from(v).ok()),
190                    )
191                });
192            }
193        }
194
195        Err(Error::MissingPeer(String::from(
196            "can't communicate with core",
197        )))
198    }
199}
200
201/// Defines a boxed type that supports the `driver::API` trait.
202pub type DriverType<T> = Box<dyn API<DeviceSet = <T as API>::DeviceSet>>;
203
204/// All drivers implement the `driver::API` trait.
205///
206/// The `API` trait defines methods that are expected to be available
207/// from a driver instance. By supporting this API, the framework can
208/// create driver instances and monitor them as they run.
209pub trait API: Send {
210    type DeviceSet: Send + Sync;
211
212    fn register_devices(
213        drc: RequestChan,
214        cfg: &DriverConfig,
215        max_history: Option<usize>,
216    ) -> Pin<Box<dyn Future<Output = Result<Self::DeviceSet>> + Send>>;
217
218    /// Creates an instance of the driver.
219    ///
220    /// `cfg` contains the driver parameters, as specified in the
221    /// `drmem.toml` configuration file. It is a `toml::Table` type so
222    /// the keys for the parameter names are strings and the
223    /// associated data are `toml::Value` types. This method should
224    /// validate the parameters and convert them into forms useful to
225    /// the driver. By convention, if any errors are found in the
226    /// configuration, this method should return `Error::BadConfig`.
227    ///
228    /// `drc` is a communication channel with which the driver makes
229    /// requests to the core. Its typical use is to register devices
230    /// with the framework, which is usually done in this method. As
231    /// other request types are added, they can be used while the
232    /// driver is running.
233    ///
234    /// `max_history` is specified in the configuration file. It is a
235    /// hint as to the maximum number of data point to save for each
236    /// of the devices created by this driver. A backend can choose to
237    /// interpret this in its own way. For instance, the simple
238    /// backend can only ever save one data point. Redis will take
239    /// this as a hint and will choose the most efficient way to prune
240    /// the history. That means, if more than the limit is present,
241    /// redis won't prune the history to less than the limit. However
242    /// there may be more than the limit -- it just won't grow without
243    /// bound.
244    fn create_instance(
245        cfg: &DriverConfig,
246    ) -> Pin<Box<dyn Future<Output = Result<Box<Self>>> + Send>>
247    where
248        Self: Sized;
249
250    /// Runs the instance of the driver.
251    ///
252    /// Since drivers provide access to hardware, this method should
253    /// never return unless something severe occurs and, in that case,
254    /// it should use `panic!()`. All drivers are monitored by a task
255    /// and if a driver panics or returns an error from this method,
256    /// it gets reported in the log and then, after a short delay, the
257    /// driver is restarted.
258    fn run<'a>(
259        &'a mut self,
260        devices: Arc<Mutex<Self::DeviceSet>>,
261    ) -> Pin<Box<dyn Future<Output = Infallible> + Send + 'a>>;
262}