airtouch5 0.2.0

A library for communicating with AirTouch 5 air conditioning system control consoles
Documentation
//! Console version extended message.
//!
//! See ยง4.b.iv.

use super::extended::{extended_message, ExtendedMessage, ExtendedMessageSubtype};

const SUBTYPE_CONSOLE_VERSION: ExtendedMessageSubtype = 0xff30;

extended_message!(SUBTYPE_CONSOLE_VERSION,
pub struct ConsoleVersionRequest {
}
pub struct ConsoleVersionResponse {
    /// Whether a console update is available.
    ///
    /// <div class="warning">
    /// This appears to more specifically reflect the notification badge in the
    /// console UI; if a user has opened the Update modal but chose not to install
    /// the update (yet) by hitting Cancel on the screen, the badge disappears and
    /// this value will be <code>false</code>, even though there is in fact still
    /// an update available to be installed.
    /// </div>
    pub update_available: bool,

    /// Version string of each console.
    pub versions: Vec<String>,
}
{
    // console version request has no data
    fn impl_frame_data_len(&self) -> usize {
        0
    }
    fn impl_frame_data<W: std::io::Write>(&self, _dst: &mut W) -> Result<(), super::MessageError> {
        Ok(())
    }

    fn from_frame_data(message_id: u8, data: Vec<u8>) -> Result<Self, super::MessageError>
    where
        Self: Sized,
    {
        if !data.is_empty() {
            Err(MessageError::InvalidData)
        } else {
            Ok(Self { message_id })
        }
    }
}
{
    fn impl_frame_data_len(&self) -> usize {
        if self.versions.is_empty() {
            2
        } else {
            self.versions.iter().fold(1, |a, s| a + s.len() + 1)
        }
    }

    fn impl_frame_data<W: std::io::Write>(&self, dst: &mut W) -> Result<(), super::MessageError> {
        let mut pre = [0u8; 2];
        pre[0] = self.update_available.into();
        pre[1] = (self.impl_frame_data_len() - 2).try_into()?;
        dst.write_all(&pre)?;
        let mut first = true;
        for s in &self.versions {
            if !first {
                dst.write_all(",".as_bytes())?;
            } else {
                first = false;
            }
            dst.write_all(s.as_bytes())?;
        }
        Ok(())
    }

    fn from_frame_data(message_id: u8, data: Vec<u8>) -> Result<Self, super::MessageError>
    {
        if data.len() < 2 || data.len() != 2 + data[1] as usize {
            return Err(MessageError::InvalidData);
        }
        let update_available = data[0] != 0;
        let s = core::str::from_utf8(&data[2..])?;
        let versions = s.split(',').map(|s| s.to_string()).collect();
        Ok(Self {
            message_id,
            update_available,
            versions,
        })
    }
});

impl ConsoleVersionRequest {
    /// Construct a console version request message.
    pub fn new() -> Self {
        Self {
            message_id: super::next_msg_id(),
        }
    }
}

impl ConsoleVersionResponse {
    /// Construct a console version response message.
    ///
    /// `versions` is an iterator of version strings.
    pub fn new<T: IntoIterator<Item: Into<String>>>(versions: T, update_available: bool) -> Self {
        Self::with_message_id(super::next_msg_id(), versions, update_available)
    }
    /// Construct a console version response message.
    ///
    /// `versions` is an iterator of version strings.
    pub fn with_message_id<T: IntoIterator<Item: Into<String>>>(
        message_id: u8,
        versions: T,
        update_available: bool,
    ) -> Self {
        Self {
            message_id,
            update_available,
            versions: versions.into_iter().map(T::Item::into).collect(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::conn::tests::data::*;

    #[test]
    fn test_console_version_req() {
        let orig = ConsoleVersionRequest::new();
        let frame: Frame = orig.clone().try_into().expect("into frame failed");
        let req: ConsoleVersionRequest = frame.try_into().expect("from frame failed");
        assert_eq!(req, orig);
    }

    #[test]
    fn test_console_version_req_from_data() {
        let req: ConsoleVersionRequest = frame(MSG_REQ_CON_VERS)
            .try_into()
            .expect("from frame failed");
        let f: Frame = req.try_into().expect("into frame failed");
        assert_eq!(f, frame(MSG_REQ_CON_VERS));
    }

    #[test]
    fn test_console_version_resp() {
        let orig = ConsoleVersionResponse::new(vec!["1.0.3", "1.0.1"], true);
        let frame: Frame = orig.clone().try_into().expect("into frame failed");
        let resp: ConsoleVersionResponse = frame.try_into().expect("from frame failed");
        assert_eq!(resp, orig);
    }

    #[test]
    fn test_console_version_resp_from_data() {
        let resp: ConsoleVersionResponse = frame(MSG_RESP_CON_VERS)
            .try_into()
            .expect("from frame failed");
        assert!(!resp.update_available);
        assert_eq!(resp.versions.len(), 2);
        assert!(resp.versions.iter().all(|v| v == "1.0.3"));
        let f: Frame = resp.try_into().expect("into frame failed");
        assert_eq!(f, frame(MSG_RESP_CON_VERS));
    }
}