vcontrol 0.6.0

A library for communication with Viessmann heating controllers.
Documentation
use core::fmt;

use arrayref::array_ref;
#[cfg(feature = "schemars")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use super::DateTime;
use crate::Device;

#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[derive(Clone, PartialEq, Deserialize, Serialize)]
pub struct Error {
  index: u8,
  time: Option<DateTime>,
}

impl Error {
  pub fn new(index: u8, time: DateTime) -> Self {
    Self { index, time: Some(time) }
  }

  pub fn index(&self) -> u8 {
    self.index
  }

  pub fn to_str(&self, device: &Device) -> Option<&'static str> {
    device.errors().get(&(self.index as i32)).cloned()
  }

  pub fn time(&self) -> Option<&DateTime> {
    self.time.as_ref()
  }

  pub fn from_bytes(bytes: &[u8; 9]) -> Result<Self, crate::Error> {
    let index = bytes[0];

    let time = match DateTime::from_bytes(array_ref![bytes, 1, 8]) {
      Ok(time) => Some(time),
      Err(_) if index == 0 => None,
      Err(err) => return Err(crate::Error::InvalidFormat(format!("invalid time for error {index}: {err}"))),
    };

    Ok(Self { index, time })
  }

  pub fn to_bytes(&self) -> [u8; 9] {
    let mut bytes = [0; 9];

    bytes[0] = self.index;

    if let Some(time) = &self.time {
      let time_bytes = time.to_bytes();
      bytes[1..].copy_from_slice(&time_bytes);
    }

    bytes
  }
}

impl fmt::Display for Error {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    if let Some(time) = &self.time {
      write!(f, "{}: Error {:02X}", time, self.index)
    } else {
      write!(f, "0000-00-00: Error {:02X}", self.index)
    }
  }
}

impl fmt::Debug for Error {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    f.debug_tuple("Error").field(&self.index).field(&self.time).finish()
  }
}

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

  use chrono::{Datelike, Timelike};

  #[test]
  fn new() {
    let time = DateTime::new(2018, 12, 23, 17, 49, 31).unwrap();
    let error = Error::new(0xAC, time.clone());

    assert_eq!(error.index, 0xAC);
    assert_eq!(error.time.as_ref().unwrap().0.year(), 2018);
    assert_eq!(error.time.as_ref().unwrap().0.month(), 12);
    assert_eq!(error.time.as_ref().unwrap().0.day(), 23);
    assert_eq!(error.time.as_ref().unwrap().0.weekday().number_from_monday(), 7);
    assert_eq!(error.time.as_ref().unwrap().0.hour(), 17);
    assert_eq!(error.time.as_ref().unwrap().0.minute(), 49);
    assert_eq!(error.time.as_ref().unwrap().0.second(), 31);
  }

  #[test]
  fn from_bytes() {
    let error = Error::from_bytes(&[0xAC, 0x20, 0x18, 0x12, 0x23, 0x07, 0x17, 0x49, 0x31]).unwrap();

    assert_eq!(error.index, 0xAC);
    assert_eq!(error.time.as_ref().unwrap().0.year(), 2018);
    assert_eq!(error.time.as_ref().unwrap().0.month(), 12);
    assert_eq!(error.time.as_ref().unwrap().0.day(), 23);
    assert_eq!(error.time.as_ref().unwrap().0.weekday().number_from_monday(), 7);
    assert_eq!(error.time.as_ref().unwrap().0.hour(), 17);
    assert_eq!(error.time.as_ref().unwrap().0.minute(), 49);
    assert_eq!(error.time.as_ref().unwrap().0.second(), 31);
  }

  #[test]
  fn to_bytes() {
    let time = DateTime::new(2018, 12, 23, 17, 49, 31).unwrap();
    let error = Error::new(0xAC, time);

    assert_eq!(error.to_bytes(), [0xAC, 0x20, 0x18, 0x12, 0x23, 0x06, 0x17, 0x49, 0x31]);
  }
}