use super::super::types::AttributeValue;
use super::types::cluster_id;
pub const CMD_ON_OFF_OFF: u8 = 0x00;
pub const CMD_ON_OFF_ON: u8 = 0x01;
pub const CMD_ON_OFF_TOGGLE: u8 = 0x02;
pub const ATTR_ON_OFF_ON_OFF: u16 = 0x0000;
pub fn on_off_command(on: bool) -> (u16, u8, Vec<u8>) {
let cmd = if on { CMD_ON_OFF_ON } else { CMD_ON_OFF_OFF };
(cluster_id::ON_OFF, cmd, vec![])
}
pub fn toggle_command() -> (u16, u8, Vec<u8>) {
(cluster_id::ON_OFF, CMD_ON_OFF_TOGGLE, vec![])
}
pub const CMD_LEVEL_MOVE_TO_LEVEL: u8 = 0x00;
pub const CMD_LEVEL_MOVE_TO_LEVEL_ON_OFF: u8 = 0x04;
pub const ATTR_LEVEL_CURRENT_LEVEL: u16 = 0x0000;
pub fn move_to_level(level: u8, transition_time_ds: u16, with_on_off: bool) -> (u16, u8, Vec<u8>) {
let cmd = if with_on_off {
CMD_LEVEL_MOVE_TO_LEVEL_ON_OFF
} else {
CMD_LEVEL_MOVE_TO_LEVEL
};
let mut payload = vec![level];
payload.extend_from_slice(&transition_time_ds.to_le_bytes());
(cluster_id::LEVEL_CONTROL, cmd, payload)
}
pub const CMD_COLOR_MOVE_TO_HUE_SAT: u8 = 0x06;
pub const CMD_COLOR_MOVE_TO_COLOR_TEMP: u8 = 0x0A;
pub const ATTR_COLOR_CURRENT_HUE: u16 = 0x0000;
pub const ATTR_COLOR_CURRENT_SAT: u16 = 0x0001;
pub const ATTR_COLOR_COLOR_TEMP: u16 = 0x0007;
pub const ATTR_COLOR_COLOR_MODE: u16 = 0x0008;
pub fn move_to_hue_sat(hue: u8, sat: u8, transition_time_ds: u16) -> (u16, u8, Vec<u8>) {
let mut payload = vec![hue, sat];
payload.extend_from_slice(&transition_time_ds.to_le_bytes());
(
cluster_id::COLOR_CONTROL,
CMD_COLOR_MOVE_TO_HUE_SAT,
payload,
)
}
pub fn move_to_color_temp(mireds: u16, transition_time_ds: u16) -> (u16, u8, Vec<u8>) {
let mut payload = Vec::new();
payload.extend_from_slice(&mireds.to_le_bytes());
payload.extend_from_slice(&transition_time_ds.to_le_bytes());
(
cluster_id::COLOR_CONTROL,
CMD_COLOR_MOVE_TO_COLOR_TEMP,
payload,
)
}
pub const ATTR_TEMP_MEASURED_VALUE: u16 = 0x0000;
pub const ATTR_TEMP_MIN_MEASURED: u16 = 0x0001;
pub const ATTR_TEMP_MAX_MEASURED: u16 = 0x0002;
pub const ATTR_TEMP_TOLERANCE: u16 = 0x0003;
pub fn decode_temperature(raw: &AttributeValue) -> Option<f32> {
match raw {
AttributeValue::I16(v) => {
if *v == i16::MIN {
None } else {
Some(*v as f32 / 100.0)
}
}
AttributeValue::U16(v) => Some(*v as i16 as f32 / 100.0),
_ => None,
}
}
pub const ATTR_HUMIDITY_MEASURED_VALUE: u16 = 0x0000;
pub fn decode_humidity(raw: &AttributeValue) -> Option<f32> {
match raw {
AttributeValue::U16(v) => {
if *v > 10_000 {
None } else {
Some(*v as f32 / 100.0)
}
}
_ => None,
}
}
pub const ATTR_OCCUPANCY_OCCUPANCY: u16 = 0x0000;
pub const CMD_DOOR_LOCK_LOCK: u8 = 0x00;
pub const CMD_DOOR_LOCK_UNLOCK: u8 = 0x01;
pub const ATTR_DOOR_LOCK_STATE: u16 = 0x0000;
pub fn door_lock_command(lock: bool, pin: &[u8]) -> (u16, u8, Vec<u8>) {
let cmd = if lock {
CMD_DOOR_LOCK_LOCK
} else {
CMD_DOOR_LOCK_UNLOCK
};
let mut payload = vec![pin.len() as u8];
payload.extend_from_slice(pin);
(cluster_id::DOOR_LOCK, cmd, payload)
}
pub const ATTR_IAS_ZONE_STATUS: u16 = 0x0002;
pub const ATTR_IAS_ZONE_TYPE: u16 = 0x0001;
pub const IAS_ZONE_TYPE_CONTACT: u16 = 0x0015;
pub const IAS_ZONE_TYPE_MOTION: u16 = 0x000D;
pub const IAS_ZONE_TYPE_SMOKE: u16 = 0x0028;
pub const IAS_ZONE_TYPE_CO: u16 = 0x002B;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn on_off_command_on() {
let (cl, cmd, payload) = on_off_command(true);
assert_eq!(cl, cluster_id::ON_OFF);
assert_eq!(cmd, CMD_ON_OFF_ON);
assert!(payload.is_empty());
}
#[test]
fn on_off_command_off() {
let (cl, cmd, payload) = on_off_command(false);
assert_eq!(cl, cluster_id::ON_OFF);
assert_eq!(cmd, CMD_ON_OFF_OFF);
assert!(payload.is_empty());
}
#[test]
fn toggle_command_correct() {
let (cl, cmd, _) = toggle_command();
assert_eq!(cl, cluster_id::ON_OFF);
assert_eq!(cmd, CMD_ON_OFF_TOGGLE);
}
#[test]
fn move_to_level_payload() {
let (cl, cmd, payload) = move_to_level(127, 10, false);
assert_eq!(cl, cluster_id::LEVEL_CONTROL);
assert_eq!(cmd, CMD_LEVEL_MOVE_TO_LEVEL);
assert_eq!(payload, vec![127, 10, 0]);
}
#[test]
fn move_to_level_with_on_off() {
let (_, cmd, _) = move_to_level(200, 5, true);
assert_eq!(cmd, CMD_LEVEL_MOVE_TO_LEVEL_ON_OFF);
}
#[test]
fn decode_temperature_valid() {
assert_eq!(decode_temperature(&AttributeValue::I16(2350)), Some(23.5));
}
#[test]
fn decode_temperature_invalid() {
assert_eq!(decode_temperature(&AttributeValue::I16(i16::MIN)), None);
}
#[test]
fn decode_humidity_valid() {
assert_eq!(decode_humidity(&AttributeValue::U16(4500)), Some(45.0));
}
#[test]
fn decode_humidity_out_of_range() {
assert_eq!(decode_humidity(&AttributeValue::U16(10_001)), None);
}
#[test]
fn door_lock_lock_no_pin() {
let (cl, cmd, payload) = door_lock_command(true, &[]);
assert_eq!(cl, cluster_id::DOOR_LOCK);
assert_eq!(cmd, CMD_DOOR_LOCK_LOCK);
assert_eq!(payload, vec![0x00]); }
#[test]
fn move_to_color_temp_payload() {
let (cl, cmd, payload) = move_to_color_temp(300, 20);
assert_eq!(cl, cluster_id::COLOR_CONTROL);
assert_eq!(cmd, CMD_COLOR_MOVE_TO_COLOR_TEMP);
assert_eq!(payload, vec![0x2C, 0x01, 0x14, 0x00]);
}
}