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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
use core::convert::TryFrom;

use crate::{
    formats::v3::SensorValuesV3, formats::v5::SensorValuesV5, Acceleration, AccelerationVector,
    BatteryPotential, Humidity, MacAddress, MeasurementSequenceNumber, MovementCounter, ParseError,
    Pressure, Temperature, TransmitterPower,
};

/// Represents a set of values read from sensors on the device
#[derive(Debug, PartialEq)]
pub struct SensorValues {
    /// humidity in parts per million
    humidity: Option<u32>,
    /// temperature in milli-kelvins
    temperature: Option<u32>,
    /// pressure in pascals
    pressure: Option<u32>,
    /// 3-dimensional acceleration vector, each component is in milli-G
    acceleration: Option<AccelerationVector>,
    /// battery potential in milli-volts
    battery_potential: Option<u16>,
    /// transmitter power in dBm
    tx_power: Option<i8>,
    /// movement counter
    movement_counter: Option<u32>,
    /// measurement sequence number
    measurement_sequence_number: Option<u32>,
    /// MAC address
    mac_address: Option<[u8; 6]>,
}

impl SensorValues {
    /// Parses sensor values from the payload encoded in manufacturer specific data -field. The
    /// function returns a `ParseError` if the `id` does not match the exptected `id` in the
    /// manufacturer specific data, or the format of the `value` is not supported. At the moment
    /// only versions 3 and 5 of the format are supported.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use ruuvi_sensor_protocol::{SensorValues, Temperature};
    /// # use ruuvi_sensor_protocol::ParseError;
    ///
    /// let id = 0x0499;
    /// let value = &[
    ///     0x03, 0x17, 0x01, 0x45, 0x35, 0x58, 0x03, 0xE8, 0x04, 0xE7, 0x05, 0xE6, 0x08, 0x86,
    /// ];
    /// let values = SensorValues::from_manufacturer_specific_data(id, value)?;
    /// assert_eq!(values.temperature_as_millicelsius(), Some(1690));
    /// # Ok::<(), ParseError>(())
    /// ```
    pub fn from_manufacturer_specific_data(id: u16, value: &[u8]) -> Result<Self, ParseError> {
        match (id, value) {
            (0x0499, []) => Err(ParseError::EmptyValue),
            (0x0499, value) => {
                let format_version = value[0];

                match format_version {
                    3 => {
                        let values = SensorValuesV3::try_from(&value[1..])?;
                        Ok(Self::from(&values))
                    }
                    5 => {
                        let values = SensorValuesV5::try_from(&value[1..])?;
                        Ok(Self::from(&values))
                    }
                    _ => Err(ParseError::UnsupportedFormatVersion(format_version)),
                }
            }
            (id, _) => Err(ParseError::UnknownManufacturerId(id)),
        }
    }
}

impl Acceleration for SensorValues {
    fn acceleration_vector_as_milli_g(&self) -> Option<AccelerationVector> {
        self.acceleration
    }
}

impl BatteryPotential for SensorValues {
    fn battery_potential_as_millivolts(&self) -> Option<u16> {
        self.battery_potential
    }
}

impl TransmitterPower for SensorValues {
    fn tx_power_as_dbm(&self) -> Option<i8> {
        self.tx_power
    }
}

impl Humidity for SensorValues {
    fn humidity_as_ppm(&self) -> Option<u32> {
        self.humidity
    }
}

impl Temperature for SensorValues {
    fn temperature_as_millikelvins(&self) -> Option<u32> {
        self.temperature
    }
}

impl Pressure for SensorValues {
    fn pressure_as_pascals(&self) -> Option<u32> {
        self.pressure
    }
}

impl MovementCounter for SensorValues {
    fn movement_counter(&self) -> Option<u32> {
        self.movement_counter
    }
}

impl MeasurementSequenceNumber for SensorValues {
    fn measurement_sequence_number(&self) -> Option<u32> {
        self.measurement_sequence_number
    }
}

impl MacAddress for SensorValues {
    fn mac_address(&self) -> Option<[u8; 6]> {
        self.mac_address
    }
}

impl<T> From<&T> for SensorValues
where
    T: Acceleration
        + BatteryPotential
        + TransmitterPower
        + Humidity
        + Temperature
        + Pressure
        + MovementCounter
        + MeasurementSequenceNumber
        + MacAddress,
{
    fn from(values: &T) -> SensorValues {
        SensorValues {
            humidity: values.humidity_as_ppm(),
            temperature: values.temperature_as_millikelvins(),
            pressure: values.pressure_as_pascals(),
            acceleration: values.acceleration_vector_as_milli_g(),
            battery_potential: values.battery_potential_as_millivolts(),
            tx_power: values.tx_power_as_dbm(),
            movement_counter: values.movement_counter(),
            measurement_sequence_number: values.measurement_sequence_number(),
            mac_address: values.mac_address(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_unsupported_manufacturer_id() {
        let value = [3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        let result = SensorValues::from_manufacturer_specific_data(0x0477, &value);
        assert!(result.is_err());
        assert_eq!(
            result.unwrap_err(),
            ParseError::UnknownManufacturerId(0x0477)
        );
    }

    #[test]
    fn parse_unsupported_format() {
        let value = [0, 1, 2, 3];
        let result = SensorValues::from_manufacturer_specific_data(0x0499, &value);
        assert!(result.is_err());
        assert_eq!(result.unwrap_err(), ParseError::UnsupportedFormatVersion(0));
    }

    #[test]
    fn parse_empty_data() {
        let value = [];
        let result = SensorValues::from_manufacturer_specific_data(0x0499, &value);
        assert!(result.is_err());
        assert_eq!(result.unwrap_err(), ParseError::EmptyValue);
    }

    #[test]
    fn parse_version_3_data_with_invalid_length() {
        let value = [3, 103, 22, 50, 60, 70];
        let result = SensorValues::from_manufacturer_specific_data(0x0499, &value);
        assert!(result.is_err());
        assert_eq!(
            result.unwrap_err(),
            ParseError::InvalidValueLength(3, 6, 14)
        );
    }

    #[test]
    fn parse_valid_version_3_data() {
        let value = [
            3, 0x17, 0x01, 0x45, 0x35, 0x58, 0x03, 0xE8, 0x04, 0xE7, 0x05, 0xE6, 0x08, 0x86,
        ];
        let result = SensorValues::from_manufacturer_specific_data(0x0499, &value);

        assert_eq!(
            result,
            Ok(SensorValues {
                humidity: Some(115_000),
                temperature: Some(1690 + 273_150),
                pressure: Some(63656),
                acceleration: Some(AccelerationVector(1000, 1255, 1510)),
                battery_potential: Some(2182),
                tx_power: None,
                movement_counter: None,
                measurement_sequence_number: None,
                mac_address: None,
            })
        );
    }

    #[test]
    fn parse_version_5_data_with_invalid_length() {
        let value = [0x05, 0x12, 0xFC, 0x53];
        let result = SensorValues::from_manufacturer_specific_data(0x0499, &value);

        assert!(result.is_err());
        assert_eq!(
            result.unwrap_err(),
            ParseError::InvalidValueLength(5, 4, 24)
        );
    }

    #[test]
    fn parse_valid_version_5_data() {
        let value = [
            0x05, 0x12, 0xFC, 0x53, 0x94, 0xC3, 0x7C, 0x00, 0x04, 0xFF, 0xFC, 0x04, 0x0C, 0xAC,
            0x36, 0x42, 0x00, 0xCD, 0xCB, 0xB8, 0x33, 0x4C, 0x88, 0x4F,
        ];
        let result = SensorValues::from_manufacturer_specific_data(0x0499, &value);

        assert_eq!(
            result,
            Ok(SensorValues {
                humidity: Some(534_900),
                temperature: Some(24_300 + 273_150),
                pressure: Some(100_044),
                acceleration: Some(AccelerationVector(4, -4, 1036)),
                battery_potential: Some(2977),
                tx_power: Some(4),
                movement_counter: Some(66),
                measurement_sequence_number: Some(205),
                mac_address: Some([0xcb, 0xb8, 0x33, 0x4c, 0x88, 0x4f]),
            })
        );
    }
}