1use 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 }
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 _ => 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 (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 (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 (state, JsonNode::EndArray) if state.is_array_state() => {
185 state_stack.pop().ok_or(ModelNodeParseError::UnexpectedMapArrayEnd)?
186 },
187 (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), };
195 if !dont_invoke_callback {
196 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(); }
211 _ => {} }
213 map_state
214 }
215 (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 _ => {} }
255 }
256 AppliancesParserState::DeviceMap
257 }
258 (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 _ => {} }
276 }
277 AppliancesParserState::ApplianceMap
278 }
279 (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 _ => {} }
310 }
311 AppliancesParserState::ModelMap
312 }
313 (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 _ => {} }
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(_)) => { AppliancesParserState::UnknownMap }
347 (AppliancesParserState::UnknownArray, JsonNode::Value(_)) => { AppliancesParserState::UnknownArray }
350 (_, JsonNode::Value(_)) => { 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}