Build E1 commands

This commit is contained in:
hodasemi 2023-09-28 10:09:33 +02:00
parent 631e88aa46
commit ce87e28c11
7 changed files with 345 additions and 26 deletions

View file

@ -1,4 +1,4 @@
use std::ops::Index; use std::ops::{Index, IndexMut};
use super::header::Header; use super::header::Header;
@ -21,14 +21,18 @@ impl Body {
impl From<&[u8]> for Body { impl From<&[u8]> for Body {
fn from(value: &[u8]) -> Self { fn from(value: &[u8]) -> Self {
Self( let data = value[(Header::HEADER_LENGTH as usize)..value.len() - 1].to_vec();
value[(Header::HEADER_LENGTH as usize)..value.len() - 1]
.iter() Self(data)
.cloned() }
.collect::<Vec<u8>>() }
.try_into()
.unwrap(), 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<usize> for Body {
&self.0[index] &self.0[index]
} }
} }
impl IndexMut<usize> for Body {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.0[index]
}
}

View file

@ -5,10 +5,8 @@ mod response;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use self::{body::Body, header::Header};
#[repr(u8)] #[repr(u8)]
#[derive(Debug, Clone, Serialize, Deserialize, FromPrimitive)] #[derive(Debug, Clone, Serialize, Deserialize, FromPrimitive, PartialEq, Eq, PartialOrd, Ord)]
pub enum MessageType { pub enum MessageType {
None = 0x00, None = 0x00,
Set = 0x02, Set = 0x02,
@ -33,4 +31,7 @@ pub struct Command {
pub(crate) body: Body, pub(crate) body: Body,
} }
impl Command {} pub use body::Body;
pub use header::Header;
pub use request::*;
pub use response::*;

View file

@ -1,4 +1,5 @@
use super::{body::Body, header::Header, Command, MessageType}; use super::{body::Body, header::Header, Command, MessageType};
use std::ops::Deref;
pub struct CommandRequest { pub struct CommandRequest {
command: Command, command: Command,
@ -28,7 +29,7 @@ impl CommandRequest {
} }
} }
pub fn serialize(&self, body: &[u8]) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
let mut stream = Vec::new(); let mut stream = Vec::new();
stream.extend_from_slice(self.command.header.raw()); stream.extend_from_slice(self.command.header.raw());
@ -37,6 +38,18 @@ impl CommandRequest {
stream 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 { 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 { pub struct CommandQueryCustom {
command: CommandRequest, command: CommandRequest,
} }
@ -67,3 +88,11 @@ impl CommandQueryCustom {
} }
} }
} }
impl Deref for CommandQueryCustom {
type Target = CommandRequest;
fn deref(&self) -> &Self::Target {
&self.command
}
}

View file

@ -1,3 +1,5 @@
use std::ops::Deref;
use super::{body::Body, header::Header, Command, MessageType}; use super::{body::Body, header::Header, Command, MessageType};
pub struct CommandResponse { 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 { pub struct CommandSubtypeResponse {
@ -44,3 +54,11 @@ impl CommandSubtypeResponse {
Self { command, sub_type } Self { command, sub_type }
} }
} }
impl Deref for CommandSubtypeResponse {
type Target = CommandResponse;
fn deref(&self) -> &Self::Target {
&self.command
}
}

View file

@ -10,7 +10,7 @@ use anyhow::{bail, Context, Error, Result};
use serde_json::to_string; use serde_json::to_string;
use crate::{ use crate::{
command::{Command, MessageType}, command::{Command, CommandRequest, MessageType},
devices::{e1::E1, DeviceBackend}, devices::{e1::E1, DeviceBackend},
hex, hex,
packet_builder::PacketBuilder, packet_builder::PacketBuilder,
@ -112,7 +112,7 @@ impl Device {
} }
pub fn refresh_status(&mut self) -> Result<()> { 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() { if self.sub_type.is_none() {
cmds.insert(0, Command::sub_type(self.info.device_type)); cmds.insert(0, Command::sub_type(self.info.device_type));
@ -210,8 +210,9 @@ impl Device {
self.send_message(&msg) self.send_message(&msg)
} }
fn build_send(&self, cmd: Command) -> Result<()> { fn build_send(&self, cmd: CommandRequest) -> Result<()> {
let data = PacketBuilder::builder(self.info.id, to_string(&cmd)?.as_bytes()).finalize(1); let data = PacketBuilder::builder(self.info.id, to_string(&cmd.serialize())?.as_bytes())
.finalize(1);
self.send_message(&data) self.send_message(&data)
} }
} }

View file

@ -1,8 +1,8 @@
use std::collections::HashMap; use std::{collections::HashMap, ops::Deref};
use anyhow::Result; use anyhow::Result;
use crate::command::Command; use crate::command::*;
use super::DeviceBackend; use super::DeviceBackend;
@ -99,7 +99,64 @@ impl DeviceAttributes {
enum AttributeValue { enum AttributeValue {
String(Option<String>), String(Option<String>),
Bool(bool), Bool(bool),
Int(i32), Int(u8),
}
impl AttributeValue {
pub fn set(&mut self, value: impl Into<Self>) {
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<String> for AttributeValue {
fn from(value: String) -> Self {
Self::String(Some(value))
}
}
impl From<Option<String>> for AttributeValue {
fn from(value: Option<String>) -> Self {
Self::String(value)
}
}
impl From<bool> for AttributeValue {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl From<u8> for AttributeValue {
fn from(value: u8) -> Self {
Self::Int(value)
}
} }
pub struct E1 { pub struct E1 {
@ -191,8 +248,9 @@ impl E1 {
} }
impl DeviceBackend for E1 { impl DeviceBackend for E1 {
fn build_query(&self, device_protocol_version: u32) -> Result<Vec<Command>> { fn build_query(&self, device_protocol_version: u32) -> CommandRequest {
Ok(vec![CommandQuery::new(device_protocol_version)]) // Ok(vec![CommandQuery::new(device_protocol_version)])
todo!()
} }
fn process_message(&self, msg: &[u8]) -> Result<Vec<u8>> { fn process_message(&self, msg: &[u8]) -> Result<Vec<u8>> {
@ -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<DeviceAttributes, AttributeValue>) {
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);
}
}
}

View file

@ -1,9 +1,11 @@
use crate::command::Command; use anyhow::Result;
use crate::command::CommandRequest;
pub mod e1; pub mod e1;
pub trait DeviceBackend { pub trait DeviceBackend {
fn build_query(&self, device_protocol_version: u32) -> anyhow::Result<Vec<Command>>; fn build_query(&self, device_protocol_version: u32) -> CommandRequest;
fn process_message(&self, msg: &[u8]) -> anyhow::Result<Vec<u8>>; fn process_message(&self, msg: &[u8]) -> Result<Vec<u8>>;
fn set_attribute(&self, attribute: &str, value: &str) -> (); fn set_attribute(&self, attribute: &str, value: &str) -> ();
} }