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}