dht_logger/
messages.rs

1//! Serializable messages representing DHT sensor data.
2
3use std::collections::{HashMap, HashSet};
4use std::io::{Error, ErrorKind};
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9use super::Result;
10
11/// Serde JSON from the DHT sensor over serial.
12#[derive(Clone, Debug, Deserialize, Serialize)]
13pub struct DhtDataRaw {
14    pub t: f32,
15    pub h: f32,
16    pub hi: f32,
17}
18
19/// A single reading for a DHT sensor.
20#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize)]
21pub struct SensorData {
22    pub temperature: f32,
23    pub humidity: f32,
24    pub heat_index: f32,
25}
26
27/// Convert the RAW Json to SensorData so it can be re-serialized with full field names.
28impl From<DhtDataRaw> for SensorData {
29    fn from(data: DhtDataRaw) -> Self {
30        SensorData {
31            temperature: data.t,
32            humidity: data.h,
33            heat_index: data.hi,
34        }
35    }
36}
37
38/// Container of measurements from all DHT sensors in one reading.
39///
40/// The JSON serialization is not compact. For smaller JSON messages, use `DhtSensorsSerde`.
41#[derive(Debug, Deserialize, Serialize)]
42pub struct DhtSensors {
43    pub timestamp: DateTime<Utc>,
44    pub data: HashMap<String, SensorData>,
45}
46
47impl DhtSensors {
48    /// Decode a `DntSensorsSerde` struct into DhtSensors.
49    ///
50    /// If not all hashmaps in DhtSensorsPacked have
51    pub fn from_serde(data: DhtSensorsSerde) -> Result<DhtSensors> {
52        let mut lengths = HashSet::new();
53        lengths.insert(data.o.len());
54        lengths.insert(data.t.len());
55        lengths.insert(data.h.len());
56        lengths.insert(data.hi.len());
57
58        if lengths.len() != 1 {
59            return Err(Error::new(
60                ErrorKind::InvalidData,
61                "length mismatch in serde data",
62            ));
63        }
64
65        let mut sensor_data = HashMap::new();
66        for (i, key) in data.o.iter().enumerate() {
67            sensor_data.insert(
68                key.clone(),
69                SensorData {
70                    temperature: data.t[i],
71                    humidity: data.h[i],
72                    heat_index: data.hi[i],
73                },
74            );
75        }
76
77        Ok(DhtSensors {
78            timestamp: data.ts,
79            data: sensor_data,
80        })
81    }
82}
83
84/// A more compactly serialized verson of DhtSensors for serializing via JSON
85///
86/// This is not intended on being human-readable. For human-readability, use `DhtSensors` instead.
87#[derive(Debug, Deserialize, Serialize)]
88pub struct DhtSensorsSerde {
89    pub ts: DateTime<Utc>,
90    pub o: Vec<String>,
91    pub t: Vec<f32>,
92    pub h: Vec<f32>,
93    pub hi: Vec<f32>,
94}
95
96impl From<DhtSensors> for DhtSensorsSerde {
97    fn from(data: DhtSensors) -> DhtSensorsSerde {
98        let timestamp = data.timestamp;
99        let mut order = Vec::new();
100        let mut temperature = Vec::new();
101        let mut humidity = Vec::new();
102        let mut heat_index = Vec::new();
103
104        for (key, value) in data.data.iter() {
105            order.push(key.clone());
106            temperature.push(value.temperature);
107            humidity.push(value.humidity);
108            heat_index.push(value.heat_index);
109        }
110
111        DhtSensorsSerde {
112            ts: timestamp,
113            o: order,
114            t: temperature,
115            h: humidity,
116            hi: heat_index,
117        }
118    }
119}
120
121union DhtDataUnion<'a> {
122    error: &'a str,
123    data: SensorData,
124}
125
126/// Data container for a DHT sensor measurement that contains either an error or data.
127/// ```
128/// use dht_logger::{Measurement, SensorData};
129/// // Example test data
130/// let error = "test";
131/// let data = SensorData {
132///     temperature: 0.0,
133///     humidity: 0.0,
134///     heat_index: 0.0,
135/// };
136///
137/// // Create a measurement containing an error
138/// let measurement = Measurement::new(None, Some(error));
139/// assert!(measurement.get_data().is_none());
140/// assert!(measurement.get_error().is_some());
141/// assert_eq!(measurement.get_error().unwrap(), error);
142///
143/// // Create a measurement containing data
144/// let measurement = Measurement::new(Some(data), None);
145/// assert!(measurement.get_data().is_some());
146/// assert!(measurement.get_error().is_none());
147/// assert_eq!(measurement.get_data().unwrap(), data);
148/// ```
149pub struct Measurement<'a> {
150    error: bool,
151    data: DhtDataUnion<'a>,
152}
153
154impl<'a> Measurement<'a> {
155    /// Create a new measurement of a DHT sensor.
156    ///
157    /// Args:
158    /// * `data`: Sensor data from one DHT sensor.
159    /// * `error`: Error indicating a failure to read a DHT sensor.
160    pub fn new(data: Option<SensorData>, error: Option<&'a str>) -> Measurement {
161        if (data.is_some() && error.is_some()) || (data.is_none() && error.is_none()) {
162            panic!("Exactly one of data or error must be a Some type.");
163        }
164
165        if let Some(data) = data {
166            return Measurement {
167                error: false,
168                data: DhtDataUnion { data },
169            };
170        }
171
172        if let Some(error) = error {
173            return Measurement {
174                error: true,
175                data: DhtDataUnion { error },
176            };
177        }
178
179        // This should never happen
180        Measurement {
181            error: true,
182            data: DhtDataUnion {
183                error: "initialization error",
184            },
185        }
186    }
187
188    /// Get the data contained by the measurement, if it exists.
189    pub fn get_data(&self) -> Option<SensorData> {
190        if self.has_data() {
191            unsafe { Some(self.data.data) }
192        } else {
193            None
194        }
195    }
196
197    /// Get the error contained by the measurement, if it exists.
198    pub fn get_error(&self) -> Option<&'a str> {
199        if self.has_error() {
200            unsafe { Some(self.data.error) }
201        } else {
202            None
203        }
204    }
205
206    /// Check if the measurement has data.
207    pub fn has_data(&self) -> bool {
208        !self.error
209    }
210
211    /// Check if the measurement has an error.
212    pub fn has_error(&self) -> bool {
213        self.error
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220    // Test that Measurement panics when giving None twice
221    #[test]
222    #[should_panic]
223    fn test_measurement_new_both_none() {
224        Measurement::new(None, None);
225    }
226
227    // Test that Measurement panics when giving Some twice
228    #[test]
229    #[should_panic]
230    fn test_measurement_new_both_some() {
231        let error = "test";
232        let data = SensorData {
233            temperature: 0.0,
234            humidity: 0.0,
235            heat_index: 0.0,
236        };
237        Measurement::new(Some(data), Some(error));
238    }
239
240    // Test that SensorData can be converted from a DhtDataRaw
241    #[test]
242    fn test_convert_from_raw() {
243        let raw = DhtDataRaw {
244            t: 21.3,
245            h: 52.7,
246            hi: 22.8,
247        };
248
249        let data = SensorData::from(raw.clone());
250        assert_eq!(raw.t, data.temperature);
251        assert_eq!(raw.h, data.humidity);
252        assert_eq!(raw.hi, data.heat_index);
253    }
254}