resol-vbus 0.2.1

A Rust library for processing RESOL VBus data.
Documentation
use std::{
    fmt,
    hash::{Hash, Hasher},
};

use crate::{header::Header, id_hash::IdHash};

/// The `Datagram` type stores information according to the VBus protocol version 2.x.
///
/// Datagrams are used to issue simple commands with limited amount of payload (like e.g. getting
/// or setting a parameter).
///
/// ## The "identity" of `Datagram` values
///
/// As described in [the corresponding section of the `Header` struct][1] VBus data types use
/// some of their fields as part of their "identity". In addition to the fields used by the
/// `Header` type the `Datagram` type also respects the `command` and (under some conditions) the
/// `param16` fields. That means that two `Datagram` with differing `timestamp`, `param32` and
/// (under some conditions) `param16` fields are still considered "identical", if the other fields
/// match.
///
/// [1]: struct.Header.html#the-identity-of-header-values
#[derive(Clone)]
pub struct Datagram {
    /// The shared `Header` of all VBus protocol types.
    pub header: Header,

    /// The command of this `Datagram`.
    pub command: u16,

    /// The 16-bit parameter attached to this `Datagram`.
    pub param16: i16,

    /// The 32-bit parameter attached to this `Datagram`.
    pub param32: i32,
}

impl Datagram {
    /// Creates an identification string for this `Datagram`.
    ///
    /// The string contains all fields that count towards the "identity" of the `Datagram`:
    ///
    /// - `channel`
    /// - `destination_address`
    /// - `source_address`
    /// - `protocol_version`
    /// - `command`
    /// - `param16` (if `command` equals 0x0900)
    ///
    /// # Examples
    ///
    /// ```rust
    /// use resol_vbus::{Header, Datagram};
    /// use resol_vbus::utils::utc_timestamp;
    ///
    /// let dgram1 = Datagram {
    ///     header: Header {
    ///         timestamp: utc_timestamp(1485688933),
    ///         channel: 0x11,
    ///         destination_address: 0x1213,
    ///         source_address: 0x1415,
    ///         protocol_version: 0x26,
    ///     },
    ///     command: 0x1718,
    ///     param16: 0x191a,
    ///     param32: 0x1b1c1d1e,
    /// };
    ///
    /// let dgram2 = Datagram {
    ///     header: Header {
    ///         timestamp: utc_timestamp(1485688933),
    ///         channel: 0x11,
    ///         destination_address: 0x1213,
    ///         source_address: 0x1415,
    ///         protocol_version: 0x26,
    ///     },
    ///     command: 0x0900,
    ///     param16: 0x191a,
    ///     param32: 0x1b1c1d1e,
    /// };
    ///
    /// assert_eq!("11_1213_1415_26_1718_0000", dgram1.id_string());
    /// assert_eq!("11_1213_1415_26_0900_191A", dgram2.id_string());
    /// ```
    pub fn id_string(&self) -> String {
        let info = match self.command {
            0x0900 => self.param16,
            _ => 0,
        };
        format!(
            "{}_{:04X}_{:04X}",
            self.header.id_string(),
            self.command,
            info
        )
    }
}

impl IdHash for Datagram {
    /// Returns an identification hash for this `Datagram`.
    ///
    /// The hash contains all fields that count towards the "identity" of the `Datagram`:
    ///
    /// - `channel`
    /// - `destination_address`
    /// - `source_address`
    /// - `protocol_version`
    /// - `command`
    /// - `param16` (if `command` equals 0x0900)
    ///
    /// # Examples
    ///
    /// ```rust
    /// use resol_vbus::{Header, Datagram, id_hash};
    /// use resol_vbus::utils::utc_timestamp;
    ///
    /// let dgram = Datagram {
    ///     header: Header {
    ///         timestamp: utc_timestamp(1485688933),
    ///         channel: 0x11,
    ///         destination_address: 0x1213,
    ///         source_address: 0x1415,
    ///         protocol_version: 0x26,
    ///     },
    ///     command: 0x1718,
    ///     param16: 0x191a,
    ///     param32: 0x1b1c1d1e,
    /// };
    ///
    /// assert_eq!(2264775891674525017, id_hash(&dgram));
    /// ```
    fn id_hash<H: Hasher>(&self, h: &mut H) {
        let info = match self.command {
            0x0900 => self.param16,
            _ => 0,
        };

        self.header.id_hash(h);
        self.command.hash(h);
        info.hash(h);
    }
}

impl fmt::Debug for Datagram {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Datagram")
            .field("header", &self.header)
            .field("command", &format_args!("0x{:04X}", self.command))
            .field("param16", &format_args!("0x{:04X}", self.param16))
            .field(
                "param32",
                &format_args!("0x{:08X} ({})", self.param32, self.param32),
            )
            .finish()
    }
}

impl AsRef<Header> for Datagram {
    fn as_ref(&self) -> &Header {
        &self.header
    }
}

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

    use crate::{header::Header, utils::utc_timestamp};

    #[test]
    fn test_debug_fmt() {
        let timestamp = utc_timestamp(1485688933);

        let dgram = Datagram {
            header: Header {
                timestamp,
                channel: 0x11,
                destination_address: 0x1213,
                source_address: 0x1415,
                protocol_version: 0x26,
            },
            command: 0x1718,
            param16: 0x191a,
            param32: 0x1b1c1d1e,
        };

        let result = format!("{:?}", dgram);

        assert_eq!("Datagram { header: Header { timestamp: 2017-01-29T11:22:13Z, channel: 0x11, destination_address: 0x1213, source_address: 0x1415, protocol_version: 0x26 }, command: 0x1718, param16: 0x191A, param32: 0x1B1C1D1E (454827294) }", result);
    }
}