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}