Start implementing write

This commit is contained in:
hodasemi 2023-10-03 19:49:14 +02:00
parent 1268d2df99
commit 2ecd31e9fe
3 changed files with 267 additions and 88 deletions

View file

@ -45,7 +45,7 @@ pub struct Device {
} }
impl Device { impl Device {
pub fn connect(info: DeviceInfo, token: &str, key: &str) -> Result<Self> { pub async fn connect(info: DeviceInfo, token: &str, key: &str) -> Result<Self> {
let mut socket = Err(Error::msg("")); let mut socket = Err(Error::msg(""));
for _ in 0..10 { for _ in 0..10 {
@ -87,7 +87,7 @@ impl Device {
me.authenticate()?; me.authenticate()?;
} }
me.refresh_status()?; me.refresh_status().await?;
Ok(me) Ok(me)
} }
@ -116,7 +116,7 @@ impl Device {
Ok(()) 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()]; let mut cmds = vec![self.device_backend.build_query()];
if self.sub_type == 0 { if self.sub_type == 0 {
@ -130,7 +130,7 @@ impl Device {
let mut buf = [0; 512]; let mut buf = [0; 512];
let bytes_read = match self.socket.lock().unwrap().read(&mut buf) { let bytes_read = match self.socket.lock().unwrap().read(&mut buf) {
Ok(b) => b, Ok(b) => b,
Err(_) => break, Err(_) => continue,
}; };
if bytes_read == 0 { if bytes_read == 0 {
@ -147,11 +147,17 @@ impl Device {
Ok(()) Ok(())
} }
pub fn register_update<F>(&mut self, f: F) pub async fn set_attribute(&self, attribute: &str, value: AttributeValue) -> Result<()> {
todo!()
}
pub fn register_update<F>(mut self, f: F) -> Self
where where
F: Fn(&HashMap<&'static str, AttributeValue>) -> Result<()> + 'static, F: Fn(&HashMap<&'static str, AttributeValue>) -> Result<()> + 'static,
{ {
self.updates.push(Box::new(f)); self.updates.push(Box::new(f));
self
} }
fn parse_message(&mut self, msg: &[u8]) -> Result<ParseMessage> { fn parse_message(&mut self, msg: &[u8]) -> Result<ParseMessage> {
@ -235,7 +241,7 @@ mod test {
use futures::future::try_join; use futures::future::try_join;
use serial_test::serial; use serial_test::serial;
use crate::{device::Device, Cloud, Startup}; use crate::{device::Device, devices::AttributeValue, Cloud, Startup};
#[tokio::test] #[tokio::test]
async fn verify_hex() -> Result<()> { 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"; 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 { 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.token, token_hex);
assert_eq!(&device.key, key_hex); assert_eq!(&device.key, key_hex);
@ -265,7 +271,7 @@ mod test {
const PY_KEY: &str = "0fc0c56ea8124414a362e6449ee45ba92558a54f159d4937af697e405f2326b9"; const PY_KEY: &str = "0fc0c56ea8124414a362e6449ee45ba92558a54f159d4937af697e405f2326b9";
for device_info in devices { for device_info in devices {
Device::connect(device_info, PY_TOKEN, PY_KEY)?; Device::connect(device_info, PY_TOKEN, PY_KEY).await?;
} }
Ok(()) Ok(())
@ -282,6 +288,7 @@ mod test {
let (token, key) = cloud.keys(device_info.id).await?; let (token, key) = cloud.keys(device_info.id).await?;
Device::connect(device_info, &token, &key) Device::connect(device_info, &token, &key)
.await
.context(format!("\ntoken: {token}\nkey: {key}"))?; .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), 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(()) Ok(())
} }

View file

@ -10,7 +10,7 @@ use crate::command::*;
use super::{AttributeValue, DeviceBackend}; use super::{AttributeValue, DeviceBackend};
pub struct E1 { pub struct E1 {
modes: HashMap<u32, String>, modes: HashMap<u8, String>,
attributes: HashMap<&'static str, AttributeValue>, attributes: HashMap<&'static str, AttributeValue>,
status: [&'static str; 5], status: [&'static str; 5],
@ -51,15 +51,15 @@ impl E1 {
let attributes = [ let attributes = [
("power", AttributeValue::Bool(false)), ("power", AttributeValue::Bool(false)),
("status", AttributeValue::Int(0)), ("status", AttributeValue::String(None)),
("mode", AttributeValue::Int(0)), ("mode", AttributeValue::String(None)),
("additional", AttributeValue::Int(0)), ("additional", AttributeValue::Int(0)),
("uv", AttributeValue::Bool(false)), ("uv", AttributeValue::Bool(false)),
("dry", AttributeValue::Bool(false)), ("dry", AttributeValue::Bool(false)),
("dry_status", AttributeValue::Bool(false)), ("dry_status", AttributeValue::Bool(false)),
("door", AttributeValue::Bool(false)), ("door_closed", AttributeValue::Bool(false)),
("rinse_aid", AttributeValue::Bool(false)), ("rinse_aid_shortage", AttributeValue::Bool(false)),
("salt", AttributeValue::Bool(false)), ("salt_shortage", AttributeValue::Bool(false)),
("child_lock", AttributeValue::Bool(false)), ("child_lock", AttributeValue::Bool(false)),
("storage", AttributeValue::Bool(false)), ("storage", AttributeValue::Bool(false)),
("storage_progress", AttributeValue::Bool(false)), ("storage_progress", AttributeValue::Bool(false)),
@ -98,7 +98,12 @@ impl DeviceBackend for E1 {
} }
fn process_message(&mut self, msg: &[u8]) { 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) -> () { 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<u8, String>,
status: &[&str],
progress: &[&str],
) {
let message_type = self.command.header().message_type(); let message_type = self.command.header().message_type();
let body = self.command.body(); let body = self.command.body();
let body_type = self.command.body().body_type(); let body_type = self.command.body().body_type();
@ -312,93 +323,155 @@ impl CommandE1Response {
|| ((message_type == MessageType::Query || message_type == MessageType::Notify1) || ((message_type == MessageType::Query || message_type == MessageType::Notify1)
&& body_type == 0) && body_type == 0)
{ {
attributes.get_mut("power").unwrap().set(body[1] > 0); attributes.get_mut("power").unwrap().set(
attributes.get_mut("status").unwrap().set(body[1]); #[cfg(debug_assertions)]
attributes.get_mut("mode").unwrap().set(body[2]); "power",
attributes.get_mut("additional").unwrap().set(body[3]); 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 // 0 - open, 1 - close
attributes attributes.get_mut("door_closed").unwrap().set(
.get_mut("door") #[cfg(debug_assertions)]
.unwrap() "door_closed",
.set((body[5] & 0x01) == 0); (body[5] & 0x01) == 0,
);
// 0 - enough, 1 - shortage // 0 - enough, 1 - shortage
attributes attributes.get_mut("rinse_aid_shortage").unwrap().set(
.get_mut("rinse_aid") #[cfg(debug_assertions)]
.unwrap() "rinse_aid_shortage",
.set((body[5] & 0x02) > 0); (body[5] & 0x02) > 0,
);
// e - enough, 1 - shortage // 0 - enough, 1 - shortage
attributes attributes.get_mut("salt_shortage").unwrap().set(
.get_mut("salt") #[cfg(debug_assertions)]
.unwrap() "salt_shortage",
.set((body[5] & 0x04) > 0); (body[5] & 0x04) > 0,
);
let start_pause = (body[5] & 0x08) > 0; let start_pause = (body[5] & 0x08) > 0;
if start_pause { if start_pause {
self.start = true; 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; self.start = false;
} }
attributes attributes.get_mut("child_lock").unwrap().set(
.get_mut("child_lock") #[cfg(debug_assertions)]
.unwrap() "child_lock",
.set((body[5] & 0x10) > 0); (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("uv").unwrap().set(
attributes #[cfg(debug_assertions)]
.get_mut("dry_status") "uv",
.unwrap() (body[4] & 0x02) > 0,
.set((body[4] & 0x20) > 0); );
attributes attributes.get_mut("dry").unwrap().set(
.get_mut("storage") #[cfg(debug_assertions)]
.unwrap() "dry",
.set((body[5] & 0x20) > 0); (body[4] & 0x10) > 0,
attributes );
.get_mut("storage_progress") attributes.get_mut("dry_status").unwrap().set(
.unwrap() #[cfg(debug_assertions)]
.set((body[5] & 0x40) > 0); "dry_status",
attributes.get_mut("time_remaining").unwrap().set(body[6]); (body[4] & 0x20) > 0,
attributes.get_mut("progress").unwrap().set(body[9]); );
attributes attributes.get_mut("storage").unwrap().set(
.get_mut("storage_remaining") #[cfg(debug_assertions)]
.unwrap() "storage",
.set(if body.len() > 18 { (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] body[11]
} else { } else {
false.into() false.into()
}); },
attributes.get_mut("temperature").unwrap().set(body[11]); );
attributes attributes.get_mut("temperature").unwrap().set(
.get_mut("humidity") #[cfg(debug_assertions)]
.unwrap() "temperature",
.set(if body.len() > 33 { body[11],
);
attributes.get_mut("humidity").unwrap().set(
#[cfg(debug_assertions)]
"humidity",
if body.len() > 33 {
body[33].into() body[33].into()
} else { } else {
AttributeValue::from(None) AttributeValue::from(None)
}); },
attributes );
.get_mut("waterswitch") attributes.get_mut("waterswitch").unwrap().set(
.unwrap() #[cfg(debug_assertions)]
.set((body[4] & 0x04) > 0); "waterswitch",
attributes (body[4] & 0x04) > 0,
.get_mut("water_lack") );
.unwrap() attributes.get_mut("water_lack").unwrap().set(
.set((body[5] & 0x80) > 0); #[cfg(debug_assertions)]
attributes.get_mut("error_code").unwrap().set(body[10]); "water_lack",
attributes.get_mut("softwater").unwrap().set(body[13]); (body[5] & 0x80) > 0,
attributes.get_mut("wrong_operation").unwrap().set(body[16]); );
attributes attributes.get_mut("error_code").unwrap().set(
.get_mut("bright") #[cfg(debug_assertions)]
.unwrap() "error_code",
.set(if body.len() > 24 { 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() body[24].into()
} else { } else {
AttributeValue::from(None) AttributeValue::from(None)
}); },
);
} }
} }
} }

View file

@ -12,13 +12,68 @@ pub enum AttributeValue {
} }
impl AttributeValue { impl AttributeValue {
pub fn set(&mut self, value: impl Into<Self>) { pub fn set(&mut self, #[cfg(debug_assertions)] attribute_name: &str, value: impl Into<Self>) {
match (self, value.into()) { match (self, value.into()) {
(Self::String(current), Self::String(new)) => *current = new, (Self::String(current), Self::String(new)) => {
(Self::String(current), Self::Bool(new)) => *current = Some(new.to_string()), #[cfg(debug_assertions)]
(Self::String(current), Self::Int(new)) => *current = Some(new.to_string()), {
(Self::Bool(current), Self::Bool(new)) => *current = new, if *current != new {
(Self::Int(current), Self::Int(new)) => *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!(), _ => 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<String> for AttributeValue { impl From<String> for AttributeValue {
fn from(value: String) -> Self { fn from(value: String) -> Self {
Self::String(Some(value)) Self::String(Some(value))