Midea/src/device.rs

150 lines
3.9 KiB
Rust
Raw Normal View History

2023-09-24 11:16:57 +00:00
use std::{
io::{Read, Write},
net::TcpStream,
thread,
time::Duration,
};
2023-09-24 05:35:21 +00:00
2023-09-24 11:16:57 +00:00
use anyhow::{bail, Context, Error, Result};
2023-09-24 05:35:21 +00:00
use crate::{
2023-09-24 11:16:57 +00:00
hex,
2023-09-24 05:35:21 +00:00
security::{MsgType, Security},
DeviceInfo,
};
pub struct Device {
info: DeviceInfo,
2023-09-24 11:16:57 +00:00
socket: TcpStream,
2023-09-24 05:35:21 +00:00
security: Security,
2023-09-24 11:16:57 +00:00
token: [u8; 64],
key: [u8; 32],
2023-09-24 05:35:21 +00:00
}
impl Device {
2023-09-24 11:16:57 +00:00
pub fn connect(info: DeviceInfo, token: &str, key: &str) -> Result<Self> {
let mut socket = Err(Error::msg(""));
for _ in 0..10 {
socket = TcpStream::connect(info.addr).context(info.addr);
if socket.is_ok() {
break;
}
thread::sleep(Duration::from_millis(500));
}
let socket = socket?;
2023-09-24 05:35:21 +00:00
socket.set_write_timeout(Some(Duration::from_secs(10)))?;
socket.set_read_timeout(Some(Duration::from_secs(10)))?;
let mut me = Self {
info,
socket,
security: Security::default(),
2023-09-24 11:16:57 +00:00
token: hex(token)?.try_into().unwrap(),
key: hex(key)?.try_into().unwrap(),
2023-09-24 05:35:21 +00:00
};
if me.info.protocol == 3 {
me.authenticate()?;
}
me.refresh_status()?;
Ok(me)
}
fn authenticate(&mut self) -> Result<()> {
2023-09-24 11:16:57 +00:00
let request = self
.security
.encode_8370(&self.token, MsgType::HANDSHAKE_REQUEST)?;
self.socket.write(&request)?;
let mut buffer = [0; 512];
let bytes_read = self.socket.read(&mut buffer)?;
if bytes_read < 20 {
2023-09-25 09:30:54 +00:00
bail!(
"Authentication failed! (answer too short) {:?}",
&buffer[..bytes_read]
);
2023-09-24 11:16:57 +00:00
}
self.security.tcp_key(&buffer[8..72], &self.key)?;
2023-09-24 05:35:21 +00:00
Ok(())
}
pub fn refresh_status(&self) -> Result<()> {
//
Ok(())
}
}
#[cfg(test)]
mod test {
2023-09-25 09:30:54 +00:00
use anyhow::{Context, Result};
2023-09-24 11:16:57 +00:00
use futures::future::try_join;
2023-09-25 09:30:54 +00:00
use serial_test::serial;
2023-09-24 05:35:21 +00:00
2023-09-24 11:16:57 +00:00
use crate::{device::Device, Cloud, Startup};
2023-09-24 05:35:21 +00:00
#[tokio::test]
2023-09-25 09:30:54 +00:00
async fn verify_hex() -> Result<()> {
2023-09-24 11:16:57 +00:00
let devices = Startup::discover().await?;
2023-09-25 09:30:54 +00:00
const PY_TOKEN: &str = "06df24fc4e8e950c6d9783051b8e38d971e5fbc617da259459d30d5e7d7fc05b4ccb708fe3a085f6f0af0f8cc961fa39dabfd0746f7bbcfbf7404d9cc5c2b077";
const PY_KEY: &str = "2a5b5200c2c04d4c811d0550e1dc5b31435436b95b774d2a88d7e46d61fd9669";
2023-09-24 11:16:57 +00:00
let token_hex = b"\x06\xdf$\xfcN\x8e\x95\x0cm\x97\x83\x05\x1b\x8e8\xd9q\xe5\xfb\xc6\x17\xda%\x94Y\xd3\r^}\x7f\xc0[L\xcbp\x8f\xe3\xa0\x85\xf6\xf0\xaf\x0f\x8c\xc9a\xfa9\xda\xbf\xd0to{\xbc\xfb\xf7@M\x9c\xc5\xc2\xb0w";
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 {
2023-09-25 09:30:54 +00:00
let device = Device::connect(device_info, PY_TOKEN, PY_KEY)?;
2023-09-24 11:16:57 +00:00
assert_eq!(&device.token, token_hex);
assert_eq!(&device.key, key_hex);
}
Ok(())
}
#[tokio::test]
2023-09-25 09:30:54 +00:00
async fn connect_py_token() -> Result<()> {
let devices = Startup::discover().await?;
2023-09-24 11:16:57 +00:00
2023-09-25 09:30:54 +00:00
const PY_TOKEN: &str = "18a821cb88293c6552dc576f0672d8b9445205f74b636764929de5e8badfa48a24caa9d741f632a18e1a9fee67c40b0b40edc21ac7c4c40b6352181cd4000203";
const PY_KEY: &str = "0fc0c56ea8124414a362e6449ee45ba92558a54f159d4937af697e405f2326b9";
2023-09-24 11:16:57 +00:00
for device_info in devices {
2023-09-25 09:30:54 +00:00
Device::connect(device_info, PY_TOKEN, PY_KEY)?;
}
Ok(())
}
#[tokio::test]
#[serial]
async fn full_flow() -> Result<()> {
let mut cloud = Cloud::new("michaelh.95@t-online.de", "Hoda.semi1")?;
2023-09-24 11:16:57 +00:00
2023-09-25 09:30:54 +00:00
let (_, devices) = try_join(cloud.login(), Startup::discover()).await?;
2023-09-24 11:16:57 +00:00
2023-09-25 09:30:54 +00:00
for device_info in devices {
let (token, key) = cloud.keys(device_info.id).await?;
Device::connect(device_info, &token, &key)
.context(format!("\ntoken: {token}\nkey: {key}"))?;
}
2023-09-24 11:16:57 +00:00
Ok(())
}
2023-09-24 05:35:21 +00:00
}