at4_protocol 2.1.1

A rust crate that parses AirTouch 4 messages.
Documentation
use crate::states::{self, ACFanSpeed, ACMode, ACPowerState};

use super::{ac_status_message::ACStatus, ReceivableMessage};

/// Parse an ac status packet.
pub fn parse(bytes: &[u8]) -> Result<ReceivableMessage, &'static str> {
    let data_length: usize = (((bytes[6] as u16) << 2) | (bytes[7] as u16)) as usize;

    // Check length
    if data_length % 8 != 0 {
        return Err("invalid message data length to be an ac status message.");
    }

    // Only grab the data
    let data = bytes.get(8..8 + data_length).unwrap();

    let mut ac_units: Vec<ACStatus> = Vec::with_capacity(data_length / 8);

    for g in data.chunks(8) {
        let power_state: Option<ACPowerState> = match g[0] >> 6 {
            0 => Some(states::ACPowerState::Off),
            1 => Some(states::ACPowerState::On),
            _ => None,
        };

        // AC unit number
        let number = g[0] & 0b00111111;

        // AC operating mode
        let mode: Option<ACMode> = match g[1] >> 4 {
            0 => Some(ACMode::Auto),
            1 => Some(ACMode::Heat),
            0b10 => Some(ACMode::Dry),
            0b11 => Some(ACMode::Fan),
            0b100 => Some(ACMode::Cool),
            0b101 => Some(ACMode::AutoHeat),
            0b110 => Some(ACMode::AutoCool),
            _ => None,
        };

        let fan_speed: Option<ACFanSpeed> = match g[1] & 0b00001111 {
            0 => Some(ACFanSpeed::Auto),
            1 => Some(ACFanSpeed::Quiet),
            0b10 => Some(ACFanSpeed::Low),
            0b11 => Some(ACFanSpeed::Medium),
            0b100 => Some(ACFanSpeed::High),
            0b101 => Some(ACFanSpeed::Powerful),
            0b110 => Some(ACFanSpeed::Turbo),
            _ => None,
        };

        let spill = (g[2] >> 7) == 1;
        let has_timer = ((g[2] >> 6) & 1) == 1;
        let target_setpoint = g[2] & 0b00111111;

        let temperature: Option<f64> = {
            if g[4] == 0xff {
                None
            } else {
                let g4 = g[4] as u16;
                let g5 = g[5] as u16;

                let num = (g4 << 3) | (g5 >> 5);

                Some(((num as f64) - 500.00) / 10.00)
            }
        };

        let error_code = (g[6] as u16) << 8 | (g[7] as u16);

        ac_units.push(ACStatus {
            fan_speed,
            has_timer,
            mode,
            number,
            power_state,
            spill,
            target_setpoint,
            temperature,
            error_code,
        })
    }

    Ok(ReceivableMessage::ACStatuses(ac_units))
}

#[cfg(test)]
mod test {

    use crate::{
        messaging::{ac_status_message::ACStatus, ReceivableMessage},
        states::{ACFanSpeed, ACMode, ACPowerState},
    };

    use super::parse;

    #[test]
    fn test_parses_valid() {
        let units = parse(&[
            0x55, 0x55, 0xb0, 0x80, 0x01, 0x2d, 0x00, 0x10, 0x40, 0x42, 0x1a, 0x00, 0x61, 0x80,
            0x00, 0x00, 0x01, 0x00, 0x1a, 0x00, 0x61, 0x80, 0xff, 0xfe, 0xca, 0xcb,
        ])
        .unwrap();

        match units {
            ReceivableMessage::ACStatuses(m) => {
                let valid = vec![
                    ACStatus {
                        power_state: Some(ACPowerState::On),
                        number: 0,
                        mode: Some(ACMode::Cool),
                        fan_speed: Some(ACFanSpeed::Low),
                        spill: false,
                        has_timer: false,
                        target_setpoint: 26,
                        temperature: Some(28.0),
                        error_code: 0,
                    },
                    ACStatus {
                        power_state: Some(ACPowerState::Off),
                        number: 1,
                        mode: Some(ACMode::Auto),
                        fan_speed: Some(ACFanSpeed::Auto),
                        spill: false,
                        has_timer: false,
                        target_setpoint: 26,
                        temperature: Some(28.0),
                        error_code: 65534,
                    },
                ];

                m.iter()
                    .zip(valid.iter())
                    .for_each(|(a, b)| assert_eq!(a, b));
            }
            _ => panic!("incorrect message type returned."),
        }
    }
}