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#[derive(Debug, PartialEq)]
48pub struct DevInfoReply {
49    /// The full name of the device.
50    pub name: device::Name,
51    /// The device's engineering units. Some devices don't use units
52    /// (boolean devices are an example.)
53    pub units: Option<String>,
54    /// Indicates whether the device is settable.
55    pub settable: bool,
56    pub total_points: u32,
57    pub first_point: Option<device::Reading>,
58    pub last_point: Option<device::Reading>,
59    /// The name of the driver that supports this device.
60    pub driver: driver::Name,
61}
62
63// Defines the requests that can be sent to core.
64#[doc(hidden)]
65pub enum Request {
66    QueryDeviceInfo {
67        pattern: Option<String>,
68        rpy_chan: oneshot::Sender<Result<Vec<DevInfoReply>>>,
69    },
70
71    SetDevice {
72        name: device::Name,
73        value: device::Value,
74        rpy_chan: oneshot::Sender<Result<device::Value>>,
75    },
76
77    GetSettingChan {
78        name: device::Name,
79        _own: bool,
80        rpy_chan: oneshot::Sender<Result<driver::TxDeviceSetting>>,
81    },
82
83    MonitorDevice {
84        name: device::Name,
85        start: Option<DateTime<Utc>>,
86        end: Option<DateTime<Utc>>,
87        rpy_chan: oneshot::Sender<Result<device::DataStream<device::Reading>>>,
88    },
89}
90
91/// A handle which is used to communicate with the core of DrMem.
92/// Clients will be given a handle to be used throughout its life.
93///
94/// This type wraps the `mpsc::Sender<>` and defines a set of helper
95/// methods to send requests and receive replies with the core.
96#[derive(Clone)]
97pub struct RequestChan {
98    req_chan: mpsc::Sender<Request>,
99}
100
101impl RequestChan {
102    pub fn new(req_chan: mpsc::Sender<Request>) -> Self {
103        RequestChan { req_chan }
104    }
105
106    /// Makes a request to monitor the device, `name`.
107    ///
108    /// If sucessful, a stream is returned which yields device
109    /// readings as the device is updated.
110    pub async fn monitor_device(
111        &self,
112        name: device::Name,
113        start: Option<DateTime<Utc>>,
114        end: Option<DateTime<Utc>>,
115    ) -> Result<device::DataStream<device::Reading>> {
116        // Create our reply channel and build the request message.
117
118        let (tx, rx) = oneshot::channel();
119        let msg = Request::MonitorDevice {
120            name,
121            rpy_chan: tx,
122            start,
123            end,
124        };
125
126        // Send the message.
127
128        self.req_chan.send(msg).await?;
129
130        // Wait for a reply.
131
132        rx.await?
133    }
134
135    /// Requests that a device be set to a provided value.
136    ///
137    /// - `name` is the name of the device
138    /// - `value` is the value to be set. This value can be a
139    ///   `device::Value` value or can be any type that can be coerced
140    ///   into one.
141    ///
142    /// Returns the value the driver actually used to set the device.
143    /// Some drivers do sanity checks on the set value and, if the
144    /// value is unusable, the driver may return an error or clip the
145    /// value to something valid. The driver's documentation should
146    /// indicate how it handles invalid settings.
147    pub async fn set_device<
148        T: Into<device::Value> + TryFrom<device::Value, Error = Error>,
149    >(
150        &self,
151        name: device::Name,
152        value: T,
153    ) -> Result<T> {
154        // Create the reply channel and the request message that will
155        // be sent.
156
157        let (tx, rx) = oneshot::channel();
158        let msg = Request::SetDevice {
159            name,
160            value: value.into(),
161            rpy_chan: tx,
162        };
163
164        // Send the request to the driver.
165
166        self.req_chan.send(msg).await?;
167
168        // Wait for the reply and try to convert the set value back
169        // into the type that was used.
170
171        rx.await?.and_then(T::try_from)
172    }
173
174    pub async fn get_setting_chan(
175        &self,
176        name: device::Name,
177        own: bool,
178    ) -> Result<driver::TxDeviceSetting> {
179        // Create the reply channel and the request message that will
180        // be sent.
181
182        let (tx, rx) = oneshot::channel();
183        let msg = Request::GetSettingChan {
184            name,
185            _own: own,
186            rpy_chan: tx,
187        };
188
189        // Send the request to the driver.
190
191        self.req_chan.send(msg).await?;
192
193        // Wait for the reply and try to convert the set value back
194        // into the type that was used.
195
196        rx.await?
197    }
198
199    /// Requests device information for devices whose name matches the
200    /// provided pattern.
201    pub async fn get_device_info(
202        &self,
203        pattern: Option<String>,
204    ) -> Result<Vec<DevInfoReply>> {
205        let (rpy_chan, rx) = oneshot::channel();
206
207        // Send the request to the service (i.e. the backend) that has
208        // the device information.
209
210        self.req_chan
211            .send(Request::QueryDeviceInfo { pattern, rpy_chan })
212            .await?;
213
214        // Return the reply from the request.
215
216        rx.await.map_err(|e| e.into()).and_then(|v| v)
217    }
218}