fuga_remo_api/
device.rs

1// Device model parser for Remo Cloud API.
2// Copyright 2022-2023 Kenta Ida 
3// SPDX-License-Identifier: MIT
4//
5use core::{fmt::Write, str::FromStr};
6
7use heapless::String;
8use fuga_json_seq_parser::{JsonScalarValue, ParserCallbackAction, JsonNode};
9use fuga_json_seq_parser::Parser as JsonParser;
10use fuga_json_seq_parser::ParserError as JsonParserError;
11
12use nom::{
13    character::complete::one_of,
14    combinator::{map, recognize},
15    error::{ContextError, ParseError},
16    sequence::{pair, separated_pair},
17    IResult,
18};
19use uuid::Uuid;
20use crate::config::*;
21use crate::common_types::*;
22use crate::node_key::*;
23use crate::parser_options::{ParserOptions, copy_string_option};
24
25#[derive(Clone, Debug, Default)]
26pub struct SensorValue {
27    pub val: f32,
28    pub created_at: Timestamp,
29}
30#[derive(Debug, Default)]
31pub struct User {
32    pub id: Uuid,
33    pub nickname: String<MAX_NICKNAME_LEN>,
34    pub superuser: bool,
35}
36
37#[derive(Clone, Debug, Default)]
38pub struct NewestEvents {
39    pub temperature: Option<SensorValue>,
40    pub humidity: Option<SensorValue>,
41    pub illumination: Option<SensorValue>,
42    pub motion: Option<SensorValue>,
43}
44
45#[derive(Clone, Debug, Default, PartialEq)]
46pub struct Device {
47    pub id: Uuid,
48    pub name: String<MAX_DEVICE_NAME_LEN>,
49    pub temperature_offset: f32,
50    pub humidity_offset: f32,
51    pub created_at: Timestamp,
52    pub updated_at: Timestamp,
53    pub firmware_version: String<MAX_FIRMWARE_VERSION_LEN>,
54    pub mac_address: MacAddress,
55    pub bt_mac_address: MacAddress,
56    pub serial_number: SerialNumber,
57}
58
59#[derive(Debug, Default)]
60pub struct Model {
61    pub id: Uuid,
62    pub country: String<MAX_COUNTRY_LEN>,
63    pub manifacturer: String<MAX_MANUFACTURER_LEN>,
64    pub remote_name: String<MAX_REMOTE_NAME_LEN>,
65    pub series: String<MAX_SERIES_LEN>,
66    pub name: String<MAX_MODEL_NAME_LEN>,
67    pub image: String<MAX_IMAGE_LEN>,
68}
69
70#[derive(Debug)]
71pub enum DeviceSubNode {
72    User(User),
73    NewestEvents(NewestEvents),
74}
75
76type DevicesParser = JsonParser<REQUIRED_DEVICES_PARSER_BUFFER_LEN, 5>;
77
78#[derive(Clone, Copy, Debug)]
79enum DevicesParserState {
80    Start,
81    DevicesArray,
82    DeviceMap,
83    UsersArray,
84    UserMap,
85    NewestEventsMap,
86    NewestEventMap(NewestEventType),
87    UnknownMapArray,
88}
89
90#[derive(Clone, Copy, Debug)]
91enum NewestEventType {
92    Temperature,
93    Humidity,
94    Illumination,
95    Motion,
96}
97
98#[derive(Clone, Copy, Debug, PartialEq, Eq)]
99pub struct MacAddress(pub [u8; 6]);
100
101impl Default for MacAddress {
102    fn default() -> Self {
103        Self([0u8; 6])
104    }
105}
106
107fn parse_byte_string<'a, E: ParseError<&'a str> + ContextError<&'a str>>(
108    i: &'a str,
109) -> IResult<&'a str, u8, E> {
110    let hex_char_list = "0123456789ABCDEFabcdef";
111    map(
112        recognize(pair(one_of(hex_char_list), one_of(hex_char_list))),
113        |byte_str| u8::from_str_radix(byte_str, 16).unwrap(),
114    )(i)
115}
116
117fn parse_mac_address<'a, E: ParseError<&'a str> + ContextError<&'a str>>(
118    i: &'a str,
119) -> IResult<&'a str, [u8; 6], E> {
120    let delimiter_list = ":-";
121
122    map(
123        separated_pair(
124            parse_byte_string,
125            one_of(delimiter_list),
126            separated_pair(
127                parse_byte_string,
128                one_of(delimiter_list),
129                separated_pair(
130                    parse_byte_string,
131                    one_of(delimiter_list),
132                    separated_pair(
133                        parse_byte_string,
134                        one_of(delimiter_list),
135                        separated_pair(
136                            parse_byte_string,
137                            one_of(delimiter_list),
138                            parse_byte_string,
139                        ),
140                    ),
141                ),
142            ),
143        ),
144        |(l, r)| [l, r.0, r.1 .0, r.1 .1 .0, r.1 .1 .1 .0, r.1 .1 .1 .1],
145    )(i)
146}
147
148impl FromStr for MacAddress {
149    type Err = ModelNodeParseError;
150    fn from_str(s: &str) -> Result<Self, Self::Err> {
151        let (_, mac_address) = parse_mac_address(s)
152            .map_err(|_: nom::Err<()>| ModelNodeParseError::MacAddressParseError)?;
153        Ok(Self(mac_address))
154    }
155}
156
157pub fn read_devices<R: embedded_io::blocking::Read, F>(
158    reader: &mut R,
159    total_length: Option<usize>,
160    options: &ParserOptions,
161    mut callback: F,
162) -> Result<(), JsonParserError<R::Error, ModelNodeParseError>>
163where
164    F: for<'a> FnMut(&'a Device, Option<&'a DeviceSubNode>),
165{
166    let mut parser = DevicesParser::new();
167    parser.set_bytes_remaining(total_length);
168    let mut device = Device::default();
169    let mut subnode = DeviceSubNode::User(User::default());
170    let mut state = DevicesParserState::Start;
171    let mut node_key = None;
172    let mut unknown_map_depth = 0;
173    let mut unknown_array_depth = 0;
174
175    while !parser.parse(reader, |node| {
176        let new_state = match (state, node) {
177            (DevicesParserState::Start, JsonNode::StartArray) => {
178                DevicesParserState::DevicesArray
179            }
180            (DevicesParserState::DevicesArray, JsonNode::EndArray) => {
181                DevicesParserState::Start
182            }
183            (DevicesParserState::DevicesArray, JsonNode::StartMap) => {
184                DevicesParserState::DeviceMap
185            }
186            (DevicesParserState::DeviceMap, JsonNode::EndMap) => {
187                DevicesParserState::DevicesArray
188            }
189            (map_state, JsonNode::Key(key)) => {
190                match key {
191                    JsonScalarValue::String(key) => {
192                        node_key = ModelNodeKey::try_from(key).ok(); // Store key
193                    }
194                    _ => {}
195                }
196                map_state
197            }
198            // Process map node for device.
199            (DevicesParserState::DeviceMap, JsonNode::Value(value)) => {
200                if let Some(node_key) = node_key.take() {
201                    match (node_key, value) {
202                        (ModelNodeKey::Name, JsonScalarValue::String(s)) => {
203                            device.name = copy_string_option(s, options)?;
204                        }
205                        (ModelNodeKey::Id, JsonScalarValue::String(s)) => {
206                            device.id = Uuid::from_str(s)?
207                        }
208                        (ModelNodeKey::CreatedAt, JsonScalarValue::String(s)) => {
209                            device.created_at = Timestamp::from_str(s)?
210                        }
211                        (ModelNodeKey::UpdatedAt, JsonScalarValue::String(s)) => {
212                            device.updated_at = Timestamp::from_str(s)?
213                        }
214                        (ModelNodeKey::MacAddress, JsonScalarValue::String(s)) => {
215                            device.mac_address = MacAddress::from_str(s)?
216                        }
217                        (ModelNodeKey::BtMacAddress, JsonScalarValue::String(s)) => {
218                            device.bt_mac_address = MacAddress::from_str(s)?
219                        }
220                        (ModelNodeKey::SerialNumber, JsonScalarValue::String(s)) => {
221                            device.serial_number = copy_string_option(s, options)?;
222                        }
223                        (ModelNodeKey::FirmwareVersion, JsonScalarValue::String(s)) => {
224                            device.firmware_version = copy_string_option(s, options)?;
225                        }
226                        (ModelNodeKey::TemperatureOffset, JsonScalarValue::Number(n)) => {
227                            device.temperature_offset = n.into()
228                        }
229                        (ModelNodeKey::HumidityOffset, JsonScalarValue::Number(n)) => {
230                            device.humidity_offset = n.into()
231                        }
232                        _ => {} // Ignore unknown nodes.
233                    }
234                }
235                DevicesParserState::DeviceMap
236            }
237            (DevicesParserState::DeviceMap, JsonNode::StartArray) => {
238                match node_key.take() {
239                    Some(ModelNodeKey::Users) => {
240                        // Call callback for current device
241                        callback(&device, None);
242                        DevicesParserState::UsersArray
243                    }
244                    _ => {
245                        unknown_array_depth += 1;
246                        DevicesParserState::UnknownMapArray
247                    }
248                }
249            }
250            (DevicesParserState::DeviceMap, JsonNode::StartMap) => match node_key.take() {
251                Some(ModelNodeKey::NewestEvents) => {
252                    subnode = DeviceSubNode::NewestEvents(NewestEvents::default());
253                    DevicesParserState::NewestEventsMap
254                }
255                _ => {
256                    unknown_map_depth += 1;
257                    DevicesParserState::UnknownMapArray
258                }
259            },
260
261            // Process users array
262            (DevicesParserState::UsersArray, JsonNode::EndArray) => {
263                DevicesParserState::DeviceMap
264            } // Return to device map state
265            (DevicesParserState::UsersArray, JsonNode::StartMap) => {
266                subnode = DeviceSubNode::User(User::default());
267                DevicesParserState::UserMap
268            }
269            // Process user map
270            (DevicesParserState::UserMap, JsonNode::Value(value)) => {
271                if let DeviceSubNode::User(ref mut user) = &mut subnode {
272                    if let Some(node_key) = node_key.take() {
273                        match (node_key, value) {
274                            (ModelNodeKey::Id, JsonScalarValue::String(s)) => {
275                                user.id = Uuid::from_str(s)?
276                            }
277                            (ModelNodeKey::NickName, JsonScalarValue::String(s)) => {
278                                user.nickname = copy_string_option(s, options)?;
279                            }
280                            (ModelNodeKey::SuperUser, JsonScalarValue::Boolean(v)) => {
281                                user.superuser = v
282                            }
283                            _ => {} // Ignore unknown nodes.
284                        }
285                    }
286                }
287                DevicesParserState::UserMap
288            }
289            (DevicesParserState::UserMap, JsonNode::EndMap) => {
290                callback(&device, Some(&subnode));
291                DevicesParserState::UsersArray // Return to users array.
292            }
293            // Process newest_events map
294            (DevicesParserState::NewestEventsMap, JsonNode::EndMap) => {
295                callback(&device, Some(&subnode));
296                DevicesParserState::DeviceMap // Return to device map state
297            }
298            (DevicesParserState::NewestEventsMap, JsonNode::StartMap) => {
299                let newest_events = if let DeviceSubNode::NewestEvents(ref mut newest_events) =
300                    &mut subnode
301                {
302                    newest_events
303                } else {
304                    panic!(
305                        "sub_node must contains newest_events at (NewestEventsMap, StartMap) state"
306                    );
307                };
308
309                match node_key.take() {
310                    Some(ModelNodeKey::Te) => {
311                        newest_events.temperature = Some(SensorValue::default());
312                        DevicesParserState::NewestEventMap(NewestEventType::Temperature)
313                    }
314                    Some(ModelNodeKey::Hu) => {
315                        newest_events.humidity = Some(SensorValue::default());
316                        DevicesParserState::NewestEventMap(NewestEventType::Humidity)
317                    }
318                    Some(ModelNodeKey::Il) => {
319                        newest_events.illumination = Some(SensorValue::default());
320                        DevicesParserState::NewestEventMap(NewestEventType::Illumination)
321                    }
322                    Some(ModelNodeKey::Mo) => {
323                        newest_events.motion = Some(SensorValue::default());
324                        DevicesParserState::NewestEventMap(NewestEventType::Motion)
325                    }
326                    _ => return Err(ModelNodeParseError::UnknownNewestEventsType),
327                }
328            }
329            // Process maps in a newest_events map
330            (
331                DevicesParserState::NewestEventMap(newest_event_type),
332                JsonNode::Value(value),
333            ) => {
334                if let DeviceSubNode::NewestEvents(ref mut newest_events) = &mut subnode {
335                    let sensor_value = match newest_event_type {
336                        NewestEventType::Temperature => newest_events.temperature.as_mut().unwrap(),
337                        NewestEventType::Humidity => newest_events.humidity.as_mut().unwrap(),
338                        NewestEventType::Illumination => {
339                            newest_events.illumination.as_mut().unwrap()
340                        }
341                        NewestEventType::Motion => newest_events.motion.as_mut().unwrap(),
342                    };
343                    match (node_key.take(), value) {
344                        (Some(ModelNodeKey::Val), JsonScalarValue::Number(n)) => {
345                            sensor_value.val = n.into()
346                        }
347                        (Some(ModelNodeKey::CreatedAt), JsonScalarValue::String(s)) => {
348                            sensor_value.created_at = Timestamp::from_str(s)?
349                        }
350                        _ => {}
351                    }
352                }
353                DevicesParserState::NewestEventMap(newest_event_type)
354            }
355            (DevicesParserState::NewestEventMap(_), JsonNode::EndMap) => {
356                DevicesParserState::NewestEventsMap
357            }
358
359            // Process unknown nodes in device nodes.
360            (DevicesParserState::UnknownMapArray, JsonNode::StartArray) => {
361                unknown_array_depth += 1;
362                DevicesParserState::UnknownMapArray
363            }
364            (DevicesParserState::UnknownMapArray, JsonNode::StartMap) => {
365                unknown_map_depth += 1;
366                DevicesParserState::UnknownMapArray
367            }
368            (DevicesParserState::UnknownMapArray, JsonNode::EndArray) => {
369                unknown_array_depth -= 1;
370                if unknown_array_depth == 0 && unknown_map_depth == 0 {
371                    DevicesParserState::DeviceMap
372                } else {
373                    DevicesParserState::UnknownMapArray
374                }
375            }
376            (DevicesParserState::UnknownMapArray, JsonNode::EndMap) => {
377                unknown_map_depth -= 1;
378                if unknown_array_depth == 0 && unknown_map_depth == 0 {
379                    DevicesParserState::DeviceMap
380                } else {
381                    DevicesParserState::UnknownMapArray
382                }
383            }
384            (DevicesParserState::UnknownMapArray, _) => DevicesParserState::UnknownMapArray,    // Ignore unknown values in unknown map/array.
385            (state, json_node) => {
386                let mut error = UnexpectedNodeError::new();
387                write!(&mut error, "{:?}", (state, json_node)).ok();
388                return Err(ModelNodeParseError::UnexpectedNode(error));
389            }
390        };
391        state = new_state;
392        Ok(ParserCallbackAction::Nothing)
393    })? {}
394    Ok(())
395}
396
397#[cfg(test)]
398mod test {
399    use fuga_json_seq_parser::BufferReader;
400    use uuid::uuid;
401
402    use super::*;
403
404    fn create_reader<'a>(input: &'a str) -> (usize, BufferReader<'a>) {
405        let total_length = input.as_bytes().len();
406        (total_length, BufferReader::new(input.as_bytes()))
407    }
408
409    #[test]
410    fn test_parse_mac_address_colon() {
411        let parsed = MacAddress::from_str("f0:08:d1:00:11:22").unwrap();
412        assert_eq!(parsed, MacAddress([0xf0, 0x08, 0xd1, 0x00, 0x11, 0x22]));
413    }
414    #[test]
415    fn test_parse_mac_address_hyphen() {
416        let parsed = MacAddress::from_str("f0-08-d1-00-11-22").unwrap();
417        assert_eq!(parsed, MacAddress([0xf0, 0x08, 0xd1, 0x00, 0x11, 0x22]));
418    }
419
420    #[test]
421    fn test_parse_empty_devices() {
422        let (length, mut reader) = create_reader(
423            "
424        [
425        ]
426        ",
427        );
428        read_devices(&mut reader, Some(length), &ParserOptions::default(), |_device, _sub_node| {
429            panic!("callback must not be called for empty devices.");
430        })
431        .unwrap();
432    }
433    #[test]
434    fn test_parse_devices() {
435        let (length, mut reader) = create_reader(include_str!("../data/devices.json"));
436        let expected_devices = [
437            Device {
438                name: String::from("test remo device hoge"),
439                id: uuid!("f262cb0c-a853-47bb-9559-44d0f2c4d6e2"),
440                created_at: Timestamp::from_str("2022-10-18T06:42:59Z").unwrap(),
441                updated_at: Timestamp::from_str("2022-10-19T05:22:28Z").unwrap(),
442                mac_address: MacAddress::from_str("e8:db:84:00:11:22").unwrap(),
443                bt_mac_address: MacAddress::from_str("e8:db:84:22:33:44").unwrap(),
444                serial_number: String::from("2B012345678901"),
445                firmware_version: String::from("Remo-mini/1.10.0"),
446                temperature_offset: -0.5,
447                humidity_offset: 1.5,
448            },
449            Device {
450                name: String::from("Remo"),
451                id: uuid!("12948215-568a-49ca-be45-c556e8140c56"),
452                created_at: Timestamp::from_str("2022-10-07T05:57:52Z").unwrap(),
453                updated_at: Timestamp::from_str("2022-10-07T05:57:52Z").unwrap(),
454                mac_address: MacAddress::from_str("24:6f:28:00:11:22").unwrap(),
455                bt_mac_address: MacAddress::from_str("24:6f:28:22:33:44").unwrap(),
456                serial_number: String::from("1W012345678901"),
457                firmware_version: String::from("Remo/1.10.0"),
458                temperature_offset: 1.0,
459                humidity_offset: 0.0,
460            },
461            Device {
462                name: String::from("Remo E lite"),
463                id: uuid!("b08bdb7b-a2ad-4c3c-88f6-68645ae98077"),
464                created_at: Timestamp::from_str("2022-08-22T05:51:50Z").unwrap(),
465                updated_at: Timestamp::from_str("2022-10-03T04:16:16Z").unwrap(),
466                mac_address: MacAddress::from_str("f0:08:d1:00:11:22").unwrap(),
467                bt_mac_address: MacAddress::from_str("f0:08:d1:22:33:44").unwrap(),
468                serial_number: String::from("4W012345678901"),
469                firmware_version: String::from("Remo-E-lite/1.7.4"),
470                temperature_offset: 0.0,
471                humidity_offset: 0.0,
472            },
473        ];
474        let mut expected_devices_iter = expected_devices.iter();
475        read_devices(
476            &mut reader,
477            Some(length),
478            &ParserOptions::default(),
479            |device, sub_node| match sub_node {
480                None => {
481                    let expected_device = expected_devices_iter.next();
482                    assert!(
483                        expected_device.is_some(),
484                        "Extra device returned from parser - {:?}",
485                        device
486                    );
487                    let expected_device = expected_device.unwrap();
488                    assert_eq!(device, expected_device, "Device mismatch.");
489                }
490                _ => {}
491            },
492        )
493        .unwrap();
494    }
495}