at4_protocol 2.1.1

A rust crate that parses AirTouch 4 messages.
Documentation
use crc16::MODBUS;

use crate::{messaging::MessageType, states};

/// The group control message. Send this message to the AT4 unit to change the
/// state of a group.
#[derive(Debug, Clone, Copy)]
pub struct GroupControlMessage {
    /// Valid values are `0`-`15`.
    pub group_number: u8,

    /// The target that the AC should be aiming for; that is, a specific
    /// temperature or percentage value.
    pub target_value: states::SetGroupSettingValue,

    /// If setting_value is not `Maintain`, `Decrease`, or `Increase`, this
    /// will be ignored.
    pub control_method: states::SetGroupControlMethod,

    /// Whether or not the group is on/off, or in turbo.
    pub power: states::SetGroupPower,
}

impl GroupControlMessage {
    pub fn new(
        group_number: u8,
        target_value: states::SetGroupSettingValue,
        control_method: states::SetGroupControlMethod,
        power: states::SetGroupPower,
    ) -> Self {
        Self {
            group_number,
            target_value,
            control_method,
            power,
        }
    }

    /// Create a `GroupControlMessage` that will turn off the specified group.
    pub fn set_off(group_number: u8) -> Self {
        Self::new(
            group_number,
            states::SetGroupSettingValue::Maintain,
            states::SetGroupControlMethod::Maintain,
            states::SetGroupPower::SetOff,
        )
    }

    /// Convert the `GroupControlMessage` into the raw message bytes.
    pub fn build(&self) -> [u8; 14] {
        assert!(self.group_number <= 15);

        let mut value: u8 = 0;
        let target_value = match self.target_value {
            states::SetGroupSettingValue::Maintain => 0,
            states::SetGroupSettingValue::Decrease => 0b010,
            states::SetGroupSettingValue::Increase => 0b011,
            states::SetGroupSettingValue::SetOpenPercentage(v) => {
                assert!(v <= 100);
                value = v;
                0b100
            }
            states::SetGroupSettingValue::SetTargetSetpoint(v) => {
                value = v;
                0b101
            }
        };

        let control_method = match self.control_method {
            states::SetGroupControlMethod::Maintain => 0,
            states::SetGroupControlMethod::Toggle => 0b01,
            states::SetGroupControlMethod::Percentage => 0b10,
            states::SetGroupControlMethod::Temperature => 0b11,
        };

        let power = match self.power {
            states::SetGroupPower::Maintain => 0,
            states::SetGroupPower::ChangeToNextState => 0b001,
            states::SetGroupPower::SetOff => 0b010,
            states::SetGroupPower::SetOn => 0b011,
            states::SetGroupPower::SetTurbo => 0b101,
        };

        let byte2 = (target_value << 5) | (control_method << 3) | power;

        let crc = crc16::State::<MODBUS>::calculate(&[
            0x80,
            0xb0,
            0x01,
            MessageType::GROUP_CONTROL_MESSAGE,
            0x00,
            0x04,
            self.group_number,
            byte2,
            value,
            0,
        ]);

        return [
            0x55,
            0x55,
            0x80,
            0xb0,
            0x01,
            MessageType::GROUP_CONTROL_MESSAGE,
            0x00,
            0x04,
            self.group_number,
            byte2,
            value,
            0,
            (crc >> 8) as u8,
            (crc & 0xff) as u8,
        ];
    }
}

#[cfg(test)]
mod test {
    use super::GroupControlMessage;
    use crate::states;

    #[test]
    fn test_create_group_control_packet() {
        let packet = GroupControlMessage::new(
            1,
            states::SetGroupSettingValue::Maintain,
            states::SetGroupControlMethod::Maintain,
            states::SetGroupPower::SetOff,
        )
        .build();

        assert_eq!(
            packet,
            [0x55, 0x55, 0x80, 0xb0, 0x1, 0x2a, 0x0, 0x4, 0x1, 0x2, 0x0, 0x0, 0xda, 0x59]
        )
    }
}