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 {
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(""));
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<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
F: Fn(&HashMap<&'static str, AttributeValue>) -> Result<()> + 'static,
{
self.updates.push(Box::new(f));
self
}
fn parse_message(&mut self, msg: &[u8]) -> Result<ParseMessage> {
@ -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(())
}

View file

@ -10,7 +10,7 @@ use crate::command::*;
use super::{AttributeValue, DeviceBackend};
pub struct E1 {
modes: HashMap<u32, String>,
modes: HashMap<u8, String>,
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<u8, String>,
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)
});
},
);
}
}
}

View file

@ -12,13 +12,68 @@ pub enum 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()) {
(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<String> for AttributeValue {
fn from(value: String) -> Self {
Self::String(Some(value))