#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum CommandClass {
Basic = 0x20,
SwitchBinary = 0x25,
SwitchMultilevel = 0x26,
SwitchAll = 0x27,
SwitchColor = 0x33,
SensorBinary = 0x30,
SensorMultilevel = 0x31,
Meter = 0x32,
Notification = 0x71,
ThermostatMode = 0x40,
ThermostatSetpoint = 0x43,
ThermostatFanMode = 0x44,
ThermostatOperatingState = 0x42,
DoorLock = 0x62,
UserCode = 0x63,
Battery = 0x80,
Association = 0x85,
MultiChannelAssociation = 0x8E,
Configuration = 0x70,
Version = 0x86,
ManufacturerSpecific = 0x72,
WakeUp = 0x84,
Security = 0x98,
Security2 = 0x9F,
TransportService = 0x55,
ZWavePlusInfo = 0x5E,
MultiChannel = 0x60,
Unknown(u8),
}
impl CommandClass {
pub fn id(&self) -> u8 {
match self {
Self::Basic => 0x20,
Self::SwitchBinary => 0x25,
Self::SwitchMultilevel => 0x26,
Self::SwitchAll => 0x27,
Self::SwitchColor => 0x33,
Self::SensorBinary => 0x30,
Self::SensorMultilevel => 0x31,
Self::Meter => 0x32,
Self::Notification => 0x71,
Self::ThermostatMode => 0x40,
Self::ThermostatSetpoint => 0x43,
Self::ThermostatFanMode => 0x44,
Self::ThermostatOperatingState => 0x42,
Self::DoorLock => 0x62,
Self::UserCode => 0x63,
Self::Battery => 0x80,
Self::Association => 0x85,
Self::MultiChannelAssociation => 0x8E,
Self::Configuration => 0x70,
Self::Version => 0x86,
Self::ManufacturerSpecific => 0x72,
Self::WakeUp => 0x84,
Self::Security => 0x98,
Self::Security2 => 0x9F,
Self::TransportService => 0x55,
Self::ZWavePlusInfo => 0x5E,
Self::MultiChannel => 0x60,
Self::Unknown(id) => *id,
}
}
pub fn from_id(id: u8) -> Self {
match id {
0x20 => Self::Basic,
0x25 => Self::SwitchBinary,
0x26 => Self::SwitchMultilevel,
0x27 => Self::SwitchAll,
0x33 => Self::SwitchColor,
0x30 => Self::SensorBinary,
0x31 => Self::SensorMultilevel,
0x32 => Self::Meter,
0x71 => Self::Notification,
0x40 => Self::ThermostatMode,
0x43 => Self::ThermostatSetpoint,
0x44 => Self::ThermostatFanMode,
0x42 => Self::ThermostatOperatingState,
0x62 => Self::DoorLock,
0x63 => Self::UserCode,
0x80 => Self::Battery,
0x85 => Self::Association,
0x8E => Self::MultiChannelAssociation,
0x70 => Self::Configuration,
0x86 => Self::Version,
0x72 => Self::ManufacturerSpecific,
0x84 => Self::WakeUp,
0x98 => Self::Security,
0x9F => Self::Security2,
0x55 => Self::TransportService,
0x5E => Self::ZWavePlusInfo,
0x60 => Self::MultiChannel,
other => Self::Unknown(other),
}
}
}
pub fn switch_binary_set(on: bool) -> Vec<u8> {
vec![
CommandClass::SwitchBinary.id(),
0x01,
if on { 0xFF } else { 0x00 },
]
}
pub fn switch_binary_get() -> Vec<u8> {
vec![CommandClass::SwitchBinary.id(), 0x02]
}
pub fn switch_multilevel_set(level: u8, duration: u8) -> Vec<u8> {
vec![
CommandClass::SwitchMultilevel.id(),
0x01,
level.min(99),
duration,
]
}
pub fn switch_multilevel_get() -> Vec<u8> {
vec![CommandClass::SwitchMultilevel.id(), 0x02]
}
pub fn sensor_multilevel_get(sensor_type: u8) -> Vec<u8> {
vec![CommandClass::SensorMultilevel.id(), 0x04, sensor_type, 0x00]
}
pub fn thermostat_setpoint_set(setpoint_type: u8, value_tenths_celsius: i16) -> Vec<u8> {
let level = (1 << 5) | (0 << 3) | 2; let bytes = value_tenths_celsius.to_be_bytes();
vec![
CommandClass::ThermostatSetpoint.id(),
0x01, setpoint_type,
level,
bytes[0],
bytes[1],
]
}
pub fn thermostat_setpoint_get(setpoint_type: u8) -> Vec<u8> {
vec![CommandClass::ThermostatSetpoint.id(), 0x02, setpoint_type]
}
pub fn door_lock_set(locked: bool) -> Vec<u8> {
vec![
CommandClass::DoorLock.id(),
0x01, if locked { 0xFF } else { 0x00 },
]
}
pub fn door_lock_get() -> Vec<u8> {
vec![CommandClass::DoorLock.id(), 0x02]
}
pub fn battery_get() -> Vec<u8> {
vec![CommandClass::Battery.id(), 0x02]
}
pub fn configuration_set(param_no: u8, value: i32, size: u8) -> Vec<u8> {
let mut buf = vec![CommandClass::Configuration.id(), 0x04, param_no, size];
match size {
1 => buf.push(value as u8),
2 => buf.extend_from_slice(&(value as i16).to_be_bytes()),
4 => buf.extend_from_slice(&value.to_be_bytes()),
_ => buf.push(value as u8),
}
buf
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn command_class_binary_switch_set_on() {
let cmd = switch_binary_set(true);
assert_eq!(cmd, vec![0x25, 0x01, 0xFF]);
}
#[test]
fn command_class_binary_switch_set_off() {
let cmd = switch_binary_set(false);
assert_eq!(cmd, vec![0x25, 0x01, 0x00]);
}
#[test]
fn command_class_binary_switch_get() {
assert_eq!(switch_binary_get(), vec![0x25, 0x02]);
}
#[test]
fn command_class_multilevel_set_50_percent() {
let cmd = switch_multilevel_set(50, 0);
assert_eq!(cmd, vec![0x26, 0x01, 50, 0]);
}
#[test]
fn command_class_multilevel_set_clamps_at_99() {
let cmd = switch_multilevel_set(100, 0);
assert_eq!(cmd[2], 99);
}
#[test]
fn command_class_sensor_multilevel_get() {
let cmd = sensor_multilevel_get(0x01); assert_eq!(cmd[0], CommandClass::SensorMultilevel.id());
assert_eq!(cmd[2], 0x01);
}
#[test]
fn command_class_thermostat_setpoint_set_heating() {
let cmd = thermostat_setpoint_set(0x01, 215);
assert_eq!(cmd[0], CommandClass::ThermostatSetpoint.id());
assert_eq!(cmd[1], 0x01); assert_eq!(cmd[2], 0x01); let val = i16::from_be_bytes([cmd[4], cmd[5]]);
assert_eq!(val, 215);
}
#[test]
fn command_class_door_lock_set_locked() {
let cmd = door_lock_set(true);
assert_eq!(cmd, vec![0x62, 0x01, 0xFF]);
}
#[test]
fn command_class_door_lock_set_unlocked() {
let cmd = door_lock_set(false);
assert_eq!(cmd, vec![0x62, 0x01, 0x00]);
}
#[test]
fn command_class_unknown_passthrough() {
let cc = CommandClass::from_id(0xEE);
assert!(matches!(cc, CommandClass::Unknown(0xEE)));
assert_eq!(cc.id(), 0xEE);
}
#[test]
fn command_class_roundtrip_known() {
for id in [0x25u8, 0x26, 0x31, 0x62, 0x80] {
let cc = CommandClass::from_id(id);
assert_eq!(cc.id(), id, "roundtrip failed for {id:#04x}");
}
}
}