airtouch5 0.2.0

A library for communicating with AirTouch 5 air conditioning system control consoles
Documentation
//! Utilities for implementing Extended message type
//!
//! Use the `extended_message` macro to define the request and response structs
//! and the required `ExtendedMessage` trait functions, and automatically
//! implement the `TryFrom` conversions to and from a `Frame`:
//!
//! # Message ID
//!
//! Each message struct has a `message_id: u8` field. This is added
//! automatically by the `extended_message` macro. The `message_id` for a
//! request should be a unique-ish number; typically use `next_msg_id()` to
//! generate the ID for a request. The `messge_id` for a response is taken
//! from the request message it is a response to.
//!
//! # Examples
//!
//! ```ignore
//! # use airtouch5::message::next_msg_id;
//! # use airtouch5::message::extended::{extended_message,ExtendedMessage,ExtendedMessageSubtype};
//! const SUBTYPE_EXAMPLE: ExtendedMessageSubtype = 0xacab;
//! extended_message!(SUBTYPE_EXAMPLE,
//! pub struct ExampleRequest {
//!    pub request_value: u16,
//! }
//! pub struct ExampleResponse {
//!     pub response_value: String,
//! }
//! { // impl ExtendedMessage for ExampleRequest
//!     fn impl_frame_data_len(&self) -> usize {
//!         // frame data for the request always contains exactly one u16
//!         size_of::<u16>()
//!     }
//!     fn impl_frame_data<W: std::io::Write>(&self, dst: &mut W) -> Result<(), MessageError> {
//!         // write the request value(s) into the output buffer
//!         dst.write_all(&self.request_value.to_be_bytes())?;
//!         Ok(())
//!     }
//!     fn from_frame_data(message_id: u8, data: Vec<u8>) -> Result<Self, MessageError> {
//!         if data.len() == size_of::<u16>() {
//!             Ok(Self { message_id, request_value: data[..].try_into()? })
//!         } else {
//!             Err(MessageError::InvalidData)
//!         }
//!     }
//! }
//! { // impl ExtendedMessage for ExampleResponse
//!     fn impl_frame_data_len(&self) -> usize {
//!         // one byte length plus the string data
//!         size_of::<u8>() + self.response_value.len()
//!     }
//!     fn impl_frame_data<W: std::io::Write>(&self, dst: &mut W) -> Result<(), MessageError> {
//!         // write the length into the output buffer
//!         dst.write_all(&(self.response_value.len() as u8).to_be_bytes())?;
//!         // write the string data into the output buffer
//!         dst.write_all(self.response_value.as_bytes())?;
//!     }
//!     fn from_frame_data(message_id: u8, data: Vec<u8>) -> Result<Self, MessageError> {
//!         // check the data length is sensible
//!         if data.len() < 1 || data.len() != 1 + data[0] as usize {
//!             return Err(MessageError::InvalidData);
//!         }
//!         Ok(Self { message_id, response_value: str::from_utf8(&data[1..])?.to_string() })
//!     }
//! });
//!
//! impl ExampleRequest {
//!     // constructor(s) and/or other associated function
//!     pub fn new(request_value: u16) -> Self {
//!         Self { message_id: next_msg_id(), request_value }
//!     }
//! }
//! ```

use super::MessageError;
use crate::conn::{frame::Frame, MessageKind};

pub(super) type ExtendedMessageSubtype = u16;
const MSG_SUBTYPE_SIZE: usize = std::mem::size_of::<ExtendedMessageSubtype>();

/// Generate request and response that implement `ExtendedMessage`. See module-
/// level docs for details.
macro_rules! extended_message {
    ( $subtype:expr,
      $req_vis:vis struct $req_name:ident { $($req_fields:tt)* }
      $resp_vis:vis struct $resp_name:ident { $($resp_fields:tt)* }
      { $($req_impl:tt)* }
      { $($resp_impl:tt)* }) => {
        use crate::conn::frame::Frame;
        use super::MessageError;
        #[derive(Clone, Debug, PartialEq)]
        $req_vis struct $req_name {
            message_id: u8,
            $($req_fields)*
        }
        #[derive(Clone, Debug, PartialEq)]
        $resp_vis struct $resp_name {
            message_id: u8,
            $($resp_fields)*
        }
        impl ExtendedMessage for $req_name {
            const MSG_SUBTYPE: u16 = ($subtype);
            fn message_id(&self) -> u8 { self.message_id }
            $($req_impl)*
        }
        impl ExtendedMessage for $resp_name {
            const MSG_SUBTYPE: u16 = ($subtype);
            fn message_id(&self) -> u8 { self.message_id }
            $($resp_impl)*
        }
        impl TryFrom<Frame> for $req_name {
            type Error = MessageError;
            fn try_from(value: Frame) -> Result<Self, Self::Error> { Self::from_frame(value) }
        }
        impl TryFrom<Frame> for $resp_name {
            type Error = MessageError;
            fn try_from(value: Frame) -> Result<Self, Self::Error> { Self::from_frame(value) }
        }
        impl TryFrom<$req_name> for Frame {
            type Error = MessageError;
            fn try_from(value: $req_name) -> Result<Self, Self::Error> { value.into_request_frame() }
        }
        impl TryFrom<$resp_name> for Frame {
            type Error = MessageError;
            fn try_from(value: $resp_name) -> Result<Self, Self::Error> { value.into_response_frame() }
        }
    };
}
pub(super) use extended_message;

/// The frame message type for extended messages. See ยง3.d.
const MSG_TYPE_EXTENDED: u8 = 0x1f;

/// Trait implemented by extended message structs
pub(super) trait ExtendedMessage: Sized {
    /// Each extended message pair (request and response) has a subtype.
    const MSG_SUBTYPE: ExtendedMessageSubtype;

    /// Return the length of the encoded message data. This does not include
    /// the subtype. Must be implemented by each message struct.
    fn impl_frame_data_len(&self) -> usize;

    /// Encode the extended message data (not including the subtype) to `dst`.
    /// Must be implemented by each message struct.
    fn impl_frame_data<W: std::io::Write>(&self, dst: &mut W) -> Result<(), MessageError>;

    /// Decode the extended message from `data`. Must be implemented by each
    /// message struct.
    fn from_frame_data(message_id: u8, data: Vec<u8>) -> Result<Self, MessageError>;

    /// The message ID of this message. This is typically automatically
    /// implemented by the `extended_message` macro.
    fn message_id(&self) -> u8;

    fn from_frame(mut frame: Frame) -> Result<Self, MessageError> {
        if peek_subtype(&frame)? != Self::MSG_SUBTYPE {
            return Err(MessageError::IncorrectSubtype(super::Frame {
                inner: frame,
            }));
        }
        Self::from_frame_data(frame.msg_id, frame.data.drain(MSG_SUBTYPE_SIZE..).collect())
    }

    fn into_frame(self, kind: MessageKind) -> Result<Frame, MessageError> {
        use std::io::Write;
        let (msg_type, address) = kind.into();
        let mut data = std::io::Cursor::new(Vec::with_capacity(
            MSG_SUBTYPE_SIZE + self.impl_frame_data_len(),
        ));
        data.write_all(&Self::MSG_SUBTYPE.to_be_bytes())?;
        self.impl_frame_data(&mut data)?;

        Ok(Frame {
            address,
            msg_id: self.message_id(),
            msg_type,
            kind,
            data: data.into_inner(),
        })
    }

    fn into_request_frame(self) -> Result<Frame, MessageError> {
        self.into_frame(MessageKind::ExtendedRequest)
    }

    fn into_response_frame(self) -> Result<Frame, MessageError> {
        self.into_frame(MessageKind::ExtendedResponse)
    }
}

pub(super) fn peek_subtype(frame: &Frame) -> Result<ExtendedMessageSubtype, MessageError> {
    if frame.msg_type != MSG_TYPE_EXTENDED || frame.data.len() < MSG_SUBTYPE_SIZE {
        return Err(MessageError::InvalidData);
    }
    Ok(ExtendedMessageSubtype::from_be_bytes(
        frame.data[..MSG_SUBTYPE_SIZE].try_into().unwrap(),
    ))
}