fuga_remo_api/
appliances.rs

1// Appliance model parser for Remo Cloud API.
2// Copyright 2022-2023 Kenta Ida 
3// SPDX-License-Identifier: MIT
4//
5use core::{str::FromStr};
6
7use heapless::{String, Vec};
8use fuga_json_seq_parser::{JsonScalarValue, ParserCallbackAction, JsonNode, JsonNumber};
9use fuga_json_seq_parser::Parser as JsonParser;
10use fuga_json_seq_parser::ParserError as JsonParserError;
11
12use uuid::Uuid;
13use crate::{config::*, Device};
14use crate::common_types::*;
15use crate::node_key::*;
16use crate::device::MacAddress;
17use crate::parser_options::{ParserOptions, copy_string_option};
18
19#[derive(Clone, Debug, Default, PartialEq)]
20pub struct Appliance {
21    pub id: Uuid,
22    pub type_: ApplianceType,
23    pub nickname: String<MAX_NICKNAME_LEN>,
24    pub image: String<MAX_IMAGE_LEN>,
25}
26
27#[derive(Clone, Debug, PartialEq)]
28pub enum ApplianceType {
29    AC,
30    TV,
31    Light,
32    IR,
33    SmartMeter,
34    ElectricWaterHeater,
35    PowerDistMeter,
36    EVCD,
37    SolarPower,
38    StorageBattery,
39    QrioLock,
40    MorninPlus,
41    //Custom(String<16>),
42}
43impl Default for ApplianceType {
44    fn default() -> Self {
45        Self::AC
46    }
47}
48
49impl<'a> TryFrom<&'a str> for ApplianceType {
50    type Error = ();
51    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
52        match s {
53            "AC" => Ok(Self::AC),
54            "TV" => Ok(Self::TV),
55            "LIGHT" => Ok(Self::Light),
56            "IR" => Ok(Self::IR),
57            "EL_SMART_METER" => Ok(Self::SmartMeter),
58            "EL_ELECTRIC_WATER_HEATER" => Ok(Self::ElectricWaterHeater),
59            "EL_POWER_DIST_METER" => Ok(Self::PowerDistMeter),
60            "EL_EVCD" => Ok(Self::EVCD),
61            "EL_SOLAR_POWER" => Ok(Self::SolarPower),
62            "EL_STORAGE_BATTERY" => Ok(Self::StorageBattery),
63            "QRIO_LOCK" => Ok(Self::QrioLock),
64            "MORNIN_PLUS" => Ok(Self::MorninPlus),
65            //s => Ok(Self::Custom(String::from_str(s).map_err(|_| ())?)),
66            _ => Err(()),
67        }
68    }
69}
70
71#[derive(Clone, Debug, Default, PartialEq)]
72pub struct EchonetLiteProperty {
73    pub name: String<MAX_ECHONET_LITE_NAME_LEN>,
74    pub epc: u32,
75    pub val: String<MAX_ECHONET_LITE_VALUE_LEN>,
76    pub updated_at: Timestamp,
77}
78
79#[derive(Clone, Debug, Default, PartialEq)]
80pub struct ApplianceModel {
81    pub id: Uuid,
82    pub country: String<MAX_COUNTRY_LEN>,
83    pub manufacturer: String<MAX_MANUFACTURER_LEN>,
84    pub remote_name: String<MAX_REMOTE_NAME_LEN>,
85    pub series: String<MAX_SERIES_LEN>,
86    pub name: String<MAX_MODEL_NAME_LEN>,
87    pub image: String<MAX_IMAGE_LEN>,
88}
89
90#[derive(Clone, Debug, PartialEq)]
91pub enum ApplianceSubNode {
92    Device(Device),
93    Model(ApplianceModel),
94    EchonetLiteProperty(EchonetLiteProperty),
95}
96
97type AppliancesParser = JsonParser<REQUIRED_APPLIANCES_PARSER_BUFFER_LEN, 10>;
98
99#[derive(Clone, Copy, Debug)]
100enum AppliancesParserState {
101    Start,
102    AppliancesArray,
103    ApplianceMap,
104    DeviceMap,
105    ModelMap,
106    SmartMeterMap,
107    EchonetLitePropertiesArray,
108    EchonetLitePropertyMap,
109    UnknownMap,
110    UnknownArray,
111}
112impl AppliancesParserState {
113    fn is_map_state(&self) -> bool {
114        match self {
115            Self::ApplianceMap => true,
116            Self::DeviceMap => true,
117            Self::ModelMap => true,
118            Self::SmartMeterMap => true,
119            Self::EchonetLitePropertyMap => true,
120            Self::UnknownMap => true,
121            _ => false,
122        }
123    }
124    fn is_array_state(&self) -> bool {
125        match self {
126            Self::AppliancesArray => true,
127            Self::EchonetLitePropertiesArray => true,
128            Self::UnknownArray => true,
129            _ => false,
130        }
131    }
132}
133
134pub fn read_appliances<R: embedded_io::blocking::Read, F>(
135    reader: &mut R,
136    total_length: Option<usize>,
137    options: &ParserOptions,
138    mut callback: F,
139) -> Result<(), JsonParserError<R::Error, ModelNodeParseError>>
140where
141    F: for<'a> FnMut(&'a Appliance, Option<&'a ApplianceSubNode>),
142{
143    let mut parser = AppliancesParser::new();
144    parser.set_bytes_remaining(total_length);
145    let mut appliance = Appliance::default();
146    let mut subnode = ApplianceSubNode::Device(Device::default());
147    let mut state = AppliancesParserState::Start;
148    let mut node_key = None;
149    let mut state_stack: Vec<AppliancesParserState, 10> = Vec::new();
150
151    while !parser.parse(reader, |node| {
152        let new_state = match (state, node) {
153            // Start array
154            (state, JsonNode::StartArray) => {
155                state_stack.push(state).map_err(|_| ModelNodeParseError::NodeTooDeep)?;
156                match (state, node_key.take()) {
157                    (AppliancesParserState::Start, _) => AppliancesParserState::AppliancesArray,
158                    (AppliancesParserState::SmartMeterMap, Some(ModelNodeKey::EchonetLiteProperties)) => AppliancesParserState::EchonetLitePropertiesArray,
159                    (_, _)=> AppliancesParserState::UnknownArray,
160                }
161            },
162            // Start map
163            (state, JsonNode::StartMap) => {
164                state_stack.push(state).map_err(|_| ModelNodeParseError::NodeTooDeep)?;
165                match (state, node_key.take()) {
166                    (AppliancesParserState::AppliancesArray, _) => AppliancesParserState::ApplianceMap,
167                    (AppliancesParserState::ApplianceMap, Some(ModelNodeKey::Device)) => {
168                        subnode = ApplianceSubNode::Device(Device::default());
169                        AppliancesParserState::DeviceMap
170                    },
171                    (AppliancesParserState::ApplianceMap, Some(ModelNodeKey::Model)) => {
172                        subnode = ApplianceSubNode::Model(ApplianceModel::default());
173                        AppliancesParserState::ModelMap
174                    },
175                    (AppliancesParserState::ApplianceMap, Some(ModelNodeKey::SmartMeter)) => AppliancesParserState::SmartMeterMap,
176                    (AppliancesParserState::EchonetLitePropertiesArray, _) => {
177                        subnode = ApplianceSubNode::EchonetLiteProperty(EchonetLiteProperty::default());
178                        AppliancesParserState::EchonetLitePropertyMap
179                    }
180                    (_, _)=> AppliancesParserState::UnknownMap,
181                }
182            },
183            // End array
184            (state, JsonNode::EndArray) if state.is_array_state() => {
185                state_stack.pop().ok_or(ModelNodeParseError::UnexpectedMapArrayEnd)?
186            },
187            // End map
188            (state, JsonNode::EndMap) if state.is_map_state() => {
189                let (dont_invoke_callback, is_subnode) = match state {
190                    AppliancesParserState::UnknownMap => (true, true),
191                    AppliancesParserState::SmartMeterMap => (true, true),
192                    AppliancesParserState::ApplianceMap => (false, false),
193                    _ => (false, true), // Appliance sub node
194                };
195                if !dont_invoke_callback {
196                    // Invoke callback
197                    if is_subnode {
198                        callback(&appliance, Some(&subnode));
199                    } else {
200                        callback(&appliance, None);
201
202                    }
203                }
204                state_stack.pop().ok_or(ModelNodeParseError::UnexpectedMapArrayEnd)?
205            },
206            (map_state, JsonNode::Key(key)) => {
207                match key {
208                    JsonScalarValue::String(key) => {
209                        node_key = ModelNodeKey::try_from(key).ok(); // Store key
210                    }
211                    _ => {} // Unknown key.
212                }
213                map_state
214            }
215            // Process map node for device.
216            (AppliancesParserState::DeviceMap, JsonNode::Value(value)) => {
217                let device = match subnode {
218                    ApplianceSubNode::Device(ref mut device) => device,
219                    _ => { return Err(ModelNodeParseError::UnexpectedParserState); },
220                };
221                if let Some(node_key) = node_key.take() {
222                    match (node_key, value) {
223                        (ModelNodeKey::Name, JsonScalarValue::String(s)) => {
224                            device.name = copy_string_option(s, options)?;
225                        }
226                        (ModelNodeKey::Id, JsonScalarValue::String(s)) => {
227                            device.id = Uuid::from_str(s)?
228                        }
229                        (ModelNodeKey::CreatedAt, JsonScalarValue::String(s)) => {
230                            device.created_at = Timestamp::from_str(s)?
231                        }
232                        (ModelNodeKey::UpdatedAt, JsonScalarValue::String(s)) => {
233                            device.updated_at = Timestamp::from_str(s)?
234                        }
235                        (ModelNodeKey::MacAddress, JsonScalarValue::String(s)) => {
236                            device.mac_address = MacAddress::from_str(s)?
237                        }
238                        (ModelNodeKey::BtMacAddress, JsonScalarValue::String(s)) => {
239                            device.bt_mac_address = MacAddress::from_str(s)?
240                        }
241                        (ModelNodeKey::SerialNumber, JsonScalarValue::String(s)) => {
242                            device.serial_number = copy_string_option(s, options)?;
243                        }
244                        (ModelNodeKey::FirmwareVersion, JsonScalarValue::String(s)) => {
245                            device.firmware_version = copy_string_option(s, options)?;
246                        }
247                        (ModelNodeKey::TemperatureOffset, JsonScalarValue::Number(n)) => {
248                            device.temperature_offset = n.into()
249                        }
250                        (ModelNodeKey::HumidityOffset, JsonScalarValue::Number(n)) => {
251                            device.humidity_offset = n.into()
252                        }
253                        _ => {} // Ignore unknown nodes.
254                    }
255                }
256                AppliancesParserState::DeviceMap
257            }
258            // Appliance map
259            (AppliancesParserState::ApplianceMap, JsonNode::Value(value)) => {
260                if let Some(node_key) = node_key.take() {
261                    match (node_key, value) {
262                        (ModelNodeKey::NickName, JsonScalarValue::String(s)) => {
263                            appliance.nickname = copy_string_option(s, options)?;
264                        }
265                        (ModelNodeKey::Id, JsonScalarValue::String(s)) => {
266                            appliance.id = Uuid::from_str(s)?
267                        }
268                        (ModelNodeKey::Type, JsonScalarValue::String(s)) => {
269                            appliance.type_ = ApplianceType::try_from(s).or(Err(ModelNodeParseError::UnexpectedEnumValue))?;
270                        }
271                        (ModelNodeKey::Image, JsonScalarValue::String(s)) => {
272                            appliance.image = copy_string_option(s, options)?;
273                        }
274                        _ => {} // Ignore unknown nodes.
275                    }
276                }
277                AppliancesParserState::ApplianceMap
278            }
279            // Model map
280            (AppliancesParserState::ModelMap, JsonNode::Value(value)) => {
281                let model = match subnode {
282                    ApplianceSubNode::Model(ref mut model) => model,
283                    _ => { return Err(ModelNodeParseError::UnexpectedParserState); },
284                };
285                if let Some(node_key) = node_key.take() {
286                    match (node_key, value) {
287                        (ModelNodeKey::Name, JsonScalarValue::String(s)) => {
288                            model.name = copy_string_option(s, options)?;
289                        }
290                        (ModelNodeKey::Id, JsonScalarValue::String(s)) => {
291                            model.id = Uuid::from_str(s)?
292                        }
293                        (ModelNodeKey::Country, JsonScalarValue::String(s)) => {
294                            model.country = copy_string_option(s, options)?;
295                        }
296                        (ModelNodeKey::Manufacturer, JsonScalarValue::String(s)) => {
297                            model.manufacturer = copy_string_option(s, options)?;
298                        }
299                        (ModelNodeKey::RemoteName, JsonScalarValue::String(s)) => {
300                            model.remote_name = copy_string_option(s, options)?;
301                        }
302                        (ModelNodeKey::Series, JsonScalarValue::String(s)) => {
303                            model.series = copy_string_option(s, options)?;
304                        }
305                        (ModelNodeKey::Image, JsonScalarValue::String(s)) => {
306                            model.image = copy_string_option(s, options)?;
307                        }
308                        _ => {} // Ignore unknown nodes.
309                    }
310                }
311                AppliancesParserState::ModelMap
312            }
313            // EchonetLite Property map
314            (AppliancesParserState::EchonetLitePropertyMap, JsonNode::Value(value)) => {
315                let property = match subnode {
316                    ApplianceSubNode::EchonetLiteProperty(ref mut property) => property,
317                    _ => { return Err(ModelNodeParseError::UnexpectedParserState); },
318                };
319                if let Some(node_key) = node_key.take() {
320                    match (node_key, value) {
321                        (ModelNodeKey::Name, JsonScalarValue::String(s)) => {
322                            property.name = copy_string_option(s, options)?;
323                        }
324                        (ModelNodeKey::Epc, JsonScalarValue::Number(JsonNumber::I32(n))) => {
325                            property.epc = n as u32;
326                        }
327                        (ModelNodeKey::Val, JsonScalarValue::String(s)) => {
328                            property.val = copy_string_option(s, options)?;
329                        }
330                        (ModelNodeKey::UpdatedAt, JsonScalarValue::String(s)) => {
331                            property.updated_at = Timestamp::from_str(s)?;
332                        }
333                        _ => {} // Ignore unknown nodes.
334                    }
335                }
336                AppliancesParserState::EchonetLitePropertyMap
337            }
338            (_, JsonNode::EndArray) => {
339                return Err(ModelNodeParseError::UnexpectedMapArrayEnd);
340            }
341            (_, JsonNode::EndMap) => {
342                return Err(ModelNodeParseError::UnexpectedMapArrayEnd);
343            }
344            (AppliancesParserState::UnknownMap, JsonNode::Value(_)) => {    // Unknown map value
345                AppliancesParserState::UnknownMap   // Ignore the value.
346            }
347            (AppliancesParserState::UnknownArray, JsonNode::Value(_)) => {    // Unknown map value
348                AppliancesParserState::UnknownArray   // Ignore the value.
349            }
350            (_, JsonNode::Value(_)) => {    // Unexpected value node
351                return Err(ModelNodeParseError::UnexpectedParserState);
352            }
353        };
354        state = new_state;
355        Ok(ParserCallbackAction::Nothing)
356    })? {}
357    Ok(())
358}
359
360#[cfg(test)]
361mod test {
362    use fuga_json_seq_parser::BufferReader;
363    use uuid::uuid;
364
365    use super::*;
366
367    fn create_reader<'a>(input: &'a str) -> (usize, BufferReader<'a>) {
368        let total_length = input.as_bytes().len();
369        (total_length, BufferReader::new(input.as_bytes()))
370    }
371
372    #[test]
373    fn test_parse_empty_appliances() {
374        let (length, mut reader) = create_reader(
375            "
376        [
377        ]
378        ",
379        );
380        read_appliances(&mut reader, Some(length), &ParserOptions::default(), |_appliance, _sub_node| {
381            panic!("callback must not be called for empty appliances.");
382        })
383        .unwrap();
384    }
385    #[test]
386    fn test_parse_appliances() {
387        let (length, mut reader) = create_reader(include_str!("../data/appliances.json"));
388        let expected_appliances = [
389            Appliance {
390                id: uuid!("84875896-9f1e-44df-9f49-7989352eeecf"),
391                type_: ApplianceType::AC,
392                nickname: String::from("てすとエアコン"),
393                image: String::from("ico_ac_1"),
394            },
395            Appliance {
396                id: uuid!("081c5163-ee9e-486e-ba4d-e86a16ea4c9b"),
397                type_: ApplianceType::SmartMeter,
398                nickname: String::from("スマートメーター"),
399                image: String::from("ico_smartmeter"),
400            },
401        ];
402        let expected_subnodes = [
403            ApplianceSubNode::Device(Device {
404                name: String::from("てすとりも"),
405                id: uuid!("8afdef94-43f7-4a16-b499-fbb6286f7438"),
406                created_at: Timestamp::from_str("2022-10-14T05:51:30Z").unwrap(),
407                updated_at: Timestamp::from_str("2022-10-15T02:15:00Z").unwrap(),
408                mac_address: MacAddress::from_str("c8:2b:96:00:11:22").unwrap(),
409                bt_mac_address: MacAddress::from_str("c8:2b:96:33:44:55").unwrap(),
410                serial_number: String::from("1W300000000000"),
411                firmware_version: String::from("Remo/1.9.9"),
412                temperature_offset: 0.0,
413                humidity_offset: 0.0,
414            }),
415            ApplianceSubNode::Model(ApplianceModel {
416                id: uuid!("2a556fb6-f64b-4bd2-a911-610aa68dfc05"),
417                country: String::from("JP"),
418                manufacturer: String::from("sharp"),
419                remote_name: String::from("a986jb"),
420                series: String::from(""),
421                name: String::from("Sharp AC 033"),
422                image: String::from("ico_ac_1"),
423            }),
424            ApplianceSubNode::Device(Device {
425                name: String::from("Remo E lite"),
426                id: uuid!("159c34f6-d99a-46ca-a50a-3440ba7f8c8e"),
427                created_at: Timestamp::from_str("2022-10-08T07:49:56Z").unwrap(),
428                updated_at: Timestamp::from_str("2022-10-08T07:52:43Z").unwrap(),
429                mac_address: MacAddress::from_str("34:ab:95:00:11:22").unwrap(),
430                bt_mac_address: MacAddress::from_str("34:ab:95:33:44:55").unwrap(),
431                serial_number: String::from("4W012345678901"),
432                firmware_version: String::from("Remo-E-lite/1.7.2"),
433                temperature_offset: 0.0,
434                humidity_offset: 0.0,
435            }),
436            ApplianceSubNode::Model(ApplianceModel {
437                id: uuid!("1eb17958-9a47-4000-8b9d-b3dffaf9616c"),
438                country: String::from(""),
439                manufacturer: String::from(""),
440                remote_name: String::from(""),
441                series: String::from(""),
442                name: String::from("Smart Meter"),
443                image: String::from("ico_smartmeter"),
444            }),
445            ApplianceSubNode::EchonetLiteProperty(EchonetLiteProperty {
446                name: String::from("cumulative_electric_energy_effective_digits"),
447                epc: 215,
448                val: String::from("7"),
449                updated_at: Timestamp::from_str("2022-10-22T11:38:14Z").unwrap(),
450            }),
451            ApplianceSubNode::EchonetLiteProperty(EchonetLiteProperty {
452                name: String::from("normal_direction_cumulative_electric_energy"),
453                epc: 224,
454                val: String::from("1097158"),
455                updated_at: Timestamp::from_str("2022-10-22T11:38:14Z").unwrap(),
456            }),
457            ApplianceSubNode::EchonetLiteProperty(EchonetLiteProperty {
458                name: String::from("cumulative_electric_energy_unit"),
459                epc: 225,
460                val: String::from("2"),
461                updated_at: Timestamp::from_str("2022-10-22T11:38:14Z").unwrap(),
462            }),
463            ApplianceSubNode::EchonetLiteProperty(EchonetLiteProperty {
464                name: String::from("measured_instantaneous"),
465                epc: 231,
466                val: String::from("397"),
467                updated_at: Timestamp::from_str("2022-10-22T11:38:14Z").unwrap(),
468            }),
469        ];
470        let mut expected_appliances_iter = expected_appliances.iter();
471        let mut expected_subnodes_iter = expected_subnodes.iter();
472        read_appliances(
473            &mut reader,
474            Some(length),
475            &ParserOptions::default(),
476            |appliance, sub_node| match sub_node {
477                None => {
478                    let expected_appliance = expected_appliances_iter.next();
479                    assert!(
480                        expected_appliance.is_some(),
481                        "Extra appliance returned from parser - {:?}",
482                        appliance
483                    );
484                    let expected_appliance = expected_appliance.unwrap();
485                    assert_eq!(appliance, expected_appliance, "Appliance mismatch.");
486                }
487                Some(sub_node) => {
488                    let expected_subnode = expected_subnodes_iter.next();
489                    assert!(
490                        expected_subnode.is_some(),
491                        "Extra appliance subnode returned from parser - {:?}",
492                        sub_node
493                    );
494                    let expected_subnode = expected_subnode.unwrap();
495                    assert_eq!(sub_node, expected_subnode, "Subnode mismatch.");
496                }
497            },
498        )
499        .unwrap();
500    }
501}