echonet_lite/object/
mod.rs

1use crate::{deserialize, ElPacket, Properties};
2use core::fmt::{self, Formatter};
3pub use property_maps::*;
4use serde::{de::Visitor, ser::SerializeTuple, Deserialize, Serialize};
5
6mod property_maps;
7
8/// Packet specified to an ECHONET class.
9pub enum ClassPacket {
10    /// Any unimplemented class fallback
11    Unimplemented(UnimplementedPacket),
12    /// House hold solar power class packet
13    SolarPower(SolarPowerPacket),
14    /// Storage battery class packet
15    StorageBattery(StorageBatteryPacket),
16    /// Electric vehicle charger/discharger class packet
17    Evps(EvpsPacket),
18    /// Heat pump
19    Hp(HpPacket),
20    /// Smart Meter class packet
21    SmartMeter(SmartMeterPacket),
22    /// Home Air Conditioner class packet
23    AirConditioner(AirConditionerPacket),
24    /// Power Distribution Board Metering class packet
25    Metering(MeteringPacket),
26    /// Fuel Cell class packet
27    FuelCell(FuelCellPacket),
28    /// Instantaneous Water Heater class packet
29    InstantaneousWaterHeater(InstantaneousWaterHeaterPacket),
30    /// General Lighting class packet
31    GeneralLighting(GeneralLightingPacket),
32    /// Mono Function Lighting class packet
33    MonoFunctionLighting(MonoFunctionLightingPacket),
34    /// Lighting System class packet
35    LightingSystem(LightingSystemPacket),
36    /// Node profile class packet
37    Profile(ProfilePacket),
38    /// Controller class
39    Controller(ControllerPacket),
40}
41
42impl ClassPacket {
43    pub fn new(eoj: EchonetObject, props: Properties) -> ClassPacket {
44        match eoj.class {
45            ClassCode(code::HOUSEHOLD_SOLAR_POWER) => {
46                ClassPacket::SolarPower(SolarPowerPacket(props))
47            }
48            ClassCode(code::STORAGE_BATTERY) => {
49                ClassPacket::StorageBattery(StorageBatteryPacket(props))
50            }
51            ClassCode(code::EVPS) => ClassPacket::Evps(EvpsPacket(props)),
52            ClassCode(code::HP) => ClassPacket::Hp(HpPacket(props)),
53            ClassCode(code::SMART_METER) => ClassPacket::SmartMeter(SmartMeterPacket(props)),
54            ClassCode(code::HOME_AIR_CONDITIONER) => {
55                ClassPacket::AirConditioner(AirConditionerPacket(props))
56            }
57            ClassCode(code::POWER_DISTRIBUTION_BOARD_METERING) => {
58                ClassPacket::Metering(MeteringPacket(props))
59            }
60            ClassCode(code::FUEL_CELL) => ClassPacket::FuelCell(FuelCellPacket(props)),
61            ClassCode(code::INSTANTANEOUS_WATER_HEATER) => {
62                ClassPacket::InstantaneousWaterHeater(InstantaneousWaterHeaterPacket(props))
63            }
64            ClassCode(code::GENERAL_LIGHTING) => {
65                ClassPacket::GeneralLighting(GeneralLightingPacket(props))
66            }
67            ClassCode(code::MONO_FUNCTION_LIGHTING) => {
68                ClassPacket::MonoFunctionLighting(MonoFunctionLightingPacket(props))
69            }
70            ClassCode(code::LIGHTING_SYSTEM) => {
71                ClassPacket::LightingSystem(LightingSystemPacket(props))
72            }
73            ClassCode(code::PROFILE) => ClassPacket::Profile(ProfilePacket(props)),
74            ClassCode(code::CONTROLLER) => ClassPacket::Controller(ControllerPacket(props)),
75            _ => ClassPacket::Unimplemented(UnimplementedPacket(eoj.class, props)),
76        }
77    }
78
79    /// fetches the properties for this ClassPacket, when appropriate.
80    pub fn properties(&self) -> &Properties {
81        match self {
82            Self::Unimplemented(p) => p.properties(),
83            Self::SolarPower(p) => p.properties(),
84            Self::StorageBattery(p) => p.properties(),
85            Self::Evps(p) => p.properties(),
86            Self::Hp(p) => p.properties(),
87            Self::SmartMeter(p) => p.properties(),
88            Self::AirConditioner(p) => p.properties(),
89            Self::Metering(p) => p.properties(),
90            Self::FuelCell(p) => p.properties(),
91            Self::InstantaneousWaterHeater(p) => p.properties(),
92            Self::GeneralLighting(p) => p.properties(),
93            Self::MonoFunctionLighting(p) => p.properties(),
94            Self::LightingSystem(p) => p.properties(),
95            Self::Profile(p) => p.properties(),
96            Self::Controller(p) => p.properties(),
97        }
98    }
99}
100
101impl From<ElPacket> for ClassPacket {
102    fn from(value: ElPacket) -> Self {
103        match value.seoj.class {
104            ClassCode(code::HOUSEHOLD_SOLAR_POWER) => ClassPacket::SolarPower(value.into()),
105            ClassCode(code::STORAGE_BATTERY) => ClassPacket::StorageBattery(value.into()),
106            ClassCode(code::EVPS) => ClassPacket::Evps(value.into()),
107            ClassCode(code::HP) => ClassPacket::Hp(value.into()),
108            ClassCode(code::SMART_METER) => ClassPacket::SmartMeter(value.into()),
109            ClassCode(code::HOME_AIR_CONDITIONER) => ClassPacket::AirConditioner(value.into()),
110            ClassCode(code::POWER_DISTRIBUTION_BOARD_METERING) => {
111                ClassPacket::Metering(value.into())
112            }
113            ClassCode(code::FUEL_CELL) => ClassPacket::FuelCell(value.into()),
114            ClassCode(code::INSTANTANEOUS_WATER_HEATER) => {
115                ClassPacket::InstantaneousWaterHeater(value.into())
116            }
117            ClassCode(code::GENERAL_LIGHTING) => ClassPacket::GeneralLighting(value.into()),
118            ClassCode(code::MONO_FUNCTION_LIGHTING) => {
119                ClassPacket::MonoFunctionLighting(value.into())
120            }
121            ClassCode(code::LIGHTING_SYSTEM) => ClassPacket::LightingSystem(value.into()),
122            ClassCode(code::PROFILE) => ClassPacket::Profile(value.into()),
123            ClassCode(code::CONTROLLER) => ClassPacket::Controller(value.into()),
124            _ => ClassPacket::Unimplemented(value.into()),
125        }
126    }
127}
128
129impl fmt::Display for ClassPacket {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        match self {
132            ClassPacket::SolarPower(v) => write!(f, "{v}")?,
133            ClassPacket::StorageBattery(v) => write!(f, "{v}")?,
134            ClassPacket::Evps(v) => write!(f, "{v}")?,
135            ClassPacket::Hp(v) => write!(f, "{v}")?,
136            ClassPacket::SmartMeter(v) => write!(f, "{v}")?,
137            ClassPacket::AirConditioner(v) => write!(f, "{v}")?,
138            ClassPacket::Metering(v) => write!(f, "{v}")?,
139            ClassPacket::FuelCell(v) => write!(f, "{v}")?,
140            ClassPacket::InstantaneousWaterHeater(v) => write!(f, "{v}")?,
141            ClassPacket::GeneralLighting(v) => write!(f, "{v}")?,
142            ClassPacket::MonoFunctionLighting(v) => write!(f, "{v}")?,
143            ClassPacket::LightingSystem(v) => write!(f, "{v}")?,
144            ClassPacket::Profile(v) => write!(f, "{v}")?,
145            ClassPacket::Controller(v) => write!(f, "{v}")?,
146            ClassPacket::Unimplemented(v) => write!(f, "{v}")?,
147        }
148        Ok(())
149    }
150}
151
152pub mod code {
153    pub const HOME_AIR_CONDITIONER: [u8; 2] = [0x01, 0x30];
154    pub const INSTANTANEOUS_WATER_HEATER: [u8; 2] = [0x02, 0x72];
155    pub const HOUSEHOLD_SOLAR_POWER: [u8; 2] = [0x02, 0x79];
156    pub const FUEL_CELL: [u8; 2] = [0x02, 0x7C];
157    pub const STORAGE_BATTERY: [u8; 2] = [0x02, 0x7D];
158    pub const EVPS: [u8; 2] = [0x02, 0x7E];
159    pub const HP: [u8; 2] = [0x02, 0x6B];
160    pub const POWER_DISTRIBUTION_BOARD_METERING: [u8; 2] = [0x02, 0x87];
161    pub const SMART_METER: [u8; 2] = [0x02, 0x88];
162    pub const GENERAL_LIGHTING: [u8; 2] = [0x02, 0x90];
163    pub const MONO_FUNCTION_LIGHTING: [u8; 2] = [0x02, 0x91];
164    pub const LIGHTING_SYSTEM: [u8; 2] = [0x02, 0xA3];
165    pub const CONTROLLER: [u8; 2] = [0x05, 0xFF];
166    pub const PROFILE: [u8; 2] = [0x0E, 0xF0];
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
170struct ClassCode([u8; 2]);
171
172impl From<[u8; 2]> for ClassCode {
173    fn from(value: [u8; 2]) -> Self {
174        Self(value)
175    }
176}
177
178impl fmt::Display for ClassCode {
179    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
180        write!(f, "{:02X} {:02X}", self.0[0], self.0[1])
181    }
182}
183
184pub struct UnimplementedPacket(ClassCode, Properties);
185
186impl UnimplementedPacket {
187    pub fn properties(&self) -> &Properties {
188        &self.1
189    }
190}
191
192impl fmt::Display for UnimplementedPacket {
193    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
194        writeln!(f, "Unimplemented Class: {}", self.0)?;
195        for prop in self.1.iter() {
196            if let Some(name) = SUPER_CLASS.get(&prop.epc) {
197                writeln!(f, "{prop}\t\t[{name}]")?;
198                continue;
199            }
200            writeln!(f, "{prop}\t\t[unknown]")?;
201        }
202        Ok(())
203    }
204}
205
206impl From<ElPacket> for UnimplementedPacket {
207    fn from(value: ElPacket) -> Self {
208        UnimplementedPacket(value.seoj.class, value.props)
209    }
210}
211
212macro_rules! convert_packet {
213    ( $code:expr, $ty:ty, $class:expr, $class_desc:expr) => {
214        impl $ty {
215            #[allow(dead_code)]
216            const CODE: [u8; 2] = $code;
217
218            /// Gets the properties used to construct this ClassPacket
219            pub fn properties(&self) -> &Properties {
220                &self.0
221            }
222        }
223
224        impl fmt::Display for $ty {
225            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
226                writeln!(
227                    f,
228                    "{}: 0x{:02X}{:02X}",
229                    $class_desc,
230                    Self::CODE[0],
231                    Self::CODE[1]
232                )?;
233                for prop in self.0.iter() {
234                    if let Some(name) = SUPER_CLASS.get(&prop.epc) {
235                        writeln!(f, "{prop}\t\t[{name}]")?;
236                        continue;
237                    }
238                    if let Some(name) = $class.get(&prop.epc) {
239                        writeln!(f, "{prop}\t\t[{name}]")?;
240                        continue;
241                    }
242                    writeln!(f, "{prop}\t\t[unknown]")?;
243                }
244                Ok(())
245            }
246        }
247
248        impl From<ElPacket> for $ty {
249            fn from(value: ElPacket) -> Self {
250                if value.seoj.class != ClassCode(Self::CODE) {
251                    panic!("invalid source object class.")
252                }
253                Self(value.props)
254            }
255        }
256    };
257}
258
259pub struct SmartMeterPacket(Properties);
260convert_packet!(
261    code::SMART_METER,
262    SmartMeterPacket,
263    SMART_METER_CLASS,
264    "Smart Meter"
265);
266
267pub struct SolarPowerPacket(Properties);
268convert_packet!(
269    code::HOUSEHOLD_SOLAR_POWER,
270    SolarPowerPacket,
271    HOUSEHOLD_SOLAR_POWER_CLASS,
272    "House Hold Solar Power"
273);
274pub struct StorageBatteryPacket(Properties);
275convert_packet!(
276    code::STORAGE_BATTERY,
277    StorageBatteryPacket,
278    STORAGE_BATTERY_CLASS,
279    "Storage Battery"
280);
281
282pub struct EvpsPacket(Properties);
283convert_packet!(code::EVPS, EvpsPacket, EVPS_CLASS, "EVPS");
284
285pub struct HpPacket(Properties);
286convert_packet!(code::HP, HpPacket, HP_CLASS, "HP");
287
288pub struct AirConditionerPacket(Properties);
289convert_packet!(
290    code::HOME_AIR_CONDITIONER,
291    AirConditionerPacket,
292    HOME_AIR_CONDITIONER_CLASS,
293    "Home Air Conditioner"
294);
295
296pub struct MeteringPacket(Properties);
297convert_packet!(
298    code::POWER_DISTRIBUTION_BOARD_METERING,
299    MeteringPacket,
300    POWER_DISTRIBUTION_BOARD_METERING_CLASS,
301    "Power Distribution Board Metering"
302);
303
304pub struct FuelCellPacket(Properties);
305convert_packet!(
306    code::FUEL_CELL,
307    FuelCellPacket,
308    FUEL_CELL_CLASS,
309    "Fuel Cell"
310);
311
312pub struct InstantaneousWaterHeaterPacket(Properties);
313convert_packet!(
314    code::INSTANTANEOUS_WATER_HEATER,
315    InstantaneousWaterHeaterPacket,
316    INSTANTANEOUS_WATER_HEATER_CLASS,
317    "Instantaneous Water Heater"
318);
319
320pub struct GeneralLightingPacket(Properties);
321convert_packet!(
322    code::GENERAL_LIGHTING,
323    GeneralLightingPacket,
324    GENERAL_LIGHTING_CLASS,
325    "General Lighting"
326);
327
328pub struct MonoFunctionLightingPacket(Properties);
329convert_packet!(
330    code::MONO_FUNCTION_LIGHTING,
331    MonoFunctionLightingPacket,
332    MONO_FUNCTION_LIGHTING_CLASS,
333    "Mono Function Lighting"
334);
335
336pub struct LightingSystemPacket(Properties);
337convert_packet!(
338    code::LIGHTING_SYSTEM,
339    LightingSystemPacket,
340    LIGHTING_SYSTEM_CLASS,
341    "Lighting System"
342);
343
344pub struct ProfilePacket(Properties);
345convert_packet!(code::PROFILE, ProfilePacket, PROFILE_CLASS, "Node Profile");
346
347pub struct ControllerPacket(Properties);
348convert_packet!(
349    code::CONTROLLER,
350    ControllerPacket,
351    CONTROLLER_CLASS,
352    "Controller"
353);
354
355pub struct Controller;
356impl Controller {
357    #[allow(dead_code)]
358    const CODE: [u8; 2] = code::CONTROLLER;
359}
360
361impl fmt::Display for Controller {
362    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
363        write!(
364            f,
365            "Controller: 0x{:02X}{:02X}",
366            Self::CODE[0],
367            Self::CODE[1]
368        )
369    }
370}
371
372/// An ECHONET object.
373///
374/// ECHONET objects are described using the formats [X1.X2] and [X3].
375/// - X1: Class group code
376/// - X2: Class code
377/// - X3: Instance code
378#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
379pub struct EchonetObject {
380    // TODO: use `ElClass` instead.
381    class: ClassCode,
382    instance: u8,
383}
384
385impl From<[u8; 3]> for EchonetObject {
386    fn from(eobj: [u8; 3]) -> Self {
387        Self {
388            class: ClassCode([eobj[0], eobj[1]]),
389            instance: eobj[2],
390        }
391    }
392}
393
394impl From<(ElClass, u8)> for EchonetObject {
395    fn from(value: (ElClass, u8)) -> Self {
396        let (class, instance) = value;
397        use code::*;
398        match class {
399            ElClass::HomeAC => Self {
400                class: HOME_AIR_CONDITIONER.into(),
401                instance,
402            },
403            ElClass::Hp => Self {
404                class: HP.into(),
405                instance,
406            },
407            ElClass::InstantaneousWaterHeater => Self {
408                class: INSTANTANEOUS_WATER_HEATER.into(),
409                instance,
410            },
411            ElClass::Pv => Self {
412                class: HOUSEHOLD_SOLAR_POWER.into(),
413                instance,
414            },
415            ElClass::FuelCell => Self {
416                class: FUEL_CELL.into(),
417                instance,
418            },
419            ElClass::Battery => Self {
420                class: STORAGE_BATTERY.into(),
421                instance,
422            },
423            ElClass::Evps => Self {
424                class: EVPS.into(),
425                instance,
426            },
427            ElClass::Metering => Self {
428                class: POWER_DISTRIBUTION_BOARD_METERING.into(),
429                instance,
430            },
431            ElClass::SmartMeter => Self {
432                class: SMART_METER.into(),
433                instance,
434            },
435            ElClass::MultiInputPCS => Self {
436                class: [0x02, 0xA5].into(),
437                instance,
438            },
439            ElClass::GeneralLighting => Self {
440                class: GENERAL_LIGHTING.into(),
441                instance,
442            },
443            ElClass::MonoFunctionLighting => Self {
444                class: MONO_FUNCTION_LIGHTING.into(),
445                instance,
446            },
447            ElClass::LightingSystem => Self {
448                class: LIGHTING_SYSTEM.into(),
449                instance,
450            },
451            ElClass::Controller => Self {
452                class: CONTROLLER.into(),
453                instance,
454            },
455            ElClass::Profile => Self {
456                class: PROFILE.into(),
457                instance,
458            },
459            ElClass::Unknown(code) => Self {
460                class: code.into(),
461                instance,
462            },
463        }
464    }
465}
466
467impl TryFrom<&[u8]> for EchonetObject {
468    type Error = crate::error::Error;
469
470    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
471        let (_, eobj) = deserialize(value)?;
472        Ok(eobj)
473    }
474}
475
476impl fmt::Display for EchonetObject {
477    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
478        use code::*;
479        let class = match self.class.0 {
480            HOME_AIR_CONDITIONER => "Home AC",
481            HP => "Heat pump",
482            INSTANTANEOUS_WATER_HEATER => "Instantaneous water heater",
483            HOUSEHOLD_SOLAR_POWER => "Household solar power",
484            FUEL_CELL => "Fuel cell",
485            STORAGE_BATTERY => "Storage battery",
486            EVPS => "V2H",
487            POWER_DISTRIBUTION_BOARD_METERING => "Power distribution Metering",
488            SMART_METER => "Smart meter",
489            GENERAL_LIGHTING => "General lighting",
490            MONO_FUNCTION_LIGHTING => "Mono function lighting",
491            LIGHTING_SYSTEM => "Lighting system",
492            CONTROLLER => "Controller",
493            PROFILE => "Profile",
494            _ => "Unknown",
495        };
496        write!(f, "{} [{} {:02X}]", class, self.class, self.instance)
497    }
498}
499
500/// echonet-lite class representation.
501#[derive(Debug, PartialEq, Eq, Copy, Clone)]
502pub enum ElClass {
503    HomeAC,
504    Hp,
505    InstantaneousWaterHeater,
506    Pv,
507    FuelCell,
508    Battery,
509    Evps,
510    Metering,
511    SmartMeter,
512    MultiInputPCS,
513    GeneralLighting,
514    MonoFunctionLighting,
515    LightingSystem,
516    Controller,
517    Profile,
518    Unknown([u8; 2]),
519}
520
521impl Serialize for ElClass {
522    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
523    where
524        S: serde::Serializer,
525    {
526        let raw = Into::<[u8; 2]>::into(*self);
527        let mut seq = serializer.serialize_tuple(2)?;
528        seq.serialize_element(&raw[0])?;
529        seq.serialize_element(&raw[1])?;
530        seq.end()
531    }
532}
533
534struct ElClassVisitor;
535impl<'de> Visitor<'de> for ElClassVisitor {
536    type Value = (u8, u8);
537
538    fn expecting(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
539        formatter.write_str("never failed")
540    }
541
542    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
543    where
544        A: serde::de::SeqAccess<'de>,
545    {
546        let group: u8 = seq.next_element()?.unwrap();
547        let class: u8 = seq.next_element()?.unwrap();
548        Ok((group, class))
549    }
550}
551
552impl<'de> Deserialize<'de> for ElClass {
553    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
554    where
555        D: serde::Deserializer<'de>,
556    {
557        let (group, class) = deserializer.deserialize_tuple(2, ElClassVisitor)?;
558        Ok(ElClass::from(&[group, class]))
559    }
560}
561
562impl From<&[u8; 2]> for ElClass {
563    fn from(value: &[u8; 2]) -> Self {
564        use code::*;
565        use ElClass::*;
566        match *value {
567            HOME_AIR_CONDITIONER => HomeAC,
568            HP => Hp,
569            INSTANTANEOUS_WATER_HEATER => InstantaneousWaterHeater,
570            HOUSEHOLD_SOLAR_POWER => Pv,
571            FUEL_CELL => FuelCell,
572            STORAGE_BATTERY => Battery,
573            EVPS => Evps,
574            POWER_DISTRIBUTION_BOARD_METERING => Metering,
575            SMART_METER => SmartMeter,
576            [0x02, 0xA5] => MultiInputPCS,
577            GENERAL_LIGHTING => GeneralLighting,
578            MONO_FUNCTION_LIGHTING => MonoFunctionLighting,
579            LIGHTING_SYSTEM => LightingSystem,
580            CONTROLLER => Controller,
581            PROFILE => Profile,
582            _ => Unknown(*value),
583        }
584    }
585}
586
587#[allow(clippy::from_over_into)]
588impl Into<[u8; 2]> for ElClass {
589    fn into(self) -> [u8; 2] {
590        use code::*;
591        use ElClass::*;
592        match self {
593            HomeAC => HOME_AIR_CONDITIONER,
594            Hp => HP,
595            InstantaneousWaterHeater => INSTANTANEOUS_WATER_HEATER,
596            Pv => HOUSEHOLD_SOLAR_POWER,
597            FuelCell => FUEL_CELL,
598            Battery => STORAGE_BATTERY,
599            Evps => EVPS,
600            Metering => POWER_DISTRIBUTION_BOARD_METERING,
601            SmartMeter => SMART_METER,
602            MultiInputPCS => [0x02, 0xA5],
603            GeneralLighting => GENERAL_LIGHTING,
604            MonoFunctionLighting => MONO_FUNCTION_LIGHTING,
605            LightingSystem => LIGHTING_SYSTEM,
606            Controller => CONTROLLER,
607            Profile => PROFILE,
608            Unknown(raw) => raw,
609        }
610    }
611}
612
613impl From<EchonetObject> for ElClass {
614    fn from(value: EchonetObject) -> Self {
615        use code::*;
616        use ElClass::*;
617        match value.class.0 {
618            HOME_AIR_CONDITIONER => HomeAC,
619            HP => Hp,
620            INSTANTANEOUS_WATER_HEATER => InstantaneousWaterHeater,
621            HOUSEHOLD_SOLAR_POWER => Pv,
622            FUEL_CELL => FuelCell,
623            STORAGE_BATTERY => Battery,
624            EVPS => Evps,
625            POWER_DISTRIBUTION_BOARD_METERING => Metering,
626            SMART_METER => SmartMeter,
627            [0x02, 0xA5] => MultiInputPCS,
628            GENERAL_LIGHTING => GeneralLighting,
629            MONO_FUNCTION_LIGHTING => MonoFunctionLighting,
630            LIGHTING_SYSTEM => LightingSystem,
631            CONTROLLER => Controller,
632            PROFILE => Profile,
633            _ => Unknown(value.class.0),
634        }
635    }
636}
637
638#[cfg(test)]
639mod test {
640    use super::*;
641
642    #[test]
643    fn to_elclass() {
644        let eobj = EchonetObject::from([0x01, 0x30, 0x01]);
645        let class = ElClass::from(eobj);
646        assert_eq!(class, ElClass::HomeAC);
647
648        let raw = [0x01u8, 0x30u8];
649        let class = ElClass::from(&raw);
650        assert_eq!(class, ElClass::HomeAC);
651    }
652
653    #[test]
654    fn to_echonet_object() {
655        let class = ElClass::HomeAC;
656        let eobj: EchonetObject = (class, 1u8).into();
657        assert_eq!(
658            eobj,
659            EchonetObject {
660                class: ClassCode([0x01, 0x30]),
661                instance: 1
662            }
663        );
664    }
665
666    #[test]
667    fn serialize_el_class() {
668        let class = ElClass::HomeAC;
669        let bytes = crate::ser::serialize(&class).unwrap();
670        assert_eq!(bytes, vec![0x01, 0x30]);
671    }
672
673    #[test]
674    fn deserialize_el_class() {
675        let input = [0x01, 0x30];
676        let (bytes_read, class): (usize, ElClass) = deserialize(&input).unwrap();
677        assert_eq!(bytes_read, 2);
678        assert_eq!(class, ElClass::HomeAC);
679    }
680}