use packed_struct::prelude::{
packed_bits, Integer, PackedStruct, PackedStructSlice, PrimitiveEnum_u8,
};
use crate::{network::util::compute_generic_checksum, traits::CommandTrait};
#[derive(PrimitiveEnum_u8, Debug, Copy, Clone)]
pub enum HvacDataCommand {
SetState = 0x00,
GetState = 0x01,
GetAcInfo = 0x02,
}
#[derive(PrimitiveEnum_u8, Clone, Copy, Debug)]
pub enum HvacMode {
Auto = 0,
Cool = 1,
Dry = 2,
Heat = 3,
Fan = 4,
}
#[derive(PrimitiveEnum_u8, Clone, Copy, Debug)]
pub enum HvacSpeed {
None = 0,
High = 1,
Mid = 2,
Low = 3,
Auto = 5,
}
#[derive(PrimitiveEnum_u8, Clone, Copy, Debug)]
pub enum HvacPreset {
Normal = 0,
Turbo = 1,
Mute = 2,
}
#[derive(PrimitiveEnum_u8, Clone, Copy, Debug)]
pub enum HvacSwHoriz {
LeftFix = 2,
LeftRightFix = 7,
RightFix = 6,
RightFlap = 5,
On = 0,
Off = 1,
}
#[derive(PrimitiveEnum_u8, Clone, Copy, Debug)]
pub enum HvacSwVert {
On = 0,
Pos1 = 1,
Pos2 = 2,
Pos3 = 3,
Pos4 = 4,
Pos5 = 5,
Off = 7,
}
#[derive(PackedStruct, Debug)]
#[packed_struct(bit_numbering = "msb0", size_bytes = "13")]
pub struct AirCondState {
#[packed_field(bits = "66")]
pub power: bool,
#[packed_field(bits = "0..=4")]
target_temp_int: Integer<u8, packed_bits::Bits<5>>,
#[packed_field(bits = "5..=7", ty = "enum")]
pub swing_v: HvacSwVert,
#[packed_field(bits = "8..=10", ty = "enum")]
pub swing_h: HvacSwHoriz,
#[packed_field(bits = "40..=42", ty = "enum")]
pub mode: HvacMode,
#[packed_field(bits = "20..=23")]
magic1: Integer<u8, packed_bits::Bits<4>>,
#[packed_field(bits = "24..=26", ty = "enum")]
pub fanspeed: HvacSpeed,
#[packed_field(bits = "38..=39", ty = "enum")]
pub preset: HvacPreset,
#[packed_field(bits = "45")]
pub sleep: bool,
#[packed_field(bits = "44")]
pub ifeel: bool,
#[packed_field(bits = "70")]
pub health: bool,
#[packed_field(bits = "69")]
pub clean: bool,
#[packed_field(bits = "83")]
pub display: bool,
#[packed_field(bits = "84")]
pub mildew: bool,
}
impl AirCondState {
pub fn prepare_and_pack(&mut self) -> Result<Vec<u8>, String> {
self.magic1 = 0x0f.into();
Ok(self
.pack()
.map_err(|e| format!("Could not pack message! {}", e))?
.to_vec())
}
pub fn get_target_temp(&self) -> f32 {
u8::from(self.target_temp_int) as f32 + 8.0
}
pub fn set_target_temp(&mut self, input: f32) -> Result<(), String> {
if input < 16.0 || input > 32.0 {
return Err("Target temperature is out of range (16-32)".into());
}
self.target_temp_int = (input as u8 - 8).into();
Ok(())
}
}
#[derive(PackedStruct, Debug)]
#[packed_struct(bit_numbering = "msb0", size_bytes = "22")]
pub struct AirCondInfo {
#[packed_field(bits = "15")]
pub power: bool,
#[packed_field(bits = "43..=47")]
ambient_temp_int: Integer<u8, packed_bits::Bits<5>>,
#[packed_field(bits = "171..=175")]
ambient_temp_fract: Integer<u8, packed_bits::Bits<5>>,
}
impl AirCondInfo {
pub fn get_ambient_temp(&self) -> f32 {
u8::from(self.ambient_temp_int) as f32 + u8::from(self.ambient_temp_fract) as f32 / 10.0
}
}
#[derive(PackedStruct, Debug)]
#[packed_struct(bit_numbering = "msb0", endian = "lsb", size_bytes = "12")]
pub struct HvacDataMessage {
#[packed_field(bytes = "0x00:0x01")]
payload_length: u16,
#[packed_field(bytes = "0x02:0x03")]
magic1: u16,
#[packed_field(bytes = "0x04:0x05")]
magic2: u16,
#[packed_field(bytes = "0x06:0x07")]
magic3: u16,
#[packed_field(bytes = "0x08:0x09")]
data_length: u16,
#[packed_field(bytes = "0x0a:0x0b")]
command: u16,
}
impl HvacDataMessage {
pub fn new(command_type: HvacDataCommand) -> HvacDataMessage {
return HvacDataMessage {
payload_length: 0,
command: (1 << 8) as u16 + ((command_type as u8) << 4 | 1) as u16,
magic1: 0x00BBu16,
magic2: 0x8006u16,
magic3: 0,
data_length: 2,
};
}
pub fn pack_with_payload(mut self, payload: &[u8]) -> Result<Vec<u8>, String> {
self.data_length += <usize as TryInto<u16>>::try_into(payload.len())
.map_err(|e| format!("Payload is too long! {}", e))?;
self.payload_length = self
.data_length
.checked_add(10u16)
.ok_or_else(|| "Could not add the start buffer! Payload is too long")?;
let mut result = self
.pack()
.map_err(|e| format!("Could not pack message! {}", e))?
.to_vec();
result.extend(payload);
let checksum = compute_generic_checksum(&result[2..]);
result.extend(checksum.to_le_bytes().to_vec());
return Ok(result);
}
pub fn unpack_with_payload(bytes: &[u8]) -> Result<Vec<u8>, String> {
let command_header = HvacDataMessage::unpack_from_slice(&bytes[0..12])
.map_err(|e| format!("Could not unpack command from bytes! {}", e))?;
let real_size: u16 = (bytes.len() as u16) - 2;
if real_size != command_header.payload_length {
return Err(format!(
"Command checksum does not match actual checksum! Expected {:#06X} got {:#06X}",
command_header.payload_length, real_size,
));
}
let crc_offset = usize::from(command_header.payload_length);
let data_crc = u16::from_le_bytes([bytes[crc_offset], bytes[crc_offset + 1]]);
let real_checksum = compute_generic_checksum(&bytes[0x02..crc_offset]);
if data_crc != real_checksum {
return Err(format!(
"Data checksum does not match actual checksum! Expected {:#06X} got {:#06X}",
data_crc, real_checksum,
));
}
let data = &bytes[0x0C..0x0C + usize::from(command_header.data_length - 2)];
return Ok(data.to_vec());
}
}
impl CommandTrait for HvacDataMessage {
fn packet_type() -> u16 {
return 0x006A;
}
}