use std::{
fmt,
net::{IpAddr, Ipv4Addr, SocketAddr},
str::from_utf8,
};
use packed_struct::prelude::{PackedStruct, PackedStructSlice};
use crate::{
network::{
util::{local_ip_or, send_and_receive_many, send_and_receive_one},
AuthenticationMessage, AuthenticationResponse, CommandMessage, DiscoveryMessage,
DiscoveryResponse, WirelessConnection, WirelessConnectionMessage,
},
traits::{CommandTrait, DeviceTrait},
DeviceInfo, HvacDevice, RemoteDevice, HVAC_CODES, REMOTE_CODES,
};
pub enum Device {
Remote { remote: RemoteDevice },
Hvac { hvac: HvacDevice },
}
impl Device {
pub fn from_ip(addr: Ipv4Addr, local_ip: Option<Ipv4Addr>) -> Result<Device, String> {
let selected_ip = local_ip_or(local_ip)?;
let port = 42424;
let discover = DiscoveryMessage::new(selected_ip, port, None)?;
let msg = discover
.pack()
.map_err(|e| format!("Could not pack DiscoveryMessage! {}", e))?;
return Ok(
send_and_receive_one(&msg, addr, Some(port), |bytes_received, bytes, addr| {
return create_device_from_packet(addr, bytes_received, bytes);
})
.map_err(|e| format!("Could not communicate with specified device! {}", e))?,
);
}
pub fn list(ip: Option<Ipv4Addr>) -> Result<Vec<Device>, String> {
let selected_ip = local_ip_or(ip)?;
let port = 42424;
let discover = DiscoveryMessage::new(selected_ip, port, None)?;
let msg = discover
.pack()
.map_err(|e| format!("Could not pack DiscoveryMessage! {}", e))?;
let results = send_and_receive_many(
&msg,
Ipv4Addr::BROADCAST,
Some(port),
|bytes_received, bytes, addr| {
return Ok(create_device_from_packet(addr, bytes_received, &bytes)
.map_err(|e| format!("Could not create device from packet! {}", e))?);
},
)
.map_err(|e| format!("Could not send discovery message! {}", e))?;
return Ok(results);
}
pub fn authenticate(&mut self) -> Result<(), String> {
let info = self.get_info();
let msg = AuthenticationMessage::new(&info.name);
let packed = msg
.pack()
.map_err(|e| format!("Could not pack Authentication message! {}", e))?;
let response = self
.send_command::<AuthenticationMessage>(&packed)
.map_err(|e| format!("Could not send authentication command! {}", e))?;
let auth = AuthenticationResponse::unpack_from_slice(&response)
.map_err(|e| format!("Could not unpack auth response! {}", e))?;
self.save_auth_pair(auth.id, auth.key);
return Ok(());
}
pub fn connect_to_network(
network: &WirelessConnection,
) -> Result<WirelessConnectionMessage, String> {
let msg = network
.to_message()
.map_err(|e| format!("Could not create wireless connection message! {}", e))?;
let packed = msg
.pack()
.map_err(|e| format!("Could not pack wireless connection message! {}", e))?;
send_and_receive_one(&packed, Ipv4Addr::BROADCAST, None, |_, _, _| {
return Ok(());
})
.map_err(|e| format!("Could not send connection message! {}", e))?;
return Ok(msg);
}
pub fn send_command<T>(&self, payload: &[u8]) -> Result<Vec<u8>, String>
where
T: CommandTrait,
{
let info = self.get_info();
let cmd = CommandMessage::new::<T>(info.model_code, info.mac, info.auth_id);
let packed = cmd
.pack_with_payload(&payload, &info.key)
.map_err(|e| format!("Could not pack command with payload! {}", e))?;
return send_and_receive_one(&packed, info.address, None, |_, bytes, _| {
return CommandMessage::unpack_with_payload(bytes.to_vec(), &info.key);
});
}
}
impl DeviceTrait for Device {
fn get_info(&self) -> DeviceInfo {
return match self {
Device::Remote { remote } => remote.info.clone(),
Device::Hvac { hvac } => hvac.info.clone(),
};
}
fn save_auth_pair(&mut self, id: u32, key: [u8; 16]) {
return match self {
Device::Remote { remote } => {
remote.info.auth_id = id;
remote.info.key = key;
}
Device::Hvac { hvac } => {
hvac.info.auth_id = id;
hvac.info.key = key;
}
};
}
}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let info = self.get_info();
write!(
f,
"{} [{} {:?}] (address = {}, mac = {}, locked? = {})",
info.name,
info.friendly_type,
info.friendly_model,
info.address,
info.mac
.iter()
.map(|x| format!("{:02X}", x))
.collect::<Vec<String>>()
.join(":"),
info.is_locked,
)
}
}
fn create_device_from_packet(
addr: SocketAddr,
bytes_received: usize,
bytes: &[u8],
) -> Result<Device, String> {
if bytes_received != 128 {
return Err("Received invalid response! Not enough data.".into());
}
let addr_ip = match addr.ip() {
IpAddr::V4(a) => a,
_ => return Err("Device has an IPv6 Address! This should be impossible...".into()),
};
let response = DiscoveryResponse::unpack_from_slice(&bytes)
.map_err(|e| format!("Could not unpack response from device! {}", e))?;
let raw_name = response.name.clone();
let name = from_utf8(&raw_name).map_err(|e| format!("Could not decode device name! {}", e))?;
let mut device = match &response.model_code {
_ if REMOTE_CODES.contains_key(&response.model_code) => Device::Remote {
remote: RemoteDevice::new(name, addr_ip, response),
},
_ if HVAC_CODES.contains_key(&response.model_code) => Device::Hvac {
hvac: HvacDevice::new(name, addr_ip, response),
},
_ => {
return Err(format!(
"Unknown device: {} ({:#06X})",
response.model_code, response.model_code
))
}
};
device
.authenticate()
.map_err(|e| format!("Could not authenticate device! {}", e))?;
return Ok(device);
}