drmem_api/
client.rs

1//! Defines types and interfaces that internal clients use to interact
2//! with the core of DrMem. The primary, internal client is the
3//! GraphQL interface, but asks in logic blocks also use this module.
4//!
5//! Any new, internal tasks that need access to device readings or
6//! wish to set the value of the device need to have a
7//! `client::RequestChan` handle. As DrMem starts, it should
8//! `.clone()` the `RequestChan` used to communicate with the
9//! back-end.
10//!
11//! # Example
12//!
13//! ```ignore
14//! async fn some_new_task(handle: client::RequestChan) {
15//!    // Initialize and enter loop.
16//!
17//!    let device = "some:device".parse::<device::Name>().unwrap();
18//!
19//!    loop {
20//!        // Set a device value.
21//!
22//!        if some_condition {
23//!            handle.set_device(&device, true.into())
24//!        }
25//!    }
26//! }
27//!
28//! // Somewhere in DrMem start-up.
29//!
30//! let task = some_new_task(backend_chan.clone());
31//!
32//! // Add the task to the set of tasks to be awaited.
33//! ```
34
35use crate::{
36    driver,
37    types::{device, Error},
38    Result,
39};
40use chrono::*;
41use tokio::sync::{mpsc, oneshot};
42
43/// Holds information about a device. A back-end is free to store this
44/// information in any way it sees fit. However, it is returned for
45/// GraphQL queries, so it should be reasonably efficient to assemble
46/// this reply.
47
48#[derive(Debug, PartialEq)]
49pub struct DevInfoReply {
50    /// The full name of the device.
51    pub name: device::Name,
52    /// The device's engineering units. Some devices don't use units
53    /// (boolean devices are an example.)
54    pub units: Option<String>,
55    /// Indicates whether the device is settable.
56    pub settable: bool,
57    pub total_points: u32,
58    pub first_point: Option<device::Reading>,
59    pub last_point: Option<device::Reading>,
60    /// The name of the driver that supports this device.
61    pub driver: driver::Name,
62}
63
64// Defines the requests that can be sent to core.
65#[doc(hidden)]
66pub enum Request {
67    QueryDeviceInfo {
68        pattern: Option<String>,
69        rpy_chan: oneshot::Sender<Result<Vec<DevInfoReply>>>,
70    },
71
72    SetDevice {
73        name: device::Name,
74        value: device::Value,
75        rpy_chan: oneshot::Sender<Result<device::Value>>,
76    },
77
78    GetSettingChan {
79        name: device::Name,
80        _own: bool,
81        rpy_chan: oneshot::Sender<Result<driver::TxDeviceSetting>>,
82    },
83
84    MonitorDevice {
85        name: device::Name,
86        start: Option<DateTime<Utc>>,
87        end: Option<DateTime<Utc>>,
88        rpy_chan: oneshot::Sender<Result<device::DataStream<device::Reading>>>,
89    },
90}
91
92/// A handle which is used to communicate with the core of DrMem.
93/// Clients will be given a handle to be used throughout its life.
94///
95/// This type wraps the `mpsc::Sender<>` and defines a set of helper
96/// methods to send requests and receive replies with the core.
97#[derive(Clone)]
98pub struct RequestChan {
99    req_chan: mpsc::Sender<Request>,
100}
101
102impl RequestChan {
103    pub fn new(req_chan: mpsc::Sender<Request>) -> Self {
104        RequestChan { req_chan }
105    }
106
107    /// Makes a request to monitor the device, `name`.
108    ///
109    /// If sucessful, a stream is returned which yields device
110    /// readings as the device is updated.
111
112    pub async fn monitor_device(
113        &self,
114        name: device::Name,
115        start: Option<DateTime<Utc>>,
116        end: Option<DateTime<Utc>>,
117    ) -> Result<device::DataStream<device::Reading>> {
118        // Create our reply channel and build the request message.
119
120        let (tx, rx) = oneshot::channel();
121        let msg = Request::MonitorDevice {
122            name,
123            rpy_chan: tx,
124            start,
125            end,
126        };
127
128        // Send the message.
129
130        self.req_chan.send(msg).await?;
131
132        // Wait for a reply.
133
134        rx.await?
135    }
136
137    /// Requests that a device be set to a provided value.
138    ///
139    /// - `name` is the name of the device
140    /// - `value` is the value to be set. This value can be a
141    ///   `device::Value` value or can be any type that can be coerced
142    ///   into one.
143    ///
144    /// Returns the value the driver actually used to set the device.
145    /// Some drivers do sanity checks on the set value and, if the
146    /// value is unusable, the driver may return an error or clip the
147    /// value to something valid. The driver's documentation should
148    /// indicate how it handles invalid settings.
149
150    pub async fn set_device<
151        T: Into<device::Value> + TryFrom<device::Value, Error = Error>,
152    >(
153        &self,
154        name: device::Name,
155        value: T,
156    ) -> Result<T> {
157        // Create the reply channel and the request message that will
158        // be sent.
159
160        let (tx, rx) = oneshot::channel();
161        let msg = Request::SetDevice {
162            name,
163            value: value.into(),
164            rpy_chan: tx,
165        };
166
167        // Send the request to the driver.
168
169        self.req_chan.send(msg).await?;
170
171        // Wait for the reply and try to convert the set value back
172        // into the type that was used.
173
174        rx.await?.and_then(T::try_from)
175    }
176
177    pub async fn get_setting_chan(
178        &self,
179        name: device::Name,
180        own: bool,
181    ) -> Result<driver::TxDeviceSetting> {
182        // Create the reply channel and the request message that will
183        // be sent.
184
185        let (tx, rx) = oneshot::channel();
186        let msg = Request::GetSettingChan {
187            name,
188            _own: own,
189            rpy_chan: tx,
190        };
191
192        // Send the request to the driver.
193
194        self.req_chan.send(msg).await?;
195
196        // Wait for the reply and try to convert the set value back
197        // into the type that was used.
198
199        rx.await?
200    }
201
202    /// Requests device information for devices whose name matches the
203    /// provided pattern.
204
205    pub async fn get_device_info(
206        &self,
207        pattern: Option<String>,
208    ) -> Result<Vec<DevInfoReply>> {
209        let (rpy_chan, rx) = oneshot::channel();
210
211        // Send the request to the service (i.e. the backend) that has
212        // the device information.
213
214        self.req_chan
215            .send(Request::QueryDeviceInfo { pattern, rpy_chan })
216            .await?;
217
218        // Return the reply from the request.
219
220        rx.await.map_err(|e| e.into()).and_then(|v| v)
221    }
222}