1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! This module defines types and interfaces that internal clients use
//! to interact with the core of DrMem. The primary, internal client
//! is the GraphQL interface.
//!
//! Any new, internal tasks that need access to device readings or
//! wish to set the value of the device need to have a
//! `client::RequestChan` handle. As DrMem starts, it should
//! `.clone()` the `RequestChan` used to communicate with the
//! back-end.
//!
//! # Example
//!
//! ```ignore
//! async fn some_new_task(handle: client::RequestChan) {
//!    // Initialize and enter loop.
//!
//!    let device = "some:device".parse::<device::Name>().unwrap();
//!
//!    loop {
//!        // Set a device value.
//!
//!        if some_condition {
//!            handle.set_device(&device, true.into())
//!        }
//!    }
//! }
//!
//! // Somewhere in DrMem start-up.
//!
//! let task = some_new_task(backend_chan.clone());
//!
//! // Add the task to the set of tasks to be awaited.
//! ```

use crate::{
    driver,
    types::{device, Error},
    Result,
};
use chrono::*;
use tokio::sync::{mpsc, oneshot};

/// Holds information about a device. A back-end is free to store this
/// information in any way it sees fit. However, it is returned for
/// GraphQL queries, so it should be reasonably efficient to assemble
/// this reply.

#[derive(Debug, PartialEq)]
pub struct DevInfoReply {
    /// The full name of the device.
    pub name: device::Name,
    /// The device's engineering units. Some devices don't use units
    /// (boolean devices are an example.)
    pub units: Option<String>,
    /// Indicates whether the device is settable.
    pub settable: bool,
    pub total_points: u32,
    pub first_point: Option<device::Reading>,
    pub last_point: Option<device::Reading>,
    /// The name of the driver that supports this device.
    pub driver: String,
}

// Defines the requests that can be sent to core.
#[doc(hidden)]
pub enum Request {
    QueryDeviceInfo {
        pattern: Option<String>,
        rpy_chan: oneshot::Sender<Result<Vec<DevInfoReply>>>,
    },

    SetDevice {
        name: device::Name,
        value: device::Value,
        rpy_chan: oneshot::Sender<Result<device::Value>>,
    },

    GetSettingChan {
        name: device::Name,
        _own: bool,
        rpy_chan: oneshot::Sender<Result<driver::TxDeviceSetting>>,
    },

    MonitorDevice {
        name: device::Name,
        start: Option<DateTime<Utc>>,
        end: Option<DateTime<Utc>>,
        rpy_chan: oneshot::Sender<Result<device::DataStream<device::Reading>>>,
    },
}

/// A handle which is used to communicate with the core of DrMem.
/// Clients will be given a handle to be used throughout its life.
///
/// This type wraps the `mpsc::Sender<>` and defines a set of helper
/// methods to send requests and receive replies with the core.
#[derive(Clone)]
pub struct RequestChan {
    req_chan: mpsc::Sender<Request>,
}

impl RequestChan {
    pub fn new(req_chan: mpsc::Sender<Request>) -> Self {
        RequestChan { req_chan }
    }

    /// Makes a request to monitor the device, `name`.
    ///
    /// If sucessful, a stream is returned which yields device
    /// readings as the device is updated.

    pub async fn monitor_device(
        &self,
        name: device::Name,
        start: Option<DateTime<Utc>>,
        end: Option<DateTime<Utc>>,
    ) -> Result<device::DataStream<device::Reading>> {
        // Create our reply channel and build the request message.

        let (tx, rx) = oneshot::channel();
        let msg = Request::MonitorDevice {
            name,
            rpy_chan: tx,
            start,
            end,
        };

        // Send the message.

        self.req_chan.send(msg).await?;

        // Wait for a reply.

        rx.await?
    }

    /// Requests that a device be set to a provided value.
    ///
    /// - `name` is the name of the device
    /// - `value` is the value to be set. This value can be a
    ///   `device::Value` value or can be any type that can be coerced
    ///   into one.
    ///
    /// Returns the value the driver actually used to set the device.
    /// Some drivers do sanity checks on the set value and, if the
    /// value is unusable, the driver may return an error or clip the
    /// value to something valid. The driver's documentation should
    /// indicate how it handles invalid settings.

    pub async fn set_device<
        T: Into<device::Value> + TryFrom<device::Value, Error = Error>,
    >(
        &self,
        name: device::Name,
        value: T,
    ) -> Result<T> {
        // Create the reply channel and the request message that will
        // be sent.

        let (tx, rx) = oneshot::channel();
        let msg = Request::SetDevice {
            name,
            value: value.into(),
            rpy_chan: tx,
        };

        // Send the request to the driver.

        self.req_chan.send(msg).await?;

        // Wait for the reply and try to convert the set value back
        // into the type that was used.

        rx.await?.and_then(T::try_from)
    }

    pub async fn get_setting_chan(
        &self,
        name: device::Name,
        own: bool,
    ) -> Result<driver::TxDeviceSetting> {
        // Create the reply channel and the request message that will
        // be sent.

        let (tx, rx) = oneshot::channel();
        let msg = Request::GetSettingChan {
            name,
            _own: own,
            rpy_chan: tx,
        };

        // Send the request to the driver.

        self.req_chan.send(msg).await?;

        // Wait for the reply and try to convert the set value back
        // into the type that was used.

        rx.await?
    }

    /// Requests device information for devices whose name matches the
    /// provided pattern.

    pub async fn get_device_info(
        &self,
        pattern: Option<String>,
    ) -> Result<Vec<DevInfoReply>> {
        let (rpy_chan, rx) = oneshot::channel();

        // Send the request to the service (i.e. the backend) that has
        // the device information.

        self.req_chan
            .send(Request::QueryDeviceInfo { pattern, rpy_chan })
            .await?;

        // Return the reply from the request.

        rx.await.map_err(|e| e.into()).and_then(|v| v)
    }
}