use std::{collections::BTreeMap, ops::Index};
pub use crate::message::{
ac_status::{AcFlags, AcMode, AcPower, AcStatus, FanSpeed},
zone_status::{ZoneControl, ZoneFlags, ZonePower, ZoneSensorReading, ZoneStatus},
};
use crate::{
conn::frame::Frame,
message::{ac_status::AcStatusMessage, zone_status::ZoneStatusMessage, MessageError},
};
#[derive(Clone, Debug, Default)]
pub struct CurrentStatus {
acs: AcStatusSet,
zones: ZoneStatusSet,
}
impl CurrentStatus {
pub fn acs(&self) -> &AcStatusSet {
&self.acs
}
pub fn zones(&self) -> &ZoneStatusSet {
&self.zones
}
pub fn apply(&mut self, change: &StatusChange) {
match change {
StatusChange::AcStatusChange(acs) => {
self.acs.acs.append(&mut acs.acs.clone());
}
StatusChange::ZoneStatusChange(zones) => {
self.zones.zones.append(&mut zones.zones.clone());
}
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum StatusChange {
AcStatusChange(AcStatusSet),
ZoneStatusChange(ZoneStatusSet),
}
pub trait StatusSet: Index<Self::Index> + IntoIterator<Item = (Self::Index, Self::Entry)> {
type Index;
type Entry;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn get(&self, index: Self::Index) -> Option<&Self::Entry>;
fn iter(&self) -> iter::Iter<'_, Self::Index, Self::Entry>;
fn keys(&self) -> iter::Keys<'_, Self::Index, Self::Entry>;
fn values(&self) -> iter::Values<'_, Self::Index, Self::Entry>;
}
#[derive(Clone, Debug, Default)]
pub struct AcStatusSet {
acs: BTreeMap<u8, AcStatus>,
}
impl StatusSet for AcStatusSet {
type Index = u8;
type Entry = AcStatus;
fn len(&self) -> usize {
self.acs.len()
}
fn is_empty(&self) -> bool {
self.acs.is_empty()
}
fn get(&self, index: u8) -> Option<&AcStatus> {
self.acs.get(&index)
}
fn iter(&self) -> iter::Iter<'_, u8, AcStatus> {
iter::Iter::<'_, u8, AcStatus> {
inner: self.acs.iter(),
}
}
fn keys(&self) -> iter::Keys<'_, u8, AcStatus> {
iter::Keys { inner: self.iter() }
}
fn values(&self) -> iter::Values<'_, u8, AcStatus> {
iter::Values { inner: self.iter() }
}
}
impl From<AcStatusMessage> for AcStatusSet {
fn from(value: AcStatusMessage) -> Self {
Self { acs: value.acs }
}
}
impl Index<u8> for AcStatusSet {
type Output = AcStatus;
fn index(&self, index: u8) -> &Self::Output {
self.get(index).expect("no entry found for key")
}
}
impl IntoIterator for AcStatusSet {
type Item = (u8, AcStatus);
type IntoIter = std::collections::btree_map::IntoIter<u8, AcStatus>;
fn into_iter(self) -> Self::IntoIter {
self.acs.into_iter()
}
}
impl<'a> IntoIterator for &'a AcStatusSet {
type Item = (&'a u8, &'a AcStatus);
type IntoIter = std::collections::btree_map::Iter<'a, u8, AcStatus>;
fn into_iter(self) -> Self::IntoIter {
self.acs.iter()
}
}
#[derive(Clone, Debug, Default)]
pub struct ZoneStatusSet {
zones: BTreeMap<u8, ZoneStatus>,
}
impl StatusSet for ZoneStatusSet {
type Index = u8;
type Entry = ZoneStatus;
fn len(&self) -> usize {
self.zones.len()
}
fn is_empty(&self) -> bool {
self.zones.is_empty()
}
fn get(&self, index: u8) -> Option<&ZoneStatus> {
self.zones.get(&index)
}
fn iter(&self) -> iter::Iter<'_, u8, ZoneStatus> {
iter::Iter::<'_, u8, ZoneStatus> {
inner: self.zones.iter(),
}
}
fn keys(&self) -> iter::Keys<'_, u8, ZoneStatus> {
iter::Keys { inner: self.iter() }
}
fn values(&self) -> iter::Values<'_, u8, ZoneStatus> {
iter::Values { inner: self.iter() }
}
}
impl From<ZoneStatusMessage> for ZoneStatusSet {
fn from(value: ZoneStatusMessage) -> Self {
Self { zones: value.zones }
}
}
impl Index<u8> for ZoneStatusSet {
type Output = ZoneStatus;
fn index(&self, index: u8) -> &Self::Output {
self.get(index).expect("no entry found for key")
}
}
impl IntoIterator for ZoneStatusSet {
type Item = (u8, ZoneStatus);
type IntoIter = std::collections::btree_map::IntoIter<u8, ZoneStatus>;
fn into_iter(self) -> Self::IntoIter {
self.zones.into_iter()
}
}
impl<'a> IntoIterator for &'a ZoneStatusSet {
type Item = (&'a u8, &'a ZoneStatus);
type IntoIter = std::collections::btree_map::Iter<'a, u8, ZoneStatus>;
fn into_iter(self) -> Self::IntoIter {
self.zones.iter()
}
}
impl TryFrom<Frame> for StatusChange {
type Error = MessageError;
fn try_from(value: Frame) -> Result<Self, Self::Error> {
let mut frame = value;
match AcStatusMessage::try_from(frame) {
Ok(acs) => {
return Ok(Self::AcStatusChange(acs.into()));
}
Err(MessageError::IncorrectSubtype(f)) => {
frame = f.inner;
}
Err(e) => {
return Err(e);
}
}
match ZoneStatusMessage::try_from(frame) {
Ok(zones) => {
return Ok(Self::ZoneStatusChange(zones.into()));
}
Err(MessageError::IncorrectSubtype(f)) => {
frame = f.inner;
}
Err(e) => {
return Err(e);
}
}
Err(MessageError::IncorrectSubtype(crate::message::Frame {
inner: frame,
}))
}
}
pub mod iter {
pub struct Iter<'a, K, V> {
pub(super) inner: std::collections::btree_map::Iter<'a, K, V>,
}
impl<'a, K: 'a, V: 'a> Iterator for Iter<'a, K, V> {
type Item = (&'a K, &'a V);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}
pub struct Keys<'a, K, V> {
pub(super) inner: Iter<'a, K, V>,
}
impl<'a, K: 'a, V: 'a> Iterator for Keys<'a, K, V> {
type Item = &'a K;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(k, _)| k)
}
}
pub struct Values<'a, K, V> {
pub(super) inner: Iter<'a, K, V>,
}
impl<'a, K: 'a, V: 'a> Iterator for Values<'a, K, V> {
type Item = &'a V;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(_, v)| v)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::conn::tests::data;
use crate::message::{ac_status::tests::ACS, zone_status::tests::ZONES};
#[test]
fn test_ac_status_set_empty() {
let status = AcStatusSet {
acs: BTreeMap::new(),
};
assert!(status.is_empty());
assert_eq!(status.len(), 0);
assert_matches!(status.get(0), None);
assert!(std::panic::catch_unwind(|| status[0]).is_err());
assert_matches!(status.iter().next(), None);
assert_matches!(status.keys().next(), None);
assert_matches!(status.values().next(), None);
}
#[test]
fn test_ac_status_set_from_message() {
let status: AcStatusSet = AcStatusMessage::new(*ACS).into();
assert!(!status.is_empty());
assert_eq!(status.len(), ACS.len());
for (i, ac) in ACS.iter() {
assert_matches!(status.get(*i), Some(a) => {
assert_eq!(a, ac);
});
assert_eq!(&status[*i], ac);
}
}
fn const_acs() -> AcStatusSet {
AcStatusSet {
acs: ACS.iter().copied().collect(),
}
}
#[test]
fn test_ac_status_set() {
let status = const_acs();
assert!(!status.is_empty());
assert_eq!(status.len(), ACS.len());
for (i, ac) in ACS.iter() {
assert_matches!(status.get(*i), Some(a) => {
assert_eq!(a, ac);
});
assert_eq!(&status[*i], ac);
}
}
#[test]
fn test_ac_status_set_iter() {
let status = const_acs();
let mut i = status.iter();
assert_matches!(i.next(), Some((x, y)) => {
assert_eq!((*x, *y), ACS[0]);
});
assert_matches!(i.next(), Some((x, y)) => {
assert_eq!((*x, *y), ACS[1]);
});
assert_matches!(i.next(), None);
}
#[test]
fn test_ac_status_set_keys() {
let status = const_acs();
let mut k = status.keys();
assert_eq!(k.next(), Some(&ACS[0].0));
assert_eq!(k.next(), Some(&ACS[1].0));
assert_matches!(k.next(), None);
}
#[test]
fn test_ac_status_set_values() {
let status = const_acs();
let mut v = status.values();
assert_eq!(v.next(), Some(&ACS[0].1));
assert_eq!(v.next(), Some(&ACS[1].1));
assert_matches!(v.next(), None);
}
#[test]
fn test_ac_status_set_into_iter() {
let status = const_acs();
let mut i = status.into_iter();
assert_matches!(i.next(), Some((k, v)) => {
assert_eq!((k, v), ACS[0]);
});
assert_matches!(i.next(), Some((k, v)) => {
assert_eq!((k, v), ACS[1]);
});
assert_matches!(i.next(), None);
}
#[test]
fn test_ac_status_set_for() {
let status = const_acs();
for (&k, v) in &status {
assert_eq!((k, *v), ACS[k as usize]);
}
for (k, v) in status {
assert_eq!((k, v), ACS[k as usize]);
}
}
#[test]
fn test_ac_status_change_from_data() {
let frame = data::frame(data::MSG_RESP_STATUS_ACS);
assert_matches!(StatusChange::try_from(frame), Ok(StatusChange::AcStatusChange(status)) => {
assert_eq!(status.acs.len(), ACS.len());
})
}
#[test]
fn test_zone_status_set_empty() {
let status = ZoneStatusSet {
zones: BTreeMap::new(),
};
assert!(status.is_empty());
assert_eq!(status.len(), 0);
assert_matches!(status.get(0), None);
assert!(std::panic::catch_unwind(|| status[0]).is_err());
assert_matches!(status.iter().next(), None);
assert_matches!(status.keys().next(), None);
assert_matches!(status.values().next(), None);
}
#[test]
fn test_zone_status_set_from_message() {
let status: ZoneStatusSet = ZoneStatusMessage::new(*ZONES).into();
assert!(!status.is_empty());
assert_eq!(status.len(), ZONES.len());
for (i, zone) in ZONES.iter() {
assert_matches!(status.get(*i), Some(z) => {
assert_eq!(z, zone);
});
assert_eq!(&status[*i], zone);
}
}
fn const_zones() -> ZoneStatusSet {
ZoneStatusSet {
zones: ZONES.iter().copied().collect(),
}
}
#[test]
fn test_zone_status_set() {
let status = const_zones();
assert!(!status.is_empty());
assert_eq!(status.len(), ZONES.len());
for (i, zone) in ZONES.iter() {
assert_matches!(status.get(*i), Some(z) => {
assert_eq!(z, zone);
});
assert_eq!(&status[*i], zone);
}
}
#[test]
fn test_zone_status_set_iter() {
let status = const_zones();
let mut i = status.iter();
assert_matches!(i.next(), Some((k, v)) => {
assert_eq!((*k, *v), ZONES[0]);
});
assert_matches!(i.next(), Some((k, v)) => {
assert_eq!((*k, *v), ZONES[1]);
});
assert_matches!(i.next(), None);
}
#[test]
fn test_zone_status_set_keys() {
let status = const_zones();
let mut k = status.keys();
assert_eq!(k.next(), Some(&ZONES[0].0));
assert_eq!(k.next(), Some(&ZONES[1].0));
assert_matches!(k.next(), None);
}
#[test]
fn test_zone_status_set_values() {
let status = const_zones();
let mut v = status.values();
assert_eq!(v.next(), Some(&ZONES[0].1));
assert_eq!(v.next(), Some(&ZONES[1].1));
assert_matches!(v.next(), None);
}
#[test]
fn test_zone_status_set_into_iter() {
let status = const_zones();
let mut i = status.into_iter();
assert_matches!(i.next(), Some((k, v)) => {
assert_eq!((k, v), ZONES[0]);
});
assert_matches!(i.next(), Some((k, v)) => {
assert_eq!((k, v), ZONES[1]);
});
assert_matches!(i.next(), None);
}
#[test]
fn test_zone_status_set_for() {
let status = const_zones();
for (k, v) in &status {
assert_eq!((*k, *v), ZONES[*k as usize]);
}
for (k, v) in status {
assert_eq!((k, v), ZONES[k as usize]);
}
}
#[test]
fn test_zone_status_change_from_data() {
let frame = data::frame(data::MSG_RESP_STATUS_ZONES);
assert_matches!(StatusChange::try_from(frame), Ok(StatusChange::ZoneStatusChange(status)) => {
assert_eq!(status.zones.len(), ZONES.len());
})
}
#[test]
fn test_invalid_status_change_from_data() {
#[rustfmt::skip]
const MSG_RESP_INVALID: &[u8] = &[
0x55, 0x55, 0x55, 0xAA, 0xB0, 0x80, 0x01, 0xC0, 0x00, 0x08, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0xCC ];
let frame = data::frame(MSG_RESP_INVALID);
assert_matches!(
StatusChange::try_from(frame),
Err(MessageError::IncorrectSubtype(_))
);
}
#[test]
fn test_current_default() {
let status = CurrentStatus::default();
assert!(status.acs.is_empty());
assert!(status.zones.is_empty());
}
#[test]
fn test_current_apply_acs() {
let mut status = CurrentStatus::default();
let frame = data::frame(data::MSG_RESP_STATUS_ACS);
let change = StatusChange::try_from(frame).expect("not ac status");
status.apply(&change);
assert_eq!(status.acs.len(), ACS.len());
assert!(status.zones.is_empty());
}
#[test]
fn test_current_apply_zones() {
let mut status = CurrentStatus::default();
let frame = data::frame(data::MSG_RESP_STATUS_ZONES);
let change = StatusChange::try_from(frame).expect("not zone status");
status.apply(&change);
assert!(status.acs.is_empty());
assert_eq!(status.zones.len(), ZONES.len());
}
#[test]
fn test_current_apply_multi() {
let mut status = CurrentStatus::default();
let ac_change =
StatusChange::try_from(data::frame(data::MSG_RESP_STATUS_ACS)).expect("not ac status");
let zone_change = StatusChange::try_from(data::frame(data::MSG_RESP_STATUS_ZONES))
.expect("not zone status");
status.apply(&ac_change);
status.apply(&zone_change);
status.apply(&ac_change);
status.apply(&zone_change);
assert_eq!(status.acs.len(), ACS.len());
assert_eq!(status.zones.len(), ZONES.len());
}
}