echonet_lite/
el_packet.rs

1#![allow(dead_code)]
2use crate::lib::fmt;
3use crate::lib::ops::{Deref, DerefMut};
4use crate::lib::vec::Vec;
5use num_derive::FromPrimitive;
6use serde::{Deserialize, Serialize};
7use serde_repr::{Deserialize_repr, Serialize_repr};
8
9pub use crate::object::EchonetObject;
10use crate::{de, ser, Error};
11
12/// An ECHONET Lite packet representation.
13///
14/// ECHONET Lite SPEC shows an ECHONET Lite packet contains
15/// - EHD1: ECHONET Lite message header1 (1-byte)
16/// - EHD2: ECHONET Lite message header2 (1-byte)
17/// - SEOJ: Source ECHONET Lite object specification (3-byte)
18/// - DEOJ: Destination ECHONET Lite object specification (3-byte)
19/// - ESV: ECHONET Lite service
20/// - OPC: Number of processing properties
21/// - (EPC, PDC, EDT) * OPC
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub struct ElPacket {
24    // ECHONTE Lite header must be 0x1081
25    ehd1: u8,
26    ehd2: u8,
27    // unique ID for each packet
28    transaction_id: u16,
29    // source ECHONET object
30    pub seoj: EchonetObject,
31    // destination ECHONET object
32    pub deoj: EchonetObject,
33    // ECHONET service code
34    pub esv: ServiceCode,
35    // properties contain opc (Operation count), epc (ECHONET property code), and
36    // edt (ECHONET data).
37    pub props: Properties,
38}
39
40impl ElPacket {
41    /// Serializes an ECHONET Lite packet into byte array.
42    pub fn serialize(&self) -> Result<Vec<u8>, Error> {
43        ser::serialize(&self)
44    }
45
46    /// Deserializes an ECHONET Lite packet from byte array.
47    pub fn from_bytes(bytes: &[u8]) -> Result<(usize, ElPacket), Error> {
48        de::deserialize(bytes)
49    }
50
51    /// Returns whether `self` is a response for the `req`.
52    #[allow(clippy::suspicious_operation_groupings)]
53    pub fn is_response_for(&self, req: &ElPacket) -> bool {
54        self.transaction_id == req.transaction_id && self.seoj == req.deoj
55    }
56
57    /// Creates a new response for itself.
58    ///
59    /// `esv` must be one of response service code.
60    /// `props` contains all response properties.
61    ///
62    /// The created response packet has the same transaction ID as original packet.
63    /// The source and the destination are reversed.
64    pub fn create_response(&self, esv: ServiceCode, props: Properties) -> ElPacket {
65        ElPacketBuilder::new()
66            .transaction_id(self.transaction_id)
67            .seoj(self.deoj)
68            .deoj(self.seoj)
69            .esv(esv)
70            .props(props)
71            .build()
72    }
73}
74
75impl fmt::Display for ElPacket {
76    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77        writeln!(f, "EHD: {:02X}{:02X}", self.ehd1, self.ehd2)?;
78        writeln!(f, "TID: {}", self.transaction_id)?;
79        writeln!(f, "SEOJ: {}", self.seoj)?;
80        writeln!(f, "DEOJ: {}", self.deoj)?;
81        writeln!(f, "{}", self.esv)?;
82        write!(f, "{}", self.props)
83    }
84}
85
86/// Reperesents ECHONET LiteService (ESV).
87/// The service code specifies an operation for properties stipulated by the EPC.
88#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive, Serialize_repr, Deserialize_repr)]
89#[repr(u8)]
90pub enum ServiceCode {
91    /// A response for SetI; Property value write "response is not possible".
92    SetISNA = 0x50,
93    /// A response for SetC; Property value write "response is not possible".
94    SetCSNA = 0x51,
95    /// A response for Get; Property value read "response is not possible".
96    GetSNA = 0x52,
97    /// A response for InfReq; Property value notification "response is not possible".
98    InfSNA = 0x53,
99    /// A response for SetGet; Property value write & read request "response not possible".
100    SetGetSNA = 0x5E,
101    /// Property value write request (no response required). Broadcast possible.
102    SetI = 0x60,
103    /// Property value write request (response required). Broadcast possible.
104    SetC = 0x61,
105    /// Property value read request. Broadcast possible.
106    Get = 0x62,
107    /// Property value notification request. Broadcast possible.
108    InfReq = 0x63,
109    /// Property value read & write request. Broadcast possible.
110    SetGet = 0x6E,
111    /// An individual response for SetC; Property value write response.
112    SetRes = 0x71,
113    /// An individual response for Get; Property value read response.
114    GetRes = 0x72,
115    /// Property value notification. Both individual notification and broadcast notification.
116    Inf = 0x73,
117    /// Individual property value notification (response required).
118    InfC = 0x74,
119    /// An individual response for InfC; Property value notification response.
120    InfCRes = 0x7A,
121    /// An individual response for SetGet; Property value write & read response.
122    SetGetRes = 0x7E,
123}
124
125impl fmt::Display for ServiceCode {
126    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127        write!(f, "ESV: {:02X} ", *self as u8)?;
128        let esv = match self {
129            ServiceCode::SetISNA => "SetISNA",
130            ServiceCode::SetCSNA => "SetCSNA",
131            ServiceCode::GetSNA => "GetSNA",
132            ServiceCode::InfSNA => "InfSNA",
133            ServiceCode::SetGetSNA => "SetGetSNA",
134            ServiceCode::SetI => "SetI",
135            ServiceCode::SetC => "SetC",
136            ServiceCode::Get => "Get",
137            ServiceCode::InfReq => "InfReq",
138            ServiceCode::SetGet => "SetGet",
139            ServiceCode::SetRes => "SetRes",
140            ServiceCode::GetRes => "GetRes",
141            ServiceCode::Inf => "Inf",
142            ServiceCode::InfC => "InfC",
143            ServiceCode::InfCRes => "InfCRes",
144            ServiceCode::SetGetRes => "SetGetRes",
145        };
146        write!(f, "({esv})")
147    }
148}
149
150/// An ECHONET property array consists of `OPC, EPC1, PDC1, EDT1 ... EPCn, PDCn, EDTn`.
151#[derive(PartialEq, Eq, Default, Serialize, Deserialize)]
152pub struct Properties(Vec<Property>);
153impl Properties {
154    pub fn num(&self) -> usize {
155        self.0.len()
156    }
157
158    pub fn get(&self, index: usize) -> Option<&Property> {
159        if index < self.num() {
160            Some(&self.0[index])
161        } else {
162            None
163        }
164    }
165
166    pub fn iter(&self) -> core::slice::Iter<Property> {
167        self.0.iter()
168    }
169}
170
171impl core::iter::IntoIterator for Properties {
172    type Item = Property;
173    type IntoIter = crate::lib::vec::IntoIter<Property>;
174    fn into_iter(self) -> Self::IntoIter {
175        self.0.into_iter()
176    }
177}
178
179impl Deref for Properties {
180    type Target = Vec<Property>;
181
182    fn deref(&self) -> &Self::Target {
183        &self.0
184    }
185}
186
187impl DerefMut for Properties {
188    fn deref_mut(&mut self) -> &mut Self::Target {
189        &mut self.0
190    }
191}
192
193// TODO: from Iter<Property>?
194impl From<Vec<Property>> for Properties {
195    fn from(props: Vec<Property>) -> Self {
196        Self(props)
197    }
198}
199
200impl fmt::Debug for Properties {
201    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202        writeln!(f, "OPC: {}", self.0.len())?;
203        for prop in self.0.iter() {
204            write!(f, "{prop:?}")?;
205        }
206        Ok(())
207    }
208}
209
210impl fmt::Display for Properties {
211    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
212        for prop in self.0.iter() {
213            writeln!(f, "{prop}")?;
214        }
215        Ok(())
216    }
217}
218
219impl Clone for Properties {
220    fn clone(&self) -> Self {
221        Self(self.0.to_vec())
222    }
223}
224
225/// A ECHONET property putting EPC, OPC, and EDT together.
226#[derive(Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
227pub struct Property {
228    pub epc: u8,
229    pub edt: Edt,
230}
231
232impl fmt::Display for Property {
233    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
234        write!(f, "{:02X}: ", self.epc)?;
235        write!(f, "{}", self.edt)?;
236        Ok(())
237    }
238}
239
240impl fmt::Debug for Property {
241    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
242        writeln!(f, "EPC: {:02X}", self.epc)?;
243        writeln!(f, "{:?}", self.edt)?;
244        Ok(())
245    }
246}
247
248/// ECHONET property value data.
249///
250/// EDT consists of data for the relevant ECHONET property (EPC) and
251/// control by an ESV (ServiceCode).
252#[derive(PartialEq, Eq, Default, Serialize, Deserialize)]
253pub struct Edt(Vec<u8>);
254
255impl Edt {
256    pub fn new(value: Vec<u8>) -> Edt {
257        Edt(value)
258    }
259}
260
261impl Deref for Edt {
262    type Target = [u8];
263    fn deref(&self) -> &Self::Target {
264        &self.0
265    }
266}
267
268impl Clone for Edt {
269    fn clone(&self) -> Self {
270        Self(self.0.to_vec())
271    }
272}
273
274impl fmt::Display for Edt {
275    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
276        for byte in self.0.iter() {
277            write!(f, "{byte:02X} ")?;
278        }
279        Ok(())
280    }
281}
282
283impl fmt::Debug for Edt {
284    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
285        writeln!(f, "PDC: {}", self.0.len())?;
286        write!(f, "EDT: ")?;
287        for byte in self.0.iter() {
288            write!(f, "{byte:02X} ")?;
289        }
290        Ok(())
291    }
292}
293
294/// Builds a ECHONET Lite packet.
295///
296/// # Examples
297///
298/// ```
299/// use echonet_lite as el;
300/// use el::prelude::*;
301///
302/// let packet = el::ElPacketBuilder::new()
303///     .transaction_id(1)
304///     .seoj([0x05u8, 0xFFu8, 0x01u8])
305///     .deoj([0x0Eu8, 0xF0u8, 0x01u8])
306///     .esv(el::ServiceCode::Get)
307///     .props(el::props!([0x80, []]))
308///     .build();
309/// ```
310#[derive(Debug)]
311pub struct ElPacketBuilder {
312    transaction_id: u16,
313    seoj: EchonetObject,
314    deoj: EchonetObject,
315    esv: Option<ServiceCode>,
316    props: Properties,
317}
318
319impl Default for ElPacketBuilder {
320    fn default() -> Self {
321        Self::new()
322    }
323}
324
325impl ElPacketBuilder {
326    pub fn new() -> Self {
327        Self {
328            transaction_id: 0,
329            seoj: Default::default(),
330            deoj: Default::default(),
331            esv: None,
332            props: Default::default(),
333        }
334    }
335
336    pub fn transaction_id(mut self, tid: u16) -> Self {
337        self.transaction_id = tid;
338        self
339    }
340
341    pub fn seoj<T>(mut self, seoj: T) -> Self
342    where
343        T: Into<EchonetObject>,
344    {
345        self.seoj = seoj.into();
346        self
347    }
348
349    pub fn deoj<T>(mut self, deoj: T) -> Self
350    where
351        T: Into<EchonetObject>,
352    {
353        self.deoj = deoj.into();
354        self
355    }
356
357    pub fn esv(mut self, esv: ServiceCode) -> Self {
358        self.esv = Some(esv);
359        self
360    }
361
362    pub fn props(mut self, props: Properties) -> Self {
363        self.props = props;
364        self
365    }
366
367    pub fn build(self) -> ElPacket {
368        ElPacket {
369            ehd1: 0x10,
370            ehd2: 0x81,
371            transaction_id: self.transaction_id,
372            seoj: self.seoj,
373            deoj: self.deoj,
374            esv: self.esv.unwrap(), // TODO: define error
375            props: self.props,
376        }
377    }
378}
379
380/// Create a Property object from a pair of EPC (u8) and EDT ([u8; _]).
381///
382/// # Examples
383///
384/// ```
385/// use echonet_lite::{prelude::*, prop};
386/// let prop = prop!(0x80, [0x30]);
387/// ```
388#[macro_export]
389macro_rules! prop {
390    ( $epc:expr, [ $( $edt:expr ),* ] ) => {
391        {
392            let mut bytes: Vec<u8> = Vec::new();
393            $(
394                bytes.push($edt);
395            )*
396            Property{ epc: $epc, edt: Edt::new(bytes) }
397        }
398    };
399}
400
401/// Create a Properties object from an array of EPC (u8) and EDT ([u8; _]) pairs.
402///
403/// # Examples
404///
405/// ```
406/// use echonet_lite::{prelude::*, props};
407/// let props = props!([0x80, [0x30]], [0x81, [0x08]]);
408/// ```
409#[macro_export(local_inner_macros)]
410macro_rules! props {
411    ( $( [ $epc:expr, [ $( $edt:expr ),* ] ] ),* ) => {
412        {
413            let mut props: Vec<Property> = Vec::new();
414            $(
415                props.push( $crate::prop!($epc, [ $( $edt ),* ] ) );
416            )*
417            Properties::from(props)
418        }
419    };
420}
421
422/// Create a Properties object for Get request obtaining one or more property values.
423///
424/// # Examples
425///
426/// ```
427/// use echonet_lite::{prelude::*, bulk_read};
428/// let props = bulk_read!(0x80, 0x81, 0x82);
429/// ```
430#[macro_export]
431macro_rules! bulk_read {
432    ( $( $epc:expr ),* ) => {
433        {
434            let mut props: Vec<Property> = Vec::new();
435            $(
436                props.push( $crate::prop!($epc, [] ) );
437            )*
438            Properties::from(props)
439        }
440    };
441}
442
443#[cfg(test)]
444mod test {
445    use super::*;
446    use crate::de;
447
448    #[test]
449    fn serialize() {
450        let props = props!([0x80, [0x02]]);
451        let result = ElPacketBuilder::new()
452            .transaction_id(1)
453            .esv(ServiceCode::Get)
454            .seoj([0xefu8, 0xffu8, 0x01u8])
455            .deoj([0x03u8, 0x08u8, 0x01u8])
456            .props(props)
457            .build()
458            .serialize()
459            .unwrap();
460        assert_eq!(
461            vec![0x10, 0x81, 0, 1, 0xef, 0xff, 0x01, 0x03, 0x08, 0x01, 0x62, 1, 0x80, 0x01, 0x02],
462            result
463        );
464    }
465
466    #[test]
467    fn deserialize() {
468        let input: Vec<u8> = vec![
469            0x10, 0x81, 0, 1, 0xef, 0xff, 0x01, 0x03, 0x08, 0x01, 0x62, 1, 0x80, 0x01, 0x02,
470        ];
471        let (consumed, decoded): (usize, ElPacket) = ElPacket::from_bytes(&input).unwrap();
472
473        let prop = Property {
474            epc: 0x80,
475            edt: Edt(vec![0x02]),
476        };
477        let expect = ElPacketBuilder::new()
478            .transaction_id(1)
479            .esv(ServiceCode::Get)
480            .seoj([0xef, 0xff, 0x01])
481            .deoj([0x03, 0x08, 0x01])
482            .props(Properties(vec![prop]))
483            .build();
484
485        assert_eq!(15, consumed);
486        assert_eq!(expect, decoded);
487    }
488
489    #[test]
490    fn deserialize_tid() {
491        let input = [0u8, 1u8];
492        let (_, decoded): (usize, u16) = de::deserialize(&input).unwrap();
493
494        assert_eq!(1, decoded);
495    }
496
497    #[test]
498    fn deserialize_esv() {
499        let input = [0x62u8];
500        let (_, decoded): (usize, ServiceCode) = de::deserialize(&input).unwrap();
501
502        assert_eq!(ServiceCode::Get, decoded);
503    }
504
505    #[test]
506    fn deserialize_eoj() {
507        let input = [0xefu8, 0xffu8, 0x01u8];
508        let (_, decoded): (usize, EchonetObject) = de::deserialize(&input).unwrap();
509
510        assert_eq!(EchonetObject::from([0xefu8, 0xffu8, 0x01u8]), decoded);
511    }
512
513    #[test]
514    fn deserialize_edt() {
515        let input: [u8; 2] = [0x01, 0x01];
516        let (_, decoded): (usize, Edt) = de::deserialize(&input).unwrap();
517
518        let expect = Edt(vec![0x01u8]);
519        assert_eq!(expect, decoded);
520    }
521
522    #[test]
523    fn deserialize_empty_edt() {
524        let input: [u8; 1] = [0u8];
525        let (_, decoded): (usize, Edt) = de::deserialize(&input).unwrap();
526
527        let expect = Edt(vec![]);
528        assert_eq!(expect, decoded);
529    }
530
531    #[test]
532    fn deserialize_props() {
533        let input: Vec<u8> = vec![1, 0x80, 0x01, 0x02];
534        let (_, decoded): (usize, Properties) = de::deserialize(&input).unwrap();
535
536        let expect = Properties(vec![Property {
537            epc: 0x80,
538            edt: Edt(vec![0x02]),
539        }]);
540        assert_eq!(expect, decoded);
541    }
542
543    #[test]
544    fn iter_properties() {
545        let props = props!([0x80, [0x02]]);
546        let expect = prop!(0x80, [0x02]);
547        assert_eq!(1usize, props.num());
548        for prop in props.iter() {
549            assert_eq!(expect, *prop);
550        }
551    }
552}