From ce87e28c1189e31522919cd02f25d76d00ba57c5 Mon Sep 17 00:00:00 2001 From: hodasemi Date: Thu, 28 Sep 2023 10:09:33 +0200 Subject: [PATCH] Build E1 commands --- src/command/body.rs | 28 +++-- src/command/mod.rs | 9 +- src/command/request.rs | 31 ++++- src/command/response.rs | 18 +++ src/device.rs | 9 +- src/devices/e1.rs | 268 +++++++++++++++++++++++++++++++++++++++- src/devices/mod.rs | 8 +- 7 files changed, 345 insertions(+), 26 deletions(-) diff --git a/src/command/body.rs b/src/command/body.rs index dcb8836..41c3a93 100644 --- a/src/command/body.rs +++ b/src/command/body.rs @@ -1,4 +1,4 @@ -use std::ops::Index; +use std::ops::{Index, IndexMut}; use super::header::Header; @@ -21,14 +21,18 @@ impl Body { impl From<&[u8]> for Body { fn from(value: &[u8]) -> Self { - Self( - value[(Header::HEADER_LENGTH as usize)..value.len() - 1] - .iter() - .cloned() - .collect::>() - .try_into() - .unwrap(), - ) + let data = value[(Header::HEADER_LENGTH as usize)..value.len() - 1].to_vec(); + + Self(data) + } +} + +impl From<(&[u8], u8)> for Body { + fn from((data, body_type): (&[u8], u8)) -> Self { + let mut v = data.to_vec(); + v.insert(0, body_type); + + Self(v) } } @@ -39,3 +43,9 @@ impl Index for Body { &self.0[index] } } + +impl IndexMut for Body { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] + } +} diff --git a/src/command/mod.rs b/src/command/mod.rs index 9ecf643..507cad6 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -5,10 +5,8 @@ mod response; use serde::{Deserialize, Serialize}; -use self::{body::Body, header::Header}; - #[repr(u8)] -#[derive(Debug, Clone, Serialize, Deserialize, FromPrimitive)] +#[derive(Debug, Clone, Serialize, Deserialize, FromPrimitive, PartialEq, Eq, PartialOrd, Ord)] pub enum MessageType { None = 0x00, Set = 0x02, @@ -33,4 +31,7 @@ pub struct Command { pub(crate) body: Body, } -impl Command {} +pub use body::Body; +pub use header::Header; +pub use request::*; +pub use response::*; diff --git a/src/command/request.rs b/src/command/request.rs index 8de5afb..5d148eb 100644 --- a/src/command/request.rs +++ b/src/command/request.rs @@ -1,4 +1,5 @@ use super::{body::Body, header::Header, Command, MessageType}; +use std::ops::Deref; pub struct CommandRequest { command: Command, @@ -28,7 +29,7 @@ impl CommandRequest { } } - pub fn serialize(&self, body: &[u8]) -> Vec { + pub fn serialize(&self) -> Vec { let mut stream = Vec::new(); stream.extend_from_slice(self.command.header.raw()); @@ -37,6 +38,18 @@ impl CommandRequest { stream } + + pub fn header(&self) -> &Header { + &self.command.header + } + + pub fn body(&self) -> &Body { + &self.command.body + } + + pub fn body_mut(&mut self) -> &mut Body { + &mut self.command.body + } } pub struct CommandQuerySubtype { @@ -56,6 +69,14 @@ impl CommandQuerySubtype { } } +impl Deref for CommandQuerySubtype { + type Target = CommandRequest; + + fn deref(&self) -> &Self::Target { + &self.command + } +} + pub struct CommandQueryCustom { command: CommandRequest, } @@ -67,3 +88,11 @@ impl CommandQueryCustom { } } } + +impl Deref for CommandQueryCustom { + type Target = CommandRequest; + + fn deref(&self) -> &Self::Target { + &self.command + } +} diff --git a/src/command/response.rs b/src/command/response.rs index b4c4166..8b7e247 100644 --- a/src/command/response.rs +++ b/src/command/response.rs @@ -1,3 +1,5 @@ +use std::ops::Deref; + use super::{body::Body, header::Header, Command, MessageType}; pub struct CommandResponse { @@ -13,6 +15,14 @@ impl CommandResponse { }, } } + + pub fn header(&self) -> &Header { + &self.command.header + } + + pub fn body(&self) -> &Body { + &self.command.body + } } pub struct CommandSubtypeResponse { @@ -44,3 +54,11 @@ impl CommandSubtypeResponse { Self { command, sub_type } } } + +impl Deref for CommandSubtypeResponse { + type Target = CommandResponse; + + fn deref(&self) -> &Self::Target { + &self.command + } +} diff --git a/src/device.rs b/src/device.rs index dcbf518..01fe286 100644 --- a/src/device.rs +++ b/src/device.rs @@ -10,7 +10,7 @@ use anyhow::{bail, Context, Error, Result}; use serde_json::to_string; use crate::{ - command::{Command, MessageType}, + command::{Command, CommandRequest, MessageType}, devices::{e1::E1, DeviceBackend}, hex, packet_builder::PacketBuilder, @@ -112,7 +112,7 @@ impl Device { } pub fn refresh_status(&mut self) -> Result<()> { - let mut cmds = self.device_backend.build_query()?; + let mut cmds = vec![self.device_backend.build_query()]; if self.sub_type.is_none() { cmds.insert(0, Command::sub_type(self.info.device_type)); @@ -210,8 +210,9 @@ impl Device { self.send_message(&msg) } - fn build_send(&self, cmd: Command) -> Result<()> { - let data = PacketBuilder::builder(self.info.id, to_string(&cmd)?.as_bytes()).finalize(1); + fn build_send(&self, cmd: CommandRequest) -> Result<()> { + let data = PacketBuilder::builder(self.info.id, to_string(&cmd.serialize())?.as_bytes()) + .finalize(1); self.send_message(&data) } } diff --git a/src/devices/e1.rs b/src/devices/e1.rs index e939f0c..31c12f3 100644 --- a/src/devices/e1.rs +++ b/src/devices/e1.rs @@ -1,8 +1,8 @@ -use std::collections::HashMap; +use std::{collections::HashMap, ops::Deref}; use anyhow::Result; -use crate::command::Command; +use crate::command::*; use super::DeviceBackend; @@ -99,7 +99,64 @@ impl DeviceAttributes { enum AttributeValue { String(Option), Bool(bool), - Int(i32), + Int(u8), +} + +impl AttributeValue { + pub fn set(&mut self, value: impl Into) { + match (self, value.into()) { + (Self::String(current), Self::String(new)) => *current = new, + (Self::Bool(current), Self::Bool(new)) => *current = new, + (Self::Int(current), Self::Int(new)) => *current = new, + + _ => panic!(), + } + } + + pub fn str(&self) -> Option<&str> { + match self { + Self::String(opt) => opt.as_ref().map(|s| s.as_str()), + _ => panic!(), + } + } + + pub fn bool(&self) -> bool { + match self { + Self::Bool(bool) => *bool, + _ => panic!(), + } + } + + pub fn byte(&self) -> u8 { + match self { + Self::Int(b) => *b, + _ => panic!(), + } + } +} + +impl From for AttributeValue { + fn from(value: String) -> Self { + Self::String(Some(value)) + } +} + +impl From> for AttributeValue { + fn from(value: Option) -> Self { + Self::String(value) + } +} + +impl From for AttributeValue { + fn from(value: bool) -> Self { + Self::Bool(value) + } +} + +impl From for AttributeValue { + fn from(value: u8) -> Self { + Self::Int(value) + } } pub struct E1 { @@ -191,8 +248,9 @@ impl E1 { } impl DeviceBackend for E1 { - fn build_query(&self, device_protocol_version: u32) -> Result> { - Ok(vec![CommandQuery::new(device_protocol_version)]) + fn build_query(&self, device_protocol_version: u32) -> CommandRequest { + // Ok(vec![CommandQuery::new(device_protocol_version)]) + todo!() } fn process_message(&self, msg: &[u8]) -> Result> { @@ -209,3 +267,203 @@ impl DeviceBackend for E1 { } } } + +macro_rules! concat { + ($vec1:expr, $($vec2:expr,)+) => {{ + $( + $vec1.extend(&$vec2); + )+ + + $vec1 + }}; +} + +pub struct CommandE1Base { + command: CommandRequest, +} + +impl CommandE1Base { + pub fn new(device_protocol_version: u8, message_type: MessageType, body: Body) -> Self { + Self { + command: CommandRequest::new(device_protocol_version, 0xE1, message_type, body), + } + } +} + +impl Deref for CommandE1Base { + type Target = CommandRequest; + + fn deref(&self) -> &Self::Target { + &self.command + } +} + +pub struct CommandPower { + command: CommandE1Base, +} + +impl CommandPower { + pub fn new(device_protocol_version: u8) -> Self { + Self { + command: CommandE1Base::new( + device_protocol_version, + MessageType::Set, + Body::from(([0x00; 4].as_slice(), 0x08)), + ), + } + } + + pub fn power(&self) -> bool { + self.body()[1] == 0x01 + } + + pub fn set_power(&mut self, power: bool) { + self.body()[1] = if power { 0x01 } else { 0x00 }; + } +} + +impl Deref for CommandPower { + type Target = CommandRequest; + + fn deref(&self) -> &Self::Target { + &self.command + } +} + +pub struct CommandLock { + command: CommandE1Base, +} + +impl CommandLock { + pub fn new(device_protocol_version: u8) -> Self { + Self { + command: CommandE1Base::new( + device_protocol_version, + MessageType::Set, + Body::from((concat!(vec![0x04], vec![0x00; 36],).as_slice(), 0x08)), + ), + } + } + + pub fn lock(&self) -> bool { + self.body()[1] == 0x03 + } + + pub fn set_lock(&mut self, lock: bool) { + self.body()[1] = if lock { 0x03 } else { 0x04 } + } +} + +impl Deref for CommandLock { + type Target = CommandRequest; + + fn deref(&self) -> &Self::Target { + &self.command + } +} + +pub struct CommandStorage { + command: CommandE1Base, +} + +impl CommandStorage { + pub fn new(device_protocol_version: u8) -> Self { + Self { + command: CommandE1Base::new( + device_protocol_version, + MessageType::Set, + Body::from(( + concat!(vec![0x00; 4], vec![0xFF; 6], vec![0x00; 27],).as_slice(), + 0x81, + )), + ), + } + } + + pub fn storage(&self) -> bool { + self.body()[4] == 0x01 + } + + pub fn set_storage(&mut self, storage: bool) { + self.body()[4] = if storage { 0x01 } else { 0x00 } + } +} + +impl Deref for CommandStorage { + type Target = CommandRequest; + + fn deref(&self) -> &Self::Target { + &self.command + } +} + +pub struct CommandQuery { + command: CommandE1Base, +} + +impl CommandQuery { + pub fn new(device_protocol_version: u8) -> Self { + Self { + command: CommandE1Base::new( + device_protocol_version, + MessageType::Query, + Body::from(([].as_slice(), 0x00)), + ), + } + } +} + +pub struct CommandE1Response { + command: CommandResponse, + start: bool, +} + +impl CommandE1Response { + pub fn new(msg: &[u8]) -> Self { + let command = CommandResponse::new(msg); + + Self { + command, + start: false, + } + } + + pub fn update_attributes(&self, attributes: &mut HashMap) { + let message_type = self.command.header().message_type(); + let body = self.command.body(); + let body_type = self.command.body().body_type(); + + if (message_type == MessageType::Set && 0 <= body_type && body_type <= 7) + || ((message_type == MessageType::Query || message_type == MessageType::Notify1) + && body_type == 0) + { + attributes[&DeviceAttributes::Power].set(body[1] > 0); + attributes[&DeviceAttributes::Status].set(body[1]); + attributes[&DeviceAttributes::Mode].set(body[2]); + attributes[&DeviceAttributes::Additional].set(body[3]); + + // 0 - open, 1 - close + attributes[&DeviceAttributes::Door].set((body[5] & 0x01) == 0); + + // 0 - enough, 1 - shortage + attributes[&DeviceAttributes::RinseAid].set((body[5] & 0x02) > 0); + + // e - enough, 1 - shortage + attributes[&DeviceAttributes::Salt].set((body[5] & 0x04) > 0); + + let start_pause = (body[5] & 0x08) > 0; + + if start_pause { + self.start = true; + } else if attributes[&DeviceAttributes::Status].byte() == 2 + || attributes[&DeviceAttributes::Status].byte() == 3 + { + self.start = false; + } + + attributes[&DeviceAttributes::ChildLock].set((body[5] & 0x10) > 0); + attributes[&DeviceAttributes::UV].set((body[4] & 0x2) > 0); + attributes[&DeviceAttributes::Dry].set((body[4] & 0x10) > 0); + } + } +} diff --git a/src/devices/mod.rs b/src/devices/mod.rs index be84dc3..94b43d3 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -1,9 +1,11 @@ -use crate::command::Command; +use anyhow::Result; + +use crate::command::CommandRequest; pub mod e1; pub trait DeviceBackend { - fn build_query(&self, device_protocol_version: u32) -> anyhow::Result>; - fn process_message(&self, msg: &[u8]) -> anyhow::Result>; + fn build_query(&self, device_protocol_version: u32) -> CommandRequest; + fn process_message(&self, msg: &[u8]) -> Result>; fn set_attribute(&self, attribute: &str, value: &str) -> (); }