arctic/
control.rs

1//! # Control
2//!
3//! Control contains structures related to sending and receiving messages over PMD control point.
4//!
5
6use crate::{find_characteristic, Error, H10MeasurementType, PolarResult};
7
8use btleplug::api::{Characteristic, Peripheral as _, WriteType};
9use btleplug::platform::Peripheral;
10use uuid::Uuid;
11
12/// Polar Measurement Data Control Point (Read | Write | Indicate)
13const PMD_CP_UUID: Uuid = Uuid::from_u128(0xfb005c81_02e7_f387_1cad_8acd2d8df0c8);
14/// Polar Measurement Data... Data (Notify)
15const PMD_DATA_UUID: Uuid = Uuid::from_u128(0xfb005c82_02e7_f387_1cad_8acd2d8df0c8);
16
17/// Command options to write to the control point
18#[derive(Debug, PartialEq, Eq)]
19pub enum ControlPointCommand {
20    /// Do nothing
21    Null = 0,
22    /// Get the measurement settings of every data type in `PolarSensor.data_type`
23    GetMeasurementSettings,
24    /// Start measurement of every data type in `PolarSensor.data_type`
25    RequestMeasurementStart,
26    /// Stop all measurements in `PolarSensor.data_type`
27    StopMeasurement,
28}
29
30impl TryFrom<u8> for ControlPointCommand {
31    type Error = ();
32
33    fn try_from(val: u8) -> Result<ControlPointCommand, ()> {
34        match val {
35            0 => Ok(ControlPointCommand::Null),
36            1 => Ok(ControlPointCommand::GetMeasurementSettings),
37            2 => Ok(ControlPointCommand::RequestMeasurementStart),
38            3 => Ok(ControlPointCommand::StopMeasurement),
39            _ => {
40                println!("Invalid ControlPointCommand {}", val);
41                Err(())
42            }
43        }
44    }
45}
46
47/// Response code returned after a write to PMD control point
48#[derive(Debug, PartialEq, Eq)]
49pub enum ControlPointResponseCode {
50    /// Command was successful
51    Success = 0,
52    /// Control point command is not supported by device
53    InvalidOpCode,
54    /// Device does not know the specified measurement type
55    InvalidMeasurementType,
56    /// This measurement is not supported by device
57    NotSupported,
58    /// Given length does not match the received data
59    InvalidLength,
60    /// Contains parameters that prevent successful handling of request
61    InvalidParameter,
62    /// Device is already in the requested state
63    AlreadyInState,
64    /// Requested resolution is not supported by device
65    InvalidResolution,
66    /// Requested sample rate is not supported by device
67    InvalidSampleRate,
68    /// Requested range is not supported
69    InvalidRange,
70    /// Connection MTU does not match device required MTU
71    InvalidMTU,
72    /// Request contains invalid number of channels
73    InvalidNumberOfChannels,
74    /// Device is in invalid state
75    InvalidState,
76    /// Device is in charger and does not support requests
77    DeviceInCharger,
78}
79
80impl TryFrom<u8> for ControlPointResponseCode {
81    type Error = ();
82
83    fn try_from(val: u8) -> Result<ControlPointResponseCode, ()> {
84        match val {
85            0 => Ok(ControlPointResponseCode::Success),
86            1 => Ok(ControlPointResponseCode::InvalidOpCode),
87            2 => Ok(ControlPointResponseCode::InvalidMeasurementType),
88            3 => Ok(ControlPointResponseCode::NotSupported),
89            4 => Ok(ControlPointResponseCode::InvalidLength),
90            5 => Ok(ControlPointResponseCode::InvalidParameter),
91            6 => Ok(ControlPointResponseCode::AlreadyInState),
92            7 => Ok(ControlPointResponseCode::InvalidResolution),
93            8 => Ok(ControlPointResponseCode::InvalidSampleRate),
94            9 => Ok(ControlPointResponseCode::InvalidRange),
95            10 => Ok(ControlPointResponseCode::InvalidMTU),
96            11 => Ok(ControlPointResponseCode::InvalidNumberOfChannels),
97            12 => Ok(ControlPointResponseCode::InvalidState),
98            13 => Ok(ControlPointResponseCode::DeviceInCharger),
99            _ => {
100                println!("Invalid ControlPointResponseCode {}", val);
101                Err(())
102            }
103        }
104    }
105}
106
107#[derive(Debug, PartialEq, Eq)]
108enum ResponseCode {
109    Success,
110    InvalidHandle,
111    ReadNotPermitted,
112    WriteNotPermitted,
113    InvalidPdu,
114    InsufficientAuthentication,
115    RequestNotSupported,
116    InvalidOffset,
117    InsufficientAuthorization,
118    PrepareQueueFull,
119    AttributeNotFound,
120    AttributeNotLong,
121    InsufficientEncryptionKeySize,
122    InsufficientAttributeValueLength,
123    UnlikelyError,
124    InsufficientEncryption,
125    UnsupportedGroupType,
126    InsufficientResources,
127}
128
129impl TryFrom<u8> for ResponseCode {
130    type Error = ();
131
132    fn try_from(val: u8) -> Result<ResponseCode, ()> {
133        match val {
134            0 => Ok(ResponseCode::Success),
135            1 => Ok(ResponseCode::InvalidHandle),
136            2 => Ok(ResponseCode::ReadNotPermitted),
137            3 => Ok(ResponseCode::WriteNotPermitted),
138            4 => Ok(ResponseCode::InvalidPdu),
139            5 => Ok(ResponseCode::InsufficientAuthentication),
140            6 => Ok(ResponseCode::RequestNotSupported),
141            7 => Ok(ResponseCode::InvalidOffset),
142            8 => Ok(ResponseCode::InsufficientAuthorization),
143            9 => Ok(ResponseCode::PrepareQueueFull),
144            10 => Ok(ResponseCode::AttributeNotFound),
145            11 => Ok(ResponseCode::AttributeNotLong),
146            12 => Ok(ResponseCode::InsufficientEncryptionKeySize),
147            13 => Ok(ResponseCode::InsufficientAttributeValueLength),
148            14 => Ok(ResponseCode::UnlikelyError),
149            15 => Ok(ResponseCode::InsufficientEncryption),
150            16 => Ok(ResponseCode::UnsupportedGroupType),
151            17 => Ok(ResponseCode::InsufficientResources),
152            _ => {
153                println!("Invalid ResponseCode {}", val);
154                Err(())
155            }
156        }
157    }
158}
159
160#[derive(Clone, Copy, Debug)]
161enum SettingType {
162    SampleRate,
163    Resolution,
164    Range,
165}
166
167impl SettingType {
168    fn from(byte: u8) -> SettingType {
169        match byte {
170            0x00 => SettingType::SampleRate,
171            0x01 => SettingType::Resolution,
172            _ => SettingType::Range,
173        }
174    }
175}
176
177enum PmdByteType {
178    Setting,
179    ArrLen,
180    Data,
181}
182
183/// Struct to store the settings for a specific stream on your device
184#[derive(Debug, PartialEq, Eq)]
185pub struct StreamSettings {
186    ty: H10MeasurementType,
187    resolution: u8,
188    range: Option<Vec<u8>>,
189    sample_rate: Vec<u8>,
190}
191
192impl StreamSettings {
193    /// Create new stream settings
194    pub fn new(resp: &ControlResponse) -> PolarResult<StreamSettings> {
195        if *resp.opcode() != ControlPointCommand::GetMeasurementSettings {
196            return Err(Error::WrongResponse);
197        }
198
199        let mut resolution: u8 = 0;
200        let mut ranges: Vec<u8> = vec![];
201        let mut sample_rate: Vec<u8> = vec![];
202
203        let mut setting: SettingType = SettingType::from(resp.parameters[0]);
204        let mut next_byte: PmdByteType = PmdByteType::ArrLen;
205        let mut len_remaining = 0u8;
206
207        let mut data = resp.parameters()[1..].iter();
208
209        while let Some(i) = data.next() {
210            match next_byte {
211                PmdByteType::Setting => {
212                    setting = SettingType::from(*i);
213                    next_byte = PmdByteType::ArrLen;
214                }
215                PmdByteType::ArrLen => {
216                    len_remaining = *i;
217                    next_byte = PmdByteType::Data;
218                }
219                PmdByteType::Data => {
220                    match setting {
221                        SettingType::SampleRate => {
222                            sample_rate.push(*i);
223                            let _ = data.next().unwrap();
224                        }
225                        SettingType::Resolution => {
226                            resolution = *i;
227                            let _ = data.next().unwrap();
228                        }
229                        SettingType::Range => {
230                            ranges.push(*i);
231                            let _ = data.next().unwrap();
232                        }
233                    }
234
235                    len_remaining -= 1;
236                    if len_remaining == 0 {
237                        next_byte = PmdByteType::Setting;
238                    }
239                }
240            }
241        }
242
243        let range = if !ranges.is_empty() {
244            Some(ranges)
245        } else {
246            None
247        };
248
249        Ok(StreamSettings {
250            ty: *resp.data_type(),
251            resolution,
252            range,
253            sample_rate,
254        })
255    }
256
257    /// Getter for the resolution (in bits)
258    pub fn resolution(&self) -> u8 {
259        self.resolution
260    }
261
262    /// Getter for range (ACC only) (in G)
263    pub fn range(&self) -> &Option<Vec<u8>> {
264        &self.range
265    }
266
267    /// Getter for sample rates (in Hz)
268    pub fn sample_rate(&self) -> &Vec<u8> {
269        &self.sample_rate
270    }
271}
272
273/// Store data returned from the device after a write to the control point
274#[derive(Debug)]
275pub struct ControlResponse {
276    opcode: ControlPointCommand,
277    measurement_type: H10MeasurementType,
278    status: ControlPointResponseCode,
279    parameters: Vec<u8>,
280}
281
282impl ControlResponse {
283    /// Create new `ControlResponse`
284    pub async fn new(data: Vec<u8>) -> PolarResult<ControlResponse> {
285        // We need at least 4 bytes for a complete packet
286        if data.len() < 4 {
287            return Err(Error::InvalidData);
288        }
289        // check that our response is a control point response
290        if data[0] != 0xf0 {
291            return Err(Error::InvalidData);
292        }
293        let opcode = ControlPointCommand::try_from(data[1]).map_err(|_| Error::InvalidData)?;
294        let measurement_type =
295            H10MeasurementType::try_from(data[2]).map_err(|_| Error::InvalidData)?;
296        let status = ControlPointResponseCode::try_from(data[3]).map_err(|_| Error::InvalidData)?;
297        let mut parameters = Vec::new();
298
299        if data.len() > 5 {
300            parameters = data[5..].to_vec();
301        }
302
303        Ok(ControlResponse {
304            opcode,
305            measurement_type,
306            status,
307            parameters,
308        })
309    }
310
311    /// Return extra parameters of this response
312    pub fn parameters(&self) -> &Vec<u8> {
313        &self.parameters
314    }
315
316    /// Return opcode of this response
317    pub fn opcode(&self) -> &ControlPointCommand {
318        &self.opcode
319    }
320
321    /// Get measurement type
322    pub fn data_type(&self) -> &H10MeasurementType {
323        &self.measurement_type
324    }
325
326    /// Get response status
327    pub fn status(&self) -> &ControlPointResponseCode {
328        &self.status
329    }
330}
331
332/// Struct that has access to the PMD control point point and PMD data
333#[derive(Debug, PartialEq, Eq)]
334pub struct ControlPoint {
335    control_point: Characteristic,
336    measurement_data: Characteristic,
337}
338
339impl ControlPoint {
340    /// Create new `ControlPoint`
341    pub async fn new(device: &Peripheral) -> PolarResult<ControlPoint> {
342        let control_point = find_characteristic(device, PMD_CP_UUID).await?;
343        let measurement_data = find_characteristic(device, PMD_DATA_UUID).await?;
344
345        Ok(ControlPoint {
346            control_point,
347            measurement_data,
348        })
349    }
350
351    /// Send command to Control Point
352    pub async fn send_command(&self, device: &Peripheral, data: Vec<u8>) -> PolarResult<()> {
353        self.write(device, data).await?;
354
355        Ok(())
356    }
357
358    async fn write(&self, device: &Peripheral, data: Vec<u8>) -> PolarResult<()> {
359        device
360            .write(&self.control_point, &data, WriteType::WithResponse)
361            .await
362            .map_err(Error::BleError)
363    }
364
365    /// Read data from control point (for reading the features of a device)
366    pub async fn read(&self, device: &Peripheral) -> PolarResult<Vec<u8>> {
367        device
368            .read(&self.control_point)
369            .await
370            .map_err(Error::BleError)
371    }
372}
373
374#[cfg(test)]
375mod test {
376    use super::*;
377
378    // for async testing
379    macro_rules! aw {
380        ($e:expr) => {
381            tokio_test::block_on($e)
382        };
383    }
384
385    #[test]
386    fn settings_ecg() {
387        let norm = StreamSettings {
388            ty: H10MeasurementType::Ecg,
389            resolution: 14,
390            range: None,
391            sample_rate: vec![130],
392        };
393
394        let data = aw!(ControlResponse::new(vec![
395            0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x82, 0x00, 0x01, 0x01, 0x0e, 0x00
396        ]))
397        .unwrap();
398
399        assert_eq!(norm, StreamSettings::new(&data).unwrap());
400    }
401
402    #[test]
403    fn settings_acc() {
404        let norm = StreamSettings {
405            ty: H10MeasurementType::Acc,
406            resolution: 16,
407            range: Some(vec![2, 4, 8]),
408            sample_rate: vec![25, 50, 100, 200],
409        };
410
411        let data = aw!(ControlResponse::new(vec![
412            0xf0, 0x01, 0x02, 0x00, 0x00, 0x00, 0x04, 0x19, 0x00, 0x32, 0x00, 0x64, 0x00, 0xC8,
413            0x00, 0x01, 0x01, 0x10, 0x00, 0x02, 0x03, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00
414        ]))
415        .unwrap();
416
417        assert_eq!(norm, StreamSettings::new(&data).unwrap());
418    }
419}