at4_protocol 2.1.1

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

mod ac_control_message;
mod ac_status_message;
mod group_control_message;
mod group_status_message;

pub use ac_control_message::ACControlMessage;
pub use ac_status_message::{ACStatus, GET_AC_STATUS_PACKET};
pub use group_control_message::GroupControlMessage;
pub use group_status_message::{GroupStatus, GET_GROUP_STATUS_PACKET};

struct MessageType;

impl MessageType {
    pub const GROUP_CONTROL_MESSAGE: u8 = 0x2A;
    pub const GROUP_STATUS_MESSAGE: u8 = 0x2B;
    pub const AC_CONTROL_MESSAGE: u8 = 0x2C;
    pub const AC_STATUS_MESSAGE: u8 = 0x2D;
}

/// The messages that can be parsed by the parser. These are received from the
/// AT4 controller.
#[derive(Debug)]
pub enum ReceivableMessage {
    /// The group status for all groups.
    GroupStatuses(Vec<GroupStatus>),
    /// The AC status for all AC units.
    ACStatuses(Vec<ACStatus>),
}

/// Parses a byte array into the relevant message.
///
/// # Arguments
///
/// * `bytes` - The byte array containing the TCP packet from your AT4.
///
/// # Example usage
///
/// ```
/// fn main() {
///     use at4_protocol::messaging;
///     use messaging::ReceivableMessage;
///
///     let bytes = [
///         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,
///     ];
///
///     let m = messaging::parse_message(&bytes);
///
///     match m {
///         Ok(m) => match m {
///             ReceivableMessage::GroupStatuses(m) => {
///                 // Do whatever
///             }
///             ReceivableMessage::ACStatuses(m) => {
///                 // Some other function
///             }
///         },
///         Err(e) => panic!("{}", e),
///     }
/// }
/// ```
pub fn parse_message(bytes: &[u8]) -> Result<ReceivableMessage, &'static str> {
    if bytes.len() < 10 {
        return Err("bytes length is too short to be a valid AT4 message.");
    }

    // Check header bytes
    if bytes[0] != 0x55 || bytes[1] != 0x55 {
        return Err("invalid header.");
    }

    // Check address byte
    if bytes[3] != 0x80 && bytes[3] != 0x90 {
        return Err("invalid address byte.");
    }

    // bytes 7, 8
    let message_length: usize = (((bytes[6] as u16) << 2) | (bytes[7] as u16)) as usize;
    if bytes.len() < (message_length + 10) {
        return Err("bytes length is not long enough for parsed length.");
    }

    // data to be checked by the crc algorithm
    let data = bytes.get(2..(8 + message_length)).unwrap();

    // crc values to check against
    let crc = bytes
        .get((8 + message_length)..(8 + message_length + 2))
        .unwrap();

    let crc: u16 = (crc[0] as u16) << 8 | (crc[1] as u16);

    // validate crc
    if crc16::State::<MODBUS>::calculate(data) != crc {
        return Err("crc check failed.");
    }

    // TODO: Add the message ID to the return messages

    // Check according to message type
    match bytes[5] {
        // Group status
        MessageType::GROUP_STATUS_MESSAGE => group_status::parse(bytes),
        // AC status
        MessageType::AC_STATUS_MESSAGE => ac_status::parse(bytes),
        _ => return Err("invalid message type."),
    }
}

mod ac_status;
mod group_status;

#[cfg(test)]
mod tests;