Fix connection to device
This commit is contained in:
parent
2aec54c7a7
commit
1268d2df99
7 changed files with 276 additions and 402 deletions
68
midea.py
68
midea.py
|
@ -9,69 +9,67 @@ import packet_builder;
|
|||
|
||||
import datetime;
|
||||
|
||||
async def test():
|
||||
cl = cloud.MSmartHomeCloud(
|
||||
"MSmartHome",
|
||||
aiohttp.ClientSession(),
|
||||
"michaelh.95@t-online.de",
|
||||
"Hoda.semi1"
|
||||
)
|
||||
# async def test():
|
||||
# cl = cloud.MSmartHomeCloud(
|
||||
# "MSmartHome",
|
||||
# aiohttp.ClientSession(),
|
||||
# "michaelh.95@t-online.de",
|
||||
# "Hoda.semi1"
|
||||
# )
|
||||
|
||||
devices = discover.discover()
|
||||
# devices = discover.discover()
|
||||
|
||||
if len(devices) > 0:
|
||||
# if len(devices) > 0:
|
||||
# if await cl.login():
|
||||
# for device_id in devices:
|
||||
# token = "702b9dfc3ac6c82979986ee3a053a76f75f9e9c763ce5c25af5c2cc982f797a9409adff3745e23fee3a464d745e005c839efb0b84082acc962e59ab8683e0299"
|
||||
# key = "52b2feee353841588994e630dcb59819ec71ce1ffacb48628f4f436f5c54f11e"
|
||||
# keys = await cl.get_keys(device_id)
|
||||
|
||||
# for k in keys:
|
||||
# token = keys[k]['token']
|
||||
# key = keys[k]['key']
|
||||
|
||||
# device_info = devices[device_id]
|
||||
|
||||
# dev = device.MiedaDevice(
|
||||
# dev = devicee1.MideaE1Device(
|
||||
# name="",
|
||||
# device_id=device_id,
|
||||
# device_type=225,
|
||||
# ip_address=device_info['ip_address'],
|
||||
# port=device_info['port'],
|
||||
# token=token,
|
||||
# key=key,
|
||||
# protocol=3,
|
||||
# model=device_info['model'],
|
||||
# attributes={}
|
||||
# customize=""
|
||||
# )
|
||||
|
||||
# if dev.connect(False):
|
||||
# print("success")
|
||||
# else:
|
||||
# print("fail")
|
||||
# if dev.connect(True):
|
||||
# return dev
|
||||
|
||||
key = b"V\x103\xba\xa0W\x85\xaa\x0c\x01q\xb7\x94\t\x7f\xd4\xe7=L\x91\x02\x14\x8d`\xf7~\xc6\xfd\x99?\x14\xbc"
|
||||
|
||||
chunk = bytearray([77, 15, 242, 219, 218, 33, 185, 171, 8, 35, 207, 179, 31, 179, 75, 165])
|
||||
chunk_ref = bytearray([49, 19, 2, 10, 23, 20, 2, 134, 2, 0, 0, 139, 0, 0, 0, 0])
|
||||
|
||||
if await cl.login():
|
||||
for device_id in devices:
|
||||
keys = await cl.get_keys(device_id)
|
||||
sec = security.LocalSecurity()
|
||||
chunk_res = sec.aes_cbc_decrypt(chunk, key)
|
||||
|
||||
for k in keys:
|
||||
token = keys[k]['token']
|
||||
key = keys[k]['key']
|
||||
print(chunk_ref)
|
||||
print(chunk_res)
|
||||
|
||||
device_info = devices[device_id]
|
||||
|
||||
dev = devicee1.MideaE1Device(
|
||||
name="",
|
||||
device_id=device_id,
|
||||
ip_address=device_info['ip_address'],
|
||||
port=device_info['port'],
|
||||
token=token,
|
||||
key=key,
|
||||
device_id=152832116426242,
|
||||
ip_address="192.168.178.94",
|
||||
port=6444,
|
||||
token="dead840607856a4b84c1a9d94ce8f553b50c037b65fa1ca22126de339c367eb765b231b4525d5b8336c48fe5dae38439bbb5e31282ed3790ff98a48049401dca",
|
||||
key="50e77947dc63426db3883c7616613410044b02567f5240a5baf789391b2e5a79",
|
||||
protocol=3,
|
||||
model=device_info['model'],
|
||||
model="760EY015",
|
||||
customize=""
|
||||
)
|
||||
|
||||
if dev.connect(True):
|
||||
return dev
|
||||
|
||||
dev = asyncio.run(test())
|
||||
dev.connect(True)
|
||||
|
||||
print(dev)
|
||||
|
||||
|
|
|
@ -250,6 +250,9 @@ class LocalSecurity:
|
|||
padding = header[5] >> 4
|
||||
msgtype = header[5] & 0xf
|
||||
data = data[6:]
|
||||
|
||||
# (6) vec![131, 112, 0, 5, 32, 15]
|
||||
|
||||
if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST):
|
||||
sign = data[-32:]
|
||||
data = data[:-32]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
io::{Read, Write},
|
||||
net::TcpStream,
|
||||
sync::Mutex,
|
||||
|
@ -10,10 +11,10 @@ use anyhow::{bail, Context, Error, Result};
|
|||
|
||||
use crate::{
|
||||
command::{
|
||||
CommandHeartbeat, CommandQuerySubtype, CommandRequest, CommandSubtypeResponse, MessageType,
|
||||
CommandHeartbeat, CommandQuerySubtype, CommandSubtypeResponse, MessageType,
|
||||
RequestSerializer,
|
||||
},
|
||||
devices::{e1::E1, DeviceBackend},
|
||||
devices::{e1::E1, AttributeValue, DeviceBackend},
|
||||
hex,
|
||||
packet_builder::PacketBuilder,
|
||||
security::{MsgType, Security},
|
||||
|
@ -37,7 +38,7 @@ pub struct Device {
|
|||
sub_type: u16,
|
||||
device_protocol_version: u8,
|
||||
|
||||
updates: Vec<Box<dyn Fn(&[u8]) -> Result<()>>>,
|
||||
updates: Vec<Box<dyn Fn(&HashMap<&'static str, AttributeValue>) -> Result<()>>>,
|
||||
|
||||
token: [u8; 64],
|
||||
key: [u8; 32],
|
||||
|
@ -127,7 +128,10 @@ impl Device {
|
|||
|
||||
loop {
|
||||
let mut buf = [0; 512];
|
||||
let bytes_read = self.socket.lock().unwrap().read(&mut buf)?;
|
||||
let bytes_read = match self.socket.lock().unwrap().read(&mut buf) {
|
||||
Ok(b) => b,
|
||||
Err(_) => break,
|
||||
};
|
||||
|
||||
if bytes_read == 0 {
|
||||
bail!("socket error");
|
||||
|
@ -145,7 +149,7 @@ impl Device {
|
|||
|
||||
pub fn register_update<F>(&mut self, f: F)
|
||||
where
|
||||
F: Fn(&[u8]) -> Result<()> + 'static,
|
||||
F: Fn(&HashMap<&'static str, AttributeValue>) -> Result<()> + 'static,
|
||||
{
|
||||
self.updates.push(Box::new(f));
|
||||
}
|
||||
|
@ -178,12 +182,10 @@ impl Device {
|
|||
Security::aes_decrypt(crypt);
|
||||
|
||||
if self.pre_process_message(crypt) {
|
||||
let status = self.device_backend.process_message(crypt)?;
|
||||
self.device_backend.process_message(crypt);
|
||||
|
||||
if status.len() > 0 {
|
||||
for update in self.updates.iter() {
|
||||
update(&status)?;
|
||||
}
|
||||
update(self.device_backend.attributes())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -208,11 +210,6 @@ impl Device {
|
|||
fn send_message(&self, msg: &[u8]) -> Result<()> {
|
||||
let data = self.security.encode_8370(msg, MsgType::ENCRYPTED_REQUEST)?;
|
||||
|
||||
// encrypted
|
||||
let e = b"\x83p\x00~ fq\xa8\xa9b(\x8c\r,\x96X\xbdT\x1d\x06\xa1/\xb5@\xa2\xeb\x96\x0c\x01s\xf0\x8c\x98ELT\x89\x81\xcc\x9d\xaa\xb6[dq\xcf\x98\xd1s\x8c\x08\x0e\xe6u1D\x80\x17I\xe2\x987s\xbe\xb2\xa9\x13\x86\xce\xb3qq\xfe\xa6\x11\x10\xcfi\xc2\xaeXJH\xb8\xa8\x0b5\x08z\x00\xec\xa2t\x13\xeds\xe7:\x0f\x0eP\xfe\x80w7\xbb\xdf\x0f\x14D\xfd9\xceZ\xda\x1a\xda\xfb\x0b\xe0\x92\xc2D\xb4\xdfWE\x89_\xd9\xd0\xb2\xb6\xd9";
|
||||
|
||||
// assert_eq!(data, e);
|
||||
|
||||
self.socket.lock().unwrap().write(&data)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -226,17 +223,14 @@ impl Device {
|
|||
fn build_send(&self, cmd: impl RequestSerializer) -> Result<()> {
|
||||
let data = PacketBuilder::builder(self.info.id, cmd).finalize(1);
|
||||
|
||||
// msg
|
||||
let d = b"ZZ\x01\x11X\x00 \x00\x00\x00\x00\x00\x0435\x06\x02\n\x17\x14\x02\x86\x02\x00\x00\x8b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17G\x0b\xc3\xe7\r\xaa\xb93\x81r0?\x02s\xf8\x88\xe9G)\xa5\x127\x99\xdd\x8afi\xbd\xf6\xc3\xf5\xf1B\x84\x8a&n\xd9\xdb\xba\xee(\\\'\xb2\x0c\x02";
|
||||
|
||||
// assert_eq!(data, d);
|
||||
|
||||
self.send_message(&data)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use futures::future::try_join;
|
||||
use serial_test::serial;
|
||||
|
@ -293,4 +287,24 @@ mod test {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn communication() -> 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),
|
||||
};
|
||||
|
||||
Device::connect(device_info, token, key)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,166 +7,11 @@ use anyhow::Result;
|
|||
|
||||
use crate::command::*;
|
||||
|
||||
use super::DeviceBackend;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum DeviceAttributes {
|
||||
Power,
|
||||
Status,
|
||||
Mode,
|
||||
Additional,
|
||||
Door,
|
||||
RinseAid,
|
||||
Salt,
|
||||
ChildLock,
|
||||
UV,
|
||||
Dry,
|
||||
DryStatus,
|
||||
Storage,
|
||||
StorageStatus,
|
||||
TimeRemaining,
|
||||
Progress,
|
||||
StorageRemaining,
|
||||
Temperature,
|
||||
Humidity,
|
||||
Waterswitch,
|
||||
WaterLack,
|
||||
ErrorCode,
|
||||
Softwater,
|
||||
WrongOperation,
|
||||
Bright,
|
||||
}
|
||||
|
||||
impl DeviceAttributes {
|
||||
fn as_str(&self) -> &str {
|
||||
match self {
|
||||
DeviceAttributes::Power => "power",
|
||||
DeviceAttributes::Status => "status",
|
||||
DeviceAttributes::Mode => "mode",
|
||||
DeviceAttributes::Additional => "additional",
|
||||
DeviceAttributes::Door => "door",
|
||||
DeviceAttributes::RinseAid => "rinse_aid",
|
||||
DeviceAttributes::Salt => "salt",
|
||||
DeviceAttributes::ChildLock => "child_lock",
|
||||
DeviceAttributes::UV => "uv",
|
||||
DeviceAttributes::Dry => "dry",
|
||||
DeviceAttributes::DryStatus => "dry_status",
|
||||
DeviceAttributes::Storage => "storage",
|
||||
DeviceAttributes::StorageStatus => "storage_status",
|
||||
DeviceAttributes::TimeRemaining => "time_remaining",
|
||||
DeviceAttributes::Progress => "progress",
|
||||
DeviceAttributes::StorageRemaining => "storage_remaining",
|
||||
DeviceAttributes::Temperature => "temperature",
|
||||
DeviceAttributes::Humidity => "humidity",
|
||||
DeviceAttributes::Waterswitch => "waterswitch",
|
||||
DeviceAttributes::WaterLack => "water_lack",
|
||||
DeviceAttributes::ErrorCode => "error_code",
|
||||
DeviceAttributes::Softwater => "softwater",
|
||||
DeviceAttributes::WrongOperation => "wrong_operation",
|
||||
DeviceAttributes::Bright => "bright",
|
||||
}
|
||||
}
|
||||
|
||||
fn from_str(s: &str) -> Self {
|
||||
match s {
|
||||
"power" => Self::Power,
|
||||
"status" => Self::Status,
|
||||
"mode" => Self::Mode,
|
||||
"additional" => Self::Additional,
|
||||
"door" => Self::Door,
|
||||
"rinse_aid" => Self::RinseAid,
|
||||
"salt" => Self::Salt,
|
||||
"child_lock" => Self::ChildLock,
|
||||
"uv" => Self::UV,
|
||||
"dry" => Self::Dry,
|
||||
"dry_status" => Self::DryStatus,
|
||||
"storage" => Self::Storage,
|
||||
"storage_status" => Self::StorageStatus,
|
||||
"time_remaining" => Self::TimeRemaining,
|
||||
"progress" => Self::Progress,
|
||||
"storage_remaining" => Self::StorageRemaining,
|
||||
"temperature" => Self::Temperature,
|
||||
"humidity" => Self::Humidity,
|
||||
"waterswitch" => Self::Waterswitch,
|
||||
"water_lack" => Self::WaterLack,
|
||||
"error_code" => Self::ErrorCode,
|
||||
"softwater" => Self::Softwater,
|
||||
"wrong_operation" => Self::WrongOperation,
|
||||
"bright" => Self::Bright,
|
||||
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AttributeValue {
|
||||
String(Option<String>),
|
||||
Bool(bool),
|
||||
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::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,
|
||||
|
||||
_ => 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)
|
||||
}
|
||||
}
|
||||
use super::{AttributeValue, DeviceBackend};
|
||||
|
||||
pub struct E1 {
|
||||
modes: HashMap<u32, String>,
|
||||
attributes: HashMap<DeviceAttributes, AttributeValue>,
|
||||
attributes: HashMap<&'static str, AttributeValue>,
|
||||
|
||||
status: [&'static str; 5],
|
||||
progress: [&'static str; 6],
|
||||
|
@ -205,39 +50,30 @@ impl E1 {
|
|||
.collect();
|
||||
|
||||
let attributes = [
|
||||
(DeviceAttributes::Power, AttributeValue::Bool(false)),
|
||||
(DeviceAttributes::Status, AttributeValue::String(None)),
|
||||
(DeviceAttributes::Mode, AttributeValue::Int(0)),
|
||||
(DeviceAttributes::Additional, AttributeValue::Int(0)),
|
||||
(DeviceAttributes::UV, AttributeValue::Bool(false)),
|
||||
(DeviceAttributes::Dry, AttributeValue::Bool(false)),
|
||||
(DeviceAttributes::DryStatus, AttributeValue::Bool(false)),
|
||||
(DeviceAttributes::Door, AttributeValue::Bool(false)),
|
||||
(DeviceAttributes::RinseAid, AttributeValue::Bool(false)),
|
||||
(DeviceAttributes::Salt, AttributeValue::Bool(false)),
|
||||
(DeviceAttributes::ChildLock, AttributeValue::Bool(false)),
|
||||
(DeviceAttributes::Storage, AttributeValue::Bool(false)),
|
||||
(DeviceAttributes::StorageStatus, AttributeValue::Bool(false)),
|
||||
(
|
||||
DeviceAttributes::TimeRemaining,
|
||||
AttributeValue::String(None),
|
||||
),
|
||||
(DeviceAttributes::Progress, AttributeValue::String(None)),
|
||||
(
|
||||
DeviceAttributes::StorageRemaining,
|
||||
AttributeValue::String(None),
|
||||
),
|
||||
(DeviceAttributes::Temperature, AttributeValue::String(None)),
|
||||
(DeviceAttributes::Humidity, AttributeValue::String(None)),
|
||||
(DeviceAttributes::Waterswitch, AttributeValue::Bool(false)),
|
||||
(DeviceAttributes::WaterLack, AttributeValue::Bool(false)),
|
||||
(DeviceAttributes::ErrorCode, AttributeValue::String(None)),
|
||||
(DeviceAttributes::Softwater, AttributeValue::Int(0)),
|
||||
(
|
||||
DeviceAttributes::WrongOperation,
|
||||
AttributeValue::String(None),
|
||||
),
|
||||
(DeviceAttributes::Bright, AttributeValue::Int(0)),
|
||||
("power", AttributeValue::Bool(false)),
|
||||
("status", AttributeValue::Int(0)),
|
||||
("mode", AttributeValue::Int(0)),
|
||||
("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)),
|
||||
("child_lock", AttributeValue::Bool(false)),
|
||||
("storage", AttributeValue::Bool(false)),
|
||||
("storage_progress", AttributeValue::Bool(false)),
|
||||
("time_remaining", AttributeValue::String(None)),
|
||||
("progress", AttributeValue::String(None)),
|
||||
("storage_remaining", AttributeValue::String(None)),
|
||||
("temperature", AttributeValue::String(None)),
|
||||
("humidity", AttributeValue::String(None)),
|
||||
("waterswitch", AttributeValue::Bool(false)),
|
||||
("water_lack", AttributeValue::Bool(false)),
|
||||
("error_code", AttributeValue::String(None)),
|
||||
("softwater", AttributeValue::Int(0)),
|
||||
("wrong_operation", AttributeValue::String(None)),
|
||||
("bright", AttributeValue::Int(0)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
@ -261,31 +97,23 @@ impl DeviceBackend for E1 {
|
|||
CommandQuery::new(self.device_protocol_version).request()
|
||||
}
|
||||
|
||||
fn process_message(&self, msg: &[u8]) -> Result<Vec<u8>> {
|
||||
let cmd = CommandE1Response::new(msg);
|
||||
|
||||
todo!()
|
||||
fn process_message(&mut self, msg: &[u8]) {
|
||||
CommandE1Response::new(msg).update_attributes(&mut self.attributes);
|
||||
}
|
||||
|
||||
fn set_attribute(&self, attribute: &str, value: &str) -> () {
|
||||
match DeviceAttributes::from_str(attribute) {
|
||||
DeviceAttributes::Power => (),
|
||||
DeviceAttributes::ChildLock => (),
|
||||
DeviceAttributes::Storage => (),
|
||||
match attribute {
|
||||
"power" => (),
|
||||
"child_lock" => (),
|
||||
"storage" => (),
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn attributes(&self) -> &HashMap<&'static str, AttributeValue> {
|
||||
&self.attributes
|
||||
}
|
||||
|
||||
macro_rules! concat {
|
||||
($vec1:expr, $($vec2:expr,)+) => {{
|
||||
$(
|
||||
$vec1.extend(&$vec2);
|
||||
)+
|
||||
|
||||
$vec1
|
||||
}};
|
||||
}
|
||||
|
||||
pub struct CommandE1Base {
|
||||
|
@ -367,7 +195,7 @@ impl CommandLock {
|
|||
command: CommandE1Base::new(
|
||||
device_protocol_version,
|
||||
MessageType::Set,
|
||||
Body::from((concat!(vec![0x04], vec![0x00; 36],).as_slice(), 0x08)),
|
||||
Body::from(([vec![0x04], vec![0x00; 36]].concat().as_slice(), 0x08)),
|
||||
#[cfg(debug_assertions)]
|
||||
"Lock",
|
||||
),
|
||||
|
@ -406,7 +234,9 @@ impl CommandStorage {
|
|||
device_protocol_version,
|
||||
MessageType::Set,
|
||||
Body::from((
|
||||
concat!(vec![0x00; 4], vec![0xFF; 6], vec![0x00; 27],).as_slice(),
|
||||
[vec![0x00; 4], vec![0xFF; 6], vec![0x00; 27]]
|
||||
.concat()
|
||||
.as_slice(),
|
||||
0x81,
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
|
@ -473,50 +303,35 @@ impl CommandE1Response {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_attributes(
|
||||
&mut self,
|
||||
attributes: &mut HashMap<DeviceAttributes, AttributeValue>,
|
||||
) {
|
||||
pub fn update_attributes(&mut self, attributes: &mut HashMap<&'static str, 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)
|
||||
if (message_type == MessageType::Set && body_type <= 7)
|
||||
|| ((message_type == MessageType::Query || message_type == MessageType::Notify1)
|
||||
&& body_type == 0)
|
||||
{
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Power)
|
||||
.unwrap()
|
||||
.set(body[1] > 0);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Status)
|
||||
.unwrap()
|
||||
.set(body[1]);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Mode)
|
||||
.unwrap()
|
||||
.set(body[2]);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Additional)
|
||||
.unwrap()
|
||||
.set(body[3]);
|
||||
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]);
|
||||
|
||||
// 0 - open, 1 - close
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Door)
|
||||
.get_mut("door")
|
||||
.unwrap()
|
||||
.set((body[5] & 0x01) == 0);
|
||||
|
||||
// 0 - enough, 1 - shortage
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::RinseAid)
|
||||
.get_mut("rinse_aid")
|
||||
.unwrap()
|
||||
.set((body[5] & 0x02) > 0);
|
||||
|
||||
// e - enough, 1 - shortage
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Salt)
|
||||
.get_mut("salt")
|
||||
.unwrap()
|
||||
.set((body[5] & 0x04) > 0);
|
||||
|
||||
|
@ -524,58 +339,41 @@ impl CommandE1Response {
|
|||
|
||||
if start_pause {
|
||||
self.start = true;
|
||||
} else if attributes[&DeviceAttributes::Status].byte() == 2
|
||||
|| attributes[&DeviceAttributes::Status].byte() == 3
|
||||
{
|
||||
} else if attributes["status"].byte() == 2 || attributes["status"].byte() == 3 {
|
||||
self.start = false;
|
||||
}
|
||||
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::ChildLock)
|
||||
.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(&DeviceAttributes::UV)
|
||||
.unwrap()
|
||||
.set((body[4] & 0x02) > 0);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Dry)
|
||||
.unwrap()
|
||||
.set((body[4] & 0x10) > 0);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::DryStatus)
|
||||
.get_mut("dry_status")
|
||||
.unwrap()
|
||||
.set((body[4] & 0x20) > 0);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Status)
|
||||
.get_mut("storage")
|
||||
.unwrap()
|
||||
.set((body[5] & 0x20) > 0);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::StorageStatus)
|
||||
.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(&DeviceAttributes::TimeRemaining)
|
||||
.unwrap()
|
||||
.set(body[6]);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Progress)
|
||||
.unwrap()
|
||||
.set(body[9]);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::StorageRemaining)
|
||||
.get_mut("storage_remaining")
|
||||
.unwrap()
|
||||
.set(if body.len() > 18 {
|
||||
body[11]
|
||||
} else {
|
||||
false.into()
|
||||
});
|
||||
attributes.get_mut("temperature").unwrap().set(body[11]);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Temperature)
|
||||
.unwrap()
|
||||
.set(body[11]);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Humidity)
|
||||
.get_mut("humidity")
|
||||
.unwrap()
|
||||
.set(if body.len() > 33 {
|
||||
body[33].into()
|
||||
|
@ -583,27 +381,18 @@ impl CommandE1Response {
|
|||
AttributeValue::from(None)
|
||||
});
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Waterswitch)
|
||||
.get_mut("waterswitch")
|
||||
.unwrap()
|
||||
.set((body[4] & 0x04) > 0);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::WaterLack)
|
||||
.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(&DeviceAttributes::ErrorCode)
|
||||
.unwrap()
|
||||
.set(body[10]);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Softwater)
|
||||
.unwrap()
|
||||
.set(body[13]);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::WrongOperation)
|
||||
.unwrap()
|
||||
.set(body[16]);
|
||||
attributes
|
||||
.get_mut(&DeviceAttributes::Bright)
|
||||
.get_mut("bright")
|
||||
.unwrap()
|
||||
.set(if body.len() > 24 {
|
||||
body[24].into()
|
||||
|
|
|
@ -1,11 +1,78 @@
|
|||
use anyhow::Result;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::command::CommandRequest;
|
||||
|
||||
pub mod e1;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum AttributeValue {
|
||||
String(Option<String>),
|
||||
Bool(bool),
|
||||
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::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,
|
||||
|
||||
_ => 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 trait DeviceBackend {
|
||||
fn build_query(&self) -> CommandRequest;
|
||||
fn process_message(&self, msg: &[u8]) -> Result<Vec<u8>>;
|
||||
fn process_message(&mut self, msg: &[u8]);
|
||||
fn attributes(&self) -> &HashMap<&'static str, AttributeValue>;
|
||||
fn set_attribute(&self, attribute: &str, value: &str) -> ();
|
||||
}
|
||||
|
|
|
@ -62,16 +62,7 @@ impl<S: RequestSerializer> PacketBuilder<S> {
|
|||
packet[3] = 0x10;
|
||||
packet[6] = 0x7b;
|
||||
} else {
|
||||
let mut data = self.command.serialize();
|
||||
|
||||
// data
|
||||
let d = b"\xaa\x1d\xe1\x00\x00\x00\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00b";
|
||||
|
||||
assert_eq!(data, d);
|
||||
|
||||
Security::aes_encrypt(&mut data);
|
||||
|
||||
packet.extend(data);
|
||||
packet.extend(Security::aes_encrypt(&self.command.serialize()));
|
||||
}
|
||||
|
||||
// packet length
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicU16, Ordering::SeqCst};
|
|||
use aes::{
|
||||
cipher::{
|
||||
block_padding::NoPadding, generic_array::GenericArray, BlockDecrypt, BlockDecryptMut,
|
||||
BlockEncrypt, BlockEncryptMut, KeyInit, KeyIvInit,
|
||||
BlockEncrypt, BlockEncryptMut, BlockSizeUser, KeyInit, KeyIvInit,
|
||||
},
|
||||
Aes128,
|
||||
};
|
||||
|
@ -40,7 +40,7 @@ impl Security {
|
|||
md5::compute([data, Self::SALT].concat()).to_vec()
|
||||
}
|
||||
|
||||
pub fn aes_encrypt(data: &mut [u8]) -> Vec<u8> {
|
||||
pub fn aes_encrypt(data: &[u8]) -> Vec<u8> {
|
||||
let array = GenericArray::from(Self::KEY);
|
||||
let cipher = Aes128::new(&array);
|
||||
|
||||
|
@ -74,27 +74,21 @@ impl Security {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn aes_cbc_encrypt(&self, raw: &[u8], key: &[u8; 32]) -> Vec<u8> {
|
||||
pub fn aes_cbc_encrypt(raw: &[u8], key: &[u8; 32]) -> Vec<u8> {
|
||||
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
|
||||
|
||||
debug_assert_eq!(raw.len() % key.len(), 0);
|
||||
debug_assert_eq!(raw.len() % Aes256CbcEnc::block_size(), 0);
|
||||
|
||||
raw.chunks(key.len())
|
||||
.map(|r| {
|
||||
Aes256CbcEnc::new(key.into(), &Self::IV.into())
|
||||
.encrypt_padded_vec_mut::<NoPadding>(r)
|
||||
})
|
||||
.flatten()
|
||||
.collect()
|
||||
Aes256CbcEnc::new(key.into(), &Self::IV.into()).encrypt_padded_vec_mut::<NoPadding>(raw)
|
||||
}
|
||||
|
||||
pub fn aes_cbc_decrypt(&self, raw: [u8; 32], key: &[u8; 32]) -> [u8; 32] {
|
||||
pub fn aes_cbc_decrypt(raw: &[u8], key: &[u8; 32]) -> Vec<u8> {
|
||||
type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
|
||||
|
||||
debug_assert_eq!(raw.len() % Aes256CbcDec::block_size(), 0);
|
||||
|
||||
Aes256CbcDec::new(key.into(), &Self::IV.into())
|
||||
.decrypt_padded_vec_mut::<NoPadding>(&raw)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.decrypt_padded_vec_mut::<NoPadding>(raw)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
|
@ -103,15 +97,9 @@ impl Security {
|
|||
bail!("authentication failed! (code ERROR)");
|
||||
}
|
||||
|
||||
let payload: [u8; 32] = response[0..32]
|
||||
.iter()
|
||||
.map(|&b| b)
|
||||
.collect::<Vec<u8>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let payload = &response[0..32];
|
||||
let sign = &response[32..];
|
||||
let result = self.aes_cbc_decrypt(payload, &key);
|
||||
let result = Self::aes_cbc_decrypt(payload, &key);
|
||||
|
||||
if Sha256::digest(&result).into_iter().collect::<Vec<u8>>() != sign {
|
||||
bail!("sign does not match");
|
||||
|
@ -168,7 +156,7 @@ impl Security {
|
|||
.into_iter()
|
||||
.collect();
|
||||
|
||||
data = self.aes_cbc_encrypt(&data, self.tcp_key.as_ref().unwrap());
|
||||
data = Self::aes_cbc_encrypt(&data, self.tcp_key.as_ref().unwrap());
|
||||
data.extend(sign);
|
||||
}
|
||||
|
||||
|
@ -209,14 +197,24 @@ impl Security {
|
|||
let msgtype = header[5] & 0xf;
|
||||
data = data[6..].to_vec();
|
||||
|
||||
let header_ref = b"\x83p\x00\x8e c";
|
||||
|
||||
assert_eq!(header_ref, header.as_slice());
|
||||
|
||||
if msgtype == MsgType::ENCRYPTED_RESPONSE as u8
|
||||
|| msgtype == MsgType::ENCRYPTED_REQUEST as u8
|
||||
{
|
||||
let sign = data[(data.len() - 32)..].to_vec();
|
||||
// let sign_ref = b"\xf5\x1c\x93\x18\xa3\xb9HR.X\x9aFU\xb1\xc4N\xe8J\x1a\x04\x0f\xd4\x90\xc4\x132\xeby\xa6\x83\x06e";
|
||||
|
||||
// assert_eq!(sign, sign_ref);
|
||||
|
||||
data = data[..(data.len() - 32)].to_vec();
|
||||
data = self
|
||||
.aes_cbc_decrypt(data.try_into().unwrap(), &self.tcp_key.unwrap())
|
||||
.to_vec();
|
||||
let data_ref = b"\xb2\x174\xbb\xf4\xf1\x8fWg\xc4\x97h%\x06\x9aCIL\xf6tf\x86\x8b\x83\xac\xa9@\xb1r\xb3\xa0>>\xd0_#\x03\x93\xcdZ[\x17\xde\xd8\xaeT\x1a\xd0\x0f\xa5\x8fk\xebJF\x0b\xfd\xf8\\o_\x06\xe9\xdd\xda45\x0b\x90\xf1\x82*\x9f\xa4\x1a\xc3\xb3\xde]\x04\x9e\x9c\xafy\xcfq\x02\xa34\xa4\x15\x0b\xcb\t\x96V\xd3f\x14\xd6\xf5X]\xc3'\xbc\x1b\xc1\x10\x08\x9c\x8d";
|
||||
|
||||
// assert_eq!(data, data_ref);
|
||||
|
||||
data = Self::aes_cbc_decrypt(&data, &self.tcp_key.unwrap()).to_vec();
|
||||
|
||||
let compare_sign: Vec<u8> = Sha256::digest(&[header.to_vec(), data.clone()].concat())
|
||||
.into_iter()
|
||||
|
@ -254,21 +252,35 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn aes_cbc_decrypt() {
|
||||
let payload: [u8; 32] =
|
||||
b",\xcbq_T\x81L\x96\xfa\xe7\xe4\xa7\xc5\xabU \r\xf5x\xd6\x08\x94_\\\xce\x8br\x1b\xa5\xbe\xc6\x1a"
|
||||
.iter()
|
||||
.map(|&b| b)
|
||||
.collect::<Vec<u8>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let payload =
|
||||
b",\xcbq_T\x81L\x96\xfa\xe7\xe4\xa7\xc5\xabU \r\xf5x\xd6\x08\x94_\\\xce\x8br\x1b\xa5\xbe\xc6\x1a";
|
||||
let key = b"*[R\x00\xc2\xc0ML\x81\x1d\x05P\xe1\xdc[1CT6\xb9[wM*\x88\xd7\xe4ma\xfd\x96i";
|
||||
let plain = b"\x9b\xaa\xdf\xff\x07\x1a\xd2\xe4\xb7TY\xe2\xf9\x8c\xdf\xe7!+\xda\xe4\x86GY\xe6j\x94\xdb\xe7\xb9b\xda\xe6";
|
||||
|
||||
let security = Security::default();
|
||||
|
||||
let result = security.aes_cbc_decrypt(payload, key);
|
||||
let result = Security::aes_cbc_decrypt(payload, key);
|
||||
|
||||
assert_eq!(&result, plain);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xor() {
|
||||
let lhs = b"\xf8\x9c\xd9\xb4{>X\x95Y\xff\xd9\x95\x98nY\x95\x89\x8eY\x95\xe9\x86Y\x95\xe1\xb6Y\x95\xd1\xb2Y\x95";
|
||||
let rhs = b"P\xe7yG\xdccBm\xb3\x88<v\x16a4\x10\x04K\x02V\x7fR@\xa5\xba\xf7\x899\x1b.Zy";
|
||||
|
||||
let res = Security::xorstr(lhs, rhs);
|
||||
|
||||
assert_eq!(res, b"\xa8{\xa0\xf3\xa7]\x1a\xf8\xeaw\xe5\xe3\x8e\x0fm\x85\x8d\xc5[\xc3\x96\xd4\x190[A\xd0\xac\xca\x9c\x03\xec");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aes_cbc_decrypt_padding() {
|
||||
let data = b"~\xb3\xb2\xa3x\t\x95\\\xe5U\x08\xba\t\xafe\xd0M\x0f\xf2\xdb\xda!\xb9\xab\x08#\xcf\xb3\x1f\xb3K\xa5\x85\x97\x06\xb4s\xaa\x11\xde\xb7\xf4%<J~\x9d\xdd\x9c8\xa1+\xdd\xa2\xdb\xf0\xc5)\xb3Oa\xb0\xbd\xbf\xf8\xd0Ea~f*\xc7}\xa9\x89\xb8\x9b\x89\xd8\xaci\x11S}\xd4Z\x981z\xed\xbe\xcf\xc8V\xe8\xe9\x19.\x8d\xc9\xf6`\xb7\xc3\xf7\x98O$\x19\x0bUz";
|
||||
let key = b"V\x103\xba\xa0W\x85\xaa\x0c\x01q\xb7\x94\t\x7f\xd4\xe7=L\x91\x02\x14\x8d`\xf7~\xc6\xfd\x99?\x14\xbc";
|
||||
|
||||
let res_ref = b"\x00\x00ZZ\x01\x11h\x00@\x00\xfe\x11\x00\x00\xe9C1\x13\x02\n\x17\x14\x02\x86\x02\x00\x00\x8b\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80\x00\x00\x00\x00\xb9qa\xcbB\xe7\x16\x97 b\xc8\xcd\x03N\x10\xd0\x94\xd9S\x10\xf9\x88\xbdZOX,\n\xe7L'\x1d\xf2p\xa5\x15\xf6\x9d\x1d>\x84z\xfe\xda\xd6Gx\x1d\n\xf6F\xac\xe9\xcd\xc5\xb9\xb8\x98\xae\x1b\n\xab\xc9\xee\x9a\xb6\xd2\xc5=:";
|
||||
let res = Security::aes_cbc_decrypt(data, key);
|
||||
|
||||
assert_eq!(res.len(), res_ref.len());
|
||||
assert_eq!(res, res_ref);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue