From 2ecd31e9fe4e934a32af63a5540a3dd0a40085d8 Mon Sep 17 00:00:00 2001 From: hodasemi Date: Tue, 3 Oct 2023 19:49:14 +0200 Subject: [PATCH] Start implementing write --- src/device.rs | 57 ++++++++++-- src/devices/e1.rs | 219 ++++++++++++++++++++++++++++++--------------- src/devices/mod.rs | 79 ++++++++++++++-- 3 files changed, 267 insertions(+), 88 deletions(-) diff --git a/src/device.rs b/src/device.rs index 440f260..0f29bfb 100644 --- a/src/device.rs +++ b/src/device.rs @@ -45,7 +45,7 @@ pub struct Device { } impl Device { - pub fn connect(info: DeviceInfo, token: &str, key: &str) -> Result { + pub async fn connect(info: DeviceInfo, token: &str, key: &str) -> Result { let mut socket = Err(Error::msg("")); for _ in 0..10 { @@ -87,7 +87,7 @@ impl Device { me.authenticate()?; } - me.refresh_status()?; + me.refresh_status().await?; Ok(me) } @@ -116,7 +116,7 @@ impl Device { Ok(()) } - pub fn refresh_status(&mut self) -> Result<()> { + pub async fn refresh_status(&mut self) -> Result<()> { let mut cmds = vec![self.device_backend.build_query()]; if self.sub_type == 0 { @@ -130,7 +130,7 @@ impl Device { let mut buf = [0; 512]; let bytes_read = match self.socket.lock().unwrap().read(&mut buf) { Ok(b) => b, - Err(_) => break, + Err(_) => continue, }; if bytes_read == 0 { @@ -147,11 +147,17 @@ impl Device { Ok(()) } - pub fn register_update(&mut self, f: F) + pub async fn set_attribute(&self, attribute: &str, value: AttributeValue) -> Result<()> { + todo!() + } + + pub fn register_update(mut self, f: F) -> Self where F: Fn(&HashMap<&'static str, AttributeValue>) -> Result<()> + 'static, { self.updates.push(Box::new(f)); + + self } fn parse_message(&mut self, msg: &[u8]) -> Result { @@ -235,7 +241,7 @@ mod test { use futures::future::try_join; use serial_test::serial; - use crate::{device::Device, Cloud, Startup}; + use crate::{device::Device, devices::AttributeValue, Cloud, Startup}; #[tokio::test] async fn verify_hex() -> Result<()> { @@ -248,7 +254,7 @@ mod test { let key_hex = b"*[R\x00\xc2\xc0ML\x81\x1d\x05P\xe1\xdc[1CT6\xb9[wM*\x88\xd7\xe4ma\xfd\x96i"; for device_info in devices { - let device = Device::connect(device_info, PY_TOKEN, PY_KEY)?; + let device = Device::connect(device_info, PY_TOKEN, PY_KEY).await?; assert_eq!(&device.token, token_hex); assert_eq!(&device.key, key_hex); @@ -265,7 +271,7 @@ mod test { const PY_KEY: &str = "0fc0c56ea8124414a362e6449ee45ba92558a54f159d4937af697e405f2326b9"; for device_info in devices { - Device::connect(device_info, PY_TOKEN, PY_KEY)?; + Device::connect(device_info, PY_TOKEN, PY_KEY).await?; } Ok(()) @@ -282,6 +288,7 @@ mod test { let (token, key) = cloud.keys(device_info.id).await?; Device::connect(device_info, &token, &key) + .await .context(format!("\ntoken: {token}\nkey: {key}"))?; } @@ -303,7 +310,39 @@ mod test { addr: SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(192, 168, 178, 94)), 6444), }; - Device::connect(device_info, token, key)?; + Device::connect(device_info, token, key).await?; + + Ok(()) + } + + #[tokio::test] + #[serial] + async fn write() -> Result<()> { + let token = "dead840607856a4b84c1a9d94ce8f553b50c037b65fa1ca22126de339c367eb765b231b4525d5b8336c48fe5dae38439bbb5e31282ed3790ff98a48049401dca"; + let key = "50e77947dc63426db3883c7616613410044b02567f5240a5baf789391b2e5a79"; + + let device_info = crate::DeviceInfo { + id: 152832116426242, + model: "760EY015".to_string(), + sn: "0000E1541760EY01534091D002581H2R".to_string(), + protocol: 3, + device_type: 225, + addr: SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(192, 168, 178, 94)), 6444), + }; + + let mut device = Device::connect(device_info, token, key) + .await? + .register_update(|attributes| { + println!("{attributes:?}"); + + Ok(()) + }); + + device + .set_attribute("attribute", AttributeValue::Bool(false)) + .await?; + + device.refresh_status().await?; Ok(()) } diff --git a/src/devices/e1.rs b/src/devices/e1.rs index 95a222c..2cb560e 100644 --- a/src/devices/e1.rs +++ b/src/devices/e1.rs @@ -10,7 +10,7 @@ use crate::command::*; use super::{AttributeValue, DeviceBackend}; pub struct E1 { - modes: HashMap, + modes: HashMap, attributes: HashMap<&'static str, AttributeValue>, status: [&'static str; 5], @@ -51,15 +51,15 @@ impl E1 { let attributes = [ ("power", AttributeValue::Bool(false)), - ("status", AttributeValue::Int(0)), - ("mode", AttributeValue::Int(0)), + ("status", AttributeValue::String(None)), + ("mode", AttributeValue::String(None)), ("additional", AttributeValue::Int(0)), ("uv", AttributeValue::Bool(false)), ("dry", AttributeValue::Bool(false)), ("dry_status", AttributeValue::Bool(false)), - ("door", AttributeValue::Bool(false)), - ("rinse_aid", AttributeValue::Bool(false)), - ("salt", AttributeValue::Bool(false)), + ("door_closed", AttributeValue::Bool(false)), + ("rinse_aid_shortage", AttributeValue::Bool(false)), + ("salt_shortage", AttributeValue::Bool(false)), ("child_lock", AttributeValue::Bool(false)), ("storage", AttributeValue::Bool(false)), ("storage_progress", AttributeValue::Bool(false)), @@ -98,7 +98,12 @@ impl DeviceBackend for E1 { } fn process_message(&mut self, msg: &[u8]) { - CommandE1Response::new(msg).update_attributes(&mut self.attributes); + CommandE1Response::new(msg).update_attributes( + &mut self.attributes, + &self.modes, + &self.status, + &self.progress, + ); } fn set_attribute(&self, attribute: &str, value: &str) -> () { @@ -303,7 +308,13 @@ impl CommandE1Response { } } - pub fn update_attributes(&mut self, attributes: &mut HashMap<&'static str, AttributeValue>) { + pub fn update_attributes( + &mut self, + attributes: &mut HashMap<&'static str, AttributeValue>, + modes: &HashMap, + status: &[&str], + progress: &[&str], + ) { let message_type = self.command.header().message_type(); let body = self.command.body(); let body_type = self.command.body().body_type(); @@ -312,93 +323,155 @@ impl CommandE1Response { || ((message_type == MessageType::Query || message_type == MessageType::Notify1) && body_type == 0) { - attributes.get_mut("power").unwrap().set(body[1] > 0); - attributes.get_mut("status").unwrap().set(body[1]); - attributes.get_mut("mode").unwrap().set(body[2]); - attributes.get_mut("additional").unwrap().set(body[3]); + attributes.get_mut("power").unwrap().set( + #[cfg(debug_assertions)] + "power", + body[1] > 0, + ); + attributes.get_mut("status").unwrap().set( + #[cfg(debug_assertions)] + "status", + status[body[1] as usize], + ); + attributes.get_mut("mode").unwrap().set( + #[cfg(debug_assertions)] + "mode", + &modes[&body[2]], + ); + attributes.get_mut("additional").unwrap().set( + #[cfg(debug_assertions)] + "additional", + body[3], + ); // 0 - open, 1 - close - attributes - .get_mut("door") - .unwrap() - .set((body[5] & 0x01) == 0); + attributes.get_mut("door_closed").unwrap().set( + #[cfg(debug_assertions)] + "door_closed", + (body[5] & 0x01) == 0, + ); // 0 - enough, 1 - shortage - attributes - .get_mut("rinse_aid") - .unwrap() - .set((body[5] & 0x02) > 0); + attributes.get_mut("rinse_aid_shortage").unwrap().set( + #[cfg(debug_assertions)] + "rinse_aid_shortage", + (body[5] & 0x02) > 0, + ); - // e - enough, 1 - shortage - attributes - .get_mut("salt") - .unwrap() - .set((body[5] & 0x04) > 0); + // 0 - enough, 1 - shortage + attributes.get_mut("salt_shortage").unwrap().set( + #[cfg(debug_assertions)] + "salt_shortage", + (body[5] & 0x04) > 0, + ); let start_pause = (body[5] & 0x08) > 0; if start_pause { self.start = true; - } else if attributes["status"].byte() == 2 || attributes["status"].byte() == 3 { + } else if attributes["status"].str() == Some("Delay") + || attributes["status"].str() == Some("Running") + { self.start = false; } - attributes - .get_mut("child_lock") - .unwrap() - .set((body[5] & 0x10) > 0); - attributes.get_mut("uv").unwrap().set((body[4] & 0x02) > 0); - attributes.get_mut("dry").unwrap().set((body[4] & 0x10) > 0); - attributes - .get_mut("dry_status") - .unwrap() - .set((body[4] & 0x20) > 0); - attributes - .get_mut("storage") - .unwrap() - .set((body[5] & 0x20) > 0); - attributes - .get_mut("storage_progress") - .unwrap() - .set((body[5] & 0x40) > 0); - attributes.get_mut("time_remaining").unwrap().set(body[6]); - attributes.get_mut("progress").unwrap().set(body[9]); - attributes - .get_mut("storage_remaining") - .unwrap() - .set(if body.len() > 18 { + attributes.get_mut("child_lock").unwrap().set( + #[cfg(debug_assertions)] + "child_lock", + (body[5] & 0x10) > 0, + ); + attributes.get_mut("uv").unwrap().set( + #[cfg(debug_assertions)] + "uv", + (body[4] & 0x02) > 0, + ); + attributes.get_mut("dry").unwrap().set( + #[cfg(debug_assertions)] + "dry", + (body[4] & 0x10) > 0, + ); + attributes.get_mut("dry_status").unwrap().set( + #[cfg(debug_assertions)] + "dry_status", + (body[4] & 0x20) > 0, + ); + attributes.get_mut("storage").unwrap().set( + #[cfg(debug_assertions)] + "storage", + (body[5] & 0x20) > 0, + ); + attributes.get_mut("storage_progress").unwrap().set( + #[cfg(debug_assertions)] + "storage_progress", + (body[5] & 0x40) > 0, + ); + attributes.get_mut("time_remaining").unwrap().set( + #[cfg(debug_assertions)] + "time_remaining", + body[6], + ); + attributes.get_mut("progress").unwrap().set( + #[cfg(debug_assertions)] + "progress", + progress[body[9] as usize], + ); + attributes.get_mut("storage_remaining").unwrap().set( + #[cfg(debug_assertions)] + "storage_remaining", + if body.len() > 18 { body[11] } else { false.into() - }); - attributes.get_mut("temperature").unwrap().set(body[11]); - attributes - .get_mut("humidity") - .unwrap() - .set(if body.len() > 33 { + }, + ); + attributes.get_mut("temperature").unwrap().set( + #[cfg(debug_assertions)] + "temperature", + body[11], + ); + attributes.get_mut("humidity").unwrap().set( + #[cfg(debug_assertions)] + "humidity", + if body.len() > 33 { body[33].into() } else { AttributeValue::from(None) - }); - attributes - .get_mut("waterswitch") - .unwrap() - .set((body[4] & 0x04) > 0); - attributes - .get_mut("water_lack") - .unwrap() - .set((body[5] & 0x80) > 0); - attributes.get_mut("error_code").unwrap().set(body[10]); - attributes.get_mut("softwater").unwrap().set(body[13]); - attributes.get_mut("wrong_operation").unwrap().set(body[16]); - attributes - .get_mut("bright") - .unwrap() - .set(if body.len() > 24 { + }, + ); + attributes.get_mut("waterswitch").unwrap().set( + #[cfg(debug_assertions)] + "waterswitch", + (body[4] & 0x04) > 0, + ); + attributes.get_mut("water_lack").unwrap().set( + #[cfg(debug_assertions)] + "water_lack", + (body[5] & 0x80) > 0, + ); + attributes.get_mut("error_code").unwrap().set( + #[cfg(debug_assertions)] + "error_code", + body[10], + ); + attributes.get_mut("softwater").unwrap().set( + #[cfg(debug_assertions)] + "softwater", + body[13], + ); + attributes.get_mut("wrong_operation").unwrap().set( + #[cfg(debug_assertions)] + "wrong_operation", + body[16], + ); + attributes.get_mut("bright").unwrap().set( + #[cfg(debug_assertions)] + "bright", + if body.len() > 24 { body[24].into() } else { AttributeValue::from(None) - }); + }, + ); } } } diff --git a/src/devices/mod.rs b/src/devices/mod.rs index 6e73319..00dfed5 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -12,13 +12,68 @@ pub enum AttributeValue { } impl AttributeValue { - pub fn set(&mut self, value: impl Into) { + pub fn set(&mut self, #[cfg(debug_assertions)] attribute_name: &str, value: impl Into) { match (self, value.into()) { - (Self::String(current), Self::String(new)) => *current = new, - (Self::String(current), Self::Bool(new)) => *current = Some(new.to_string()), - (Self::String(current), Self::Int(new)) => *current = Some(new.to_string()), - (Self::Bool(current), Self::Bool(new)) => *current = new, - (Self::Int(current), Self::Int(new)) => *current = new, + (Self::String(current), Self::String(new)) => { + #[cfg(debug_assertions)] + { + if *current != new { + println!( + "Attribute ({attribute_name}) changed from {current:?} to {new:?}" + ); + } + } + + *current = new; + } + (Self::String(current), Self::Bool(new)) => { + #[cfg(debug_assertions)] + { + if *current != Some(new.to_string()) { + println!( + "Attribute ({attribute_name}) changed from {current:?} to {new:?}" + ); + } + } + + *current = Some(new.to_string()); + } + (Self::String(current), Self::Int(new)) => { + #[cfg(debug_assertions)] + { + if *current != Some(new.to_string()) { + println!( + "Attribute ({attribute_name}) changed from {current:?} to {new:?}" + ); + } + } + + *current = Some(new.to_string()); + } + (Self::Bool(current), Self::Bool(new)) => { + #[cfg(debug_assertions)] + { + if *current != new { + println!( + "Attribute ({attribute_name}) changed from {current:?} to {new:?}" + ); + } + } + + *current = new; + } + (Self::Int(current), Self::Int(new)) => { + #[cfg(debug_assertions)] + { + if *current != new { + println!( + "Attribute ({attribute_name}) changed from {current:?} to {new:?}" + ); + } + } + + *current = new; + } _ => panic!(), } @@ -46,6 +101,18 @@ impl AttributeValue { } } +impl From<&str> for AttributeValue { + fn from(value: &str) -> Self { + Self::String(Some(value.to_string())) + } +} + +impl From<&String> for AttributeValue { + fn from(value: &String) -> Self { + Self::String(Some(value.clone())) + } +} + impl From for AttributeValue { fn from(value: String) -> Self { Self::String(Some(value))