// -*- coding: utf-8 -*- #![allow(clippy::len_without_is_empty)] use crate::{ convert::{ datetime_to_u64, duration_to_string, duration_to_u128, f32_to_u128, f32_to_u32, string_to_duration, u128_pack, u128_to_duration, u128_to_f32, u128_unpack, u32_pack, u32_to_f32, u32_unpack, u64_pack, u64_to_datetime, u64_unpack, }, crc::{check_crc32, crc32}, time::{DateTime, TimeMask}, }; use anyhow::{self as ah, format_err as err, Context as _}; use std::time::Duration; const U32_LEN: usize = u32::BITS as usize / 8; const U64_LEN: usize = u64::BITS as usize / 8; pub const MAX_MSG_LEN: usize = 1200; const MAX_BODY_LEN: usize = MAX_MSG_LEN - MsgHdr::LEN - MsgCrc::LEN; const MAGIC: u32 = 0xBAC11C0; const VERSION: u32 = 1; const FLG_OK: u32 = 0x00000001; const MSGID_NOP: u32 = 0; const MSGID_PING: u32 = 1; const MSGID_PONG: u32 = 2; const MSGID_ACK: u32 = 3; const MSGID_GETENV: u32 = 4; const MSGID_ENV: u32 = 5; const MSGID_GETSTATUS: u32 = 6; const MSGID_STATUS: u32 = 7; const MSGID_CONTROL: u32 = 8; const MSGID_GETSETTINGS: u32 = 9; const MSGID_SETTINGS: u32 = 10; const MSGID_GETLOG: u32 = 11; const MSGID_LOG: u32 = 12; const MSGID_GETRTC: u32 = 13; const MSGID_RTC: u32 = 14; const MSGID_OTA: u32 = 15; fn floor_char_boundary(s: &str, mut i: usize) -> usize { if i >= s.len() { s.len() } else { while i > 0 { if s.is_char_boundary(i) { break; } i -= 1; } i } } pub struct MsgHdr { magic: u32, version: u32, id: u32, flags: u32, len: u32, } impl MsgHdr { const MINLEN: usize = U32_LEN * 5; const MAXLEN: usize = Self::MINLEN; pub const LEN: usize = Self::MAXLEN; fn new(id: u32, flags: u32, len: u32) -> Self { Self { magic: MAGIC, version: VERSION, id, flags, len, } } pub fn unpack(buf: &[u8]) -> ah::Result { if buf.len() < Self::MINLEN { return Err(err!("Failed to unpack MsgHdr")); } let hdr = Self { magic: u32_unpack(&buf[0..4]), version: u32_unpack(&buf[4..8]), id: u32_unpack(&buf[8..12]), flags: u32_unpack(&buf[12..16]), len: u32_unpack(&buf[16..20]), }; if hdr.magic != MAGIC { return Err(err!("Invalid MsgHdr magic: {:X}", hdr.magic)); } if hdr.version != VERSION { return Err(err!( "MsgHdr version {} not supported. Need version {}.", hdr.version, VERSION )); } if hdr.len > MAX_MSG_LEN as u32 - (Self::LEN as u32 + MsgCrc::LEN as u32) { return Err(err!("Invalid MsgHdr len: {:X}", hdr.len)); } if hdr.msg_len() > MAX_MSG_LEN { return Err(err!("Invalid MsgHdr len: {:X}", hdr.len)); } Ok(hdr) } fn pack(&self) -> ah::Result> { let mut buf = Vec::with_capacity(Self::LEN); buf.extend_from_slice(&u32_pack(self.magic)); buf.extend_from_slice(&u32_pack(self.version)); buf.extend_from_slice(&u32_pack(self.id)); buf.extend_from_slice(&u32_pack(self.flags)); buf.extend_from_slice(&u32_pack(self.len)); assert!(buf.len() == Self::LEN); Ok(buf) } pub fn id(&self) -> u32 { self.id } pub fn ok(&self) -> bool { self.flags & FLG_OK != 0 } pub fn set_ok(&mut self, ok: bool) { if ok { self.flags |= FLG_OK; } else { self.flags &= !FLG_OK; } } pub fn body_len(&self) -> usize { self.len as _ } pub fn msg_len(&self) -> usize { MsgHdr::LEN + self.body_len() + MsgCrc::LEN } pub fn into_msg(self, buf: &[u8]) -> ah::Result { macro_rules! with_body { ($id:ident, $variant:path, $bodystruct:ident, $body:ident) => {{ let Ok(body) = $bodystruct::unpack($body) else { return Err(err!("Failed to parse body of message with id {}.", $id)); }; Ok($variant(self, body)) }}; } check_crc32(&buf[0..MsgHdr::LEN + self.body_len() + MsgCrc::LEN])?; let body = &buf[MsgHdr::LEN..MsgHdr::LEN + self.body_len()]; match self.id { MSGID_NOP => Ok(Msg::Nop(self)), MSGID_PING => Ok(Msg::Ping(self)), MSGID_PONG => Ok(Msg::Pong(self)), MSGID_ACK => Ok(Msg::Ack(self)), MSGID_GETENV => Ok(Msg::GetEnv(self)), MSGID_ENV => with_body!(MSGID_ENV, Msg::Env, MsgBodyEnv, body), MSGID_GETSTATUS => Ok(Msg::GetStatus(self)), MSGID_STATUS => with_body!(MSGID_STATUS, Msg::Status, MsgBodyStatus, body), MSGID_CONTROL => with_body!(MSGID_CONTROL, Msg::Control, MsgBodyControl, body), MSGID_GETSETTINGS => with_body!( MSGID_GETSETTINGS, Msg::GetSettings, MsgBodyGetSettings, body ), MSGID_SETTINGS => with_body!(MSGID_SETTINGS, Msg::Settings, MsgBodySettings, body), MSGID_GETLOG => Ok(Msg::GetLog(self)), MSGID_LOG => with_body!(MSGID_LOG, Msg::Log, MsgBodyLog, body), MSGID_GETRTC => Ok(Msg::GetRtc(self)), MSGID_RTC => with_body!(MSGID_RTC, Msg::Rtc, MsgBodyRtc, body), MSGID_OTA => with_body!(MSGID_OTA, Msg::Ota, MsgBodyOta, body), _ => Err(err!("Unknown message ID: {}", self.id)), } } } pub struct MsgCrc { crc: u32, } impl MsgCrc { const MINLEN: usize = U32_LEN; const MAXLEN: usize = Self::MINLEN; pub(crate) const LEN: usize = Self::MAXLEN; fn new(crc: u32) -> Self { Self { crc } } pub fn unpack(buf: &[u8]) -> ah::Result { if buf.len() < Self::MINLEN { return Err(err!("Failed to unpack MsgCrc")); } Ok(Self { crc: u32_unpack(&buf[0..4]), }) } fn pack(&self) -> ah::Result> { let mut buf = Vec::with_capacity(Self::MAXLEN); buf.extend_from_slice(&u32_pack(self.crc)); assert!(buf.len() == Self::MAXLEN); Ok(buf) } pub fn get_crc(&self) -> u32 { self.crc } } pub struct MsgBodyEnv { temp: f32, hum: f32, hum_grad: f32, pres: f32, } impl MsgBodyEnv { const MINLEN: usize = 32; const MAXLEN: usize = Self::MINLEN; fn new(temp: f32, hum: f32, hum_grad: f32, pres: f32) -> Self { Self { temp, hum, hum_grad, pres, } } fn unpack(buf: &[u8]) -> ah::Result { if buf.len() < Self::MINLEN { return Err(err!("Failed to unpack MsgBodyEnv")); } Ok(Self { temp: u32_to_f32(u32_unpack(&buf[0..4]))?, hum: u32_to_f32(u32_unpack(&buf[4..8]))?, hum_grad: u32_to_f32(u32_unpack(&buf[8..12]))?, pres: u32_to_f32(u32_unpack(&buf[12..16]))?, }) } fn pack(&self) -> ah::Result> { let mut buf = Vec::with_capacity(Self::MAXLEN); buf.extend_from_slice(&u32_pack(f32_to_u32(self.temp)?)); buf.extend_from_slice(&u32_pack(f32_to_u32(self.hum)?)); buf.extend_from_slice(&u32_pack(f32_to_u32(self.hum_grad)?)); buf.extend_from_slice(&u32_pack(f32_to_u32(self.pres)?)); assert!(buf.len() <= Self::MAXLEN); buf.resize(Self::MAXLEN, 0); Ok(buf) } pub fn len(&self) -> usize { Self::MAXLEN } pub fn temp_c(&self) -> f32 { self.temp } pub fn rel_hum(&self) -> f32 { self.hum } pub fn rel_hum_grad(&self) -> f32 { self.hum_grad } pub fn pres_hpa(&self) -> f32 { self.pres } } const STATUS_FLG_FAN_ENCTRL: usize = 0; const STATUS_FLG_OVERRIDE: usize = 1; const STATUS_FLG_HUMGRAD_TURNON: usize = 2; const STATUS_FLG_HUMGRAD_SHUTOFF: usize = 3; pub struct MsgBodyStatus { ctrl_state: u32, fan_mode: u32, flags: u32, } impl MsgBodyStatus { const MINLEN: usize = 32; const MAXLEN: usize = Self::MINLEN; fn new( ctrl_state: u32, fan_mode: u32, fan_enable_ctrl: bool, override_active: bool, hum_grad_turnon: bool, hum_grad_shutoff: bool, ) -> Self { Self { ctrl_state, fan_mode, flags: (fan_enable_ctrl as u32) << STATUS_FLG_FAN_ENCTRL | (override_active as u32) << STATUS_FLG_OVERRIDE | (hum_grad_turnon as u32) << STATUS_FLG_HUMGRAD_TURNON | (hum_grad_shutoff as u32) << STATUS_FLG_HUMGRAD_SHUTOFF, } } fn unpack(buf: &[u8]) -> ah::Result { if buf.len() < Self::MINLEN { return Err(err!("Failed to unpack MsgBodyStatus")); } Ok(Self { ctrl_state: u32_unpack(&buf[0..4]), fan_mode: u32_unpack(&buf[4..8]), flags: u32_unpack(&buf[8..12]), }) } fn pack(&self) -> ah::Result> { let mut buf = Vec::with_capacity(Self::MAXLEN); buf.extend_from_slice(&u32_pack(self.ctrl_state)); buf.extend_from_slice(&u32_pack(self.fan_mode)); buf.extend_from_slice(&u32_pack(self.flags)); assert!(buf.len() <= Self::MAXLEN); buf.resize(Self::MAXLEN, 0); Ok(buf) } pub fn len(&self) -> usize { Self::MAXLEN } pub fn controller_state(&self) -> u32 { self.ctrl_state } pub fn fan_mode(&self) -> u32 { self.fan_mode } pub fn fan_enable_ctrl(&self) -> bool { self.flags & (1 << STATUS_FLG_FAN_ENCTRL) != 0 } pub fn override_active(&self) -> bool { self.flags & (1 << STATUS_FLG_OVERRIDE) != 0 } pub fn hum_grad_turnon(&self) -> bool { self.flags & (1 << STATUS_FLG_HUMGRAD_TURNON) != 0 } pub fn hum_grad_shutoff(&self) -> bool { self.flags & (1 << STATUS_FLG_HUMGRAD_SHUTOFF) != 0 } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum ControlCmd { #[default] Stop = 0, Trigger = 1, } impl TryFrom for ControlCmd { type Error = (); fn try_from(value: u32) -> Result { if value == ControlCmd::Stop as u32 { Ok(ControlCmd::Stop) } else if value == ControlCmd::Trigger as u32 { Ok(ControlCmd::Trigger) } else { Err(()) } } } impl From for u32 { fn from(val: ControlCmd) -> Self { val as u32 } } pub struct MsgBodyControl { cmd: ControlCmd, data: u128, } impl MsgBodyControl { const MINLEN: usize = 32; const MAXLEN: usize = Self::MINLEN; fn new(cmd: ControlCmd, data: u128) -> Self { Self { cmd, data } } fn unpack(buf: &[u8]) -> ah::Result { if buf.len() < Self::MINLEN { return Err(err!("Failed to unpack MsgBodyControl")); } let Ok(cmd) = u32_unpack(&buf[0..4]).try_into() else { return Err(err!("Failed to unpack MsgBodyControl")); }; let data = u128_unpack(&buf[4..20]); Ok(Self { cmd, data }) } fn pack(&self) -> ah::Result> { let mut buf = Vec::with_capacity(Self::MAXLEN); buf.extend_from_slice(&u32_pack(self.cmd.into())); buf.extend_from_slice(&u128_pack(self.data)); assert!(buf.len() <= Self::MAXLEN); buf.resize(Self::MAXLEN, 0); Ok(buf) } pub fn len(&self) -> usize { Self::MAXLEN } pub fn get_command(&self) -> ControlCmd { self.cmd } pub fn get_data(&self) -> u128 { self.data } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum SettingsId { #[default] NoSetting = 0, FanMinSwitchDist = 0x10000, FanOverrunDur = 0x10001, CtrlHumOnThres = 0x20000, CtrlHumOffThres = 0x20001, CtrlHumGradSampleDist = 0x20002, CtrlHumGradOnThres = 0x20003, CtrlHumGradOffThres = 0x20004, CtrlHumGradOnDelay = 0x20005, CtrlHumGradOffDelay = 0x20006, CtrlOverrideDur = 0x20007, CtrlFanOnDurMax = 0x20008, CtrlFanPauseDur = 0x20009, CtrlFanHumOnDur = 0x2000A, CtrlHibernation = 0x2000B, } impl SettingsId { pub fn get_type(&self) -> SettingsType { match self { SettingsId::NoSetting => SettingsType::UnsignedInteger, SettingsId::FanMinSwitchDist => SettingsType::Duration, SettingsId::FanOverrunDur => SettingsType::Duration, SettingsId::CtrlHumOnThres => SettingsType::Float32, SettingsId::CtrlHumOffThres => SettingsType::Float32, SettingsId::CtrlHumGradSampleDist => SettingsType::Duration, SettingsId::CtrlHumGradOnThres => SettingsType::Float32, SettingsId::CtrlHumGradOffThres => SettingsType::Float32, SettingsId::CtrlHumGradOnDelay => SettingsType::Duration, SettingsId::CtrlHumGradOffDelay => SettingsType::Duration, SettingsId::CtrlOverrideDur => SettingsType::Duration, SettingsId::CtrlFanOnDurMax => SettingsType::Duration, SettingsId::CtrlFanPauseDur => SettingsType::Duration, SettingsId::CtrlFanHumOnDur => SettingsType::Duration, SettingsId::CtrlHibernation => SettingsType::TimeMask, } } } impl TryFrom for SettingsId { type Error = (); fn try_from(value: u32) -> Result { macro_rules! check { ($value: ident, $id: path) => { if $value == $id as u32 { return Ok($id); } }; } check!(value, SettingsId::NoSetting); check!(value, SettingsId::FanMinSwitchDist); check!(value, SettingsId::FanOverrunDur); check!(value, SettingsId::CtrlHumOnThres); check!(value, SettingsId::CtrlHumOffThres); check!(value, SettingsId::CtrlHumGradSampleDist); check!(value, SettingsId::CtrlHumGradOnThres); check!(value, SettingsId::CtrlHumGradOffThres); check!(value, SettingsId::CtrlHumGradOnDelay); check!(value, SettingsId::CtrlHumGradOffDelay); check!(value, SettingsId::CtrlOverrideDur); check!(value, SettingsId::CtrlFanOnDurMax); check!(value, SettingsId::CtrlFanPauseDur); check!(value, SettingsId::CtrlFanHumOnDur); check!(value, SettingsId::CtrlHibernation); Err(()) } } impl From for u32 { fn from(val: SettingsId) -> Self { val as u32 } } pub enum SettingsType { UnsignedInteger, Float32, Duration, TimeMask, } pub struct MsgBodyGetSettings { id: SettingsId, } impl MsgBodyGetSettings { const MINLEN: usize = 16; const MAXLEN: usize = Self::MINLEN; fn new(id: SettingsId) -> Self { Self { id } } fn unpack(buf: &[u8]) -> ah::Result { if buf.len() < Self::MINLEN { return Err(err!("Failed to unpack MsgBodyGetSettings")); }; let Ok(id) = u32_unpack(&buf[0..4]).try_into() else { return Err(err!("Failed to unpack MsgBodyGetSettings")); }; Ok(Self { id }) } fn pack(&self) -> ah::Result> { let mut buf = Vec::with_capacity(Self::MAXLEN); buf.extend_from_slice(&u32_pack(self.id.into())); assert!(buf.len() <= Self::MAXLEN); buf.resize(Self::MAXLEN, 0); Ok(buf) } pub fn len(&self) -> usize { Self::MAXLEN } pub fn get_id(&self) -> SettingsId { self.id } } pub struct MsgBodySettings { id: SettingsId, value: u128, } impl MsgBodySettings { const MINLEN: usize = 32; const MAXLEN: usize = Self::MINLEN; pub fn new(id: SettingsId, value: u128) -> Self { Self { id, value } } pub fn new_f32(id: SettingsId, value: f32) -> ah::Result { Ok(Self::new(id, f32_to_u128(value)?)) } pub fn new_duration(id: SettingsId, value: Duration) -> ah::Result { Ok(Self::new(id, duration_to_u128(value)?)) } pub fn new_string(id: SettingsId, value: &str) -> ah::Result { let value = match id.get_type() { SettingsType::UnsignedInteger => value .parse::() .context("Not a valid unsigned integer.")?, SettingsType::Float32 => { f32_to_u128(value.parse::().context("Not a valid float32.")?) .context("Not an acceptable float32.")? } SettingsType::Duration => duration_to_u128( string_to_duration(value).context("Not an acceptable Duration format.")?, ) .context("Not a valid Duration.")?, SettingsType::TimeMask => { let value: TimeMask = value.try_into().context("Not a valid TimeMask")?; value.into() } }; Ok(Self::new(id, value)) } pub fn new_timemask(id: SettingsId, value: TimeMask) -> ah::Result { Ok(Self::new(id, value.into())) } fn unpack(buf: &[u8]) -> ah::Result { if buf.len() < Self::MINLEN { return Err(err!("Failed to unpack MsgBodySettings")); } let Ok(id) = u32_unpack(&buf[0..4]).try_into() else { return Err(err!("Failed to unpack MsgBodySettings")); }; let value = u128_unpack(&buf[4..20]); Ok(Self { id, value }) } fn pack(&self) -> ah::Result> { let mut buf = Vec::with_capacity(Self::MAXLEN); buf.extend_from_slice(&u32_pack(self.id.into())); buf.extend_from_slice(&u128_pack(self.value)); assert!(buf.len() <= Self::MAXLEN); buf.resize(Self::MAXLEN, 0); Ok(buf) } pub fn len(&self) -> usize { Self::MAXLEN } pub fn get_id(&self) -> SettingsId { self.id } pub fn get_value(&self) -> u128 { self.value } pub fn get_value_f32(&self) -> ah::Result { u128_to_f32(self.value) } pub fn get_value_duration(&self) -> ah::Result { u128_to_duration(self.value) } pub fn get_value_timemask(&self) -> ah::Result { Ok(self.value.into()) } pub fn get_value_string(&self) -> ah::Result { Ok(match self.id.get_type() { SettingsType::UnsignedInteger => format!("{}", self.get_value()), SettingsType::Float32 => format!( "{}", self.get_value_f32().context("Not an acceptable float32.")? ), SettingsType::Duration => duration_to_string( self.get_value_duration() .context("Not an acceptable Duration format.")?, ), SettingsType::TimeMask => self .get_value_timemask() .context("Not an acceptable TimeMask format.")? .into(), }) } } pub struct MsgBodyLog { stamp: DateTime, msg: Vec, } impl MsgBodyLog { const STATICPARTLEN: usize = U64_LEN; const MINLEN: usize = Self::STATICPARTLEN; const MAXLEN: usize = MAX_BODY_LEN; fn new(stamp: DateTime, msg: &str) -> Self { let slen = floor_char_boundary(msg, Self::MAXLEN - Self::STATICPARTLEN); let msg = msg.as_bytes()[..slen].to_vec(); Self { stamp, msg } } fn unpack(buf: &[u8]) -> ah::Result { if buf.len() < Self::MINLEN { return Err(err!("Failed to unpack MsgBodyLog")); } let stamp = u64_to_datetime(u64_unpack(&buf[0..8]))?; let msg = buf[8..].to_vec(); Ok(Self { stamp, msg }) } fn pack(&self) -> ah::Result> { let mut buf = Vec::with_capacity(self.len()); buf.extend_from_slice(&u64_pack(datetime_to_u64(self.stamp))); buf.extend_from_slice(&self.msg); assert!(buf.len() <= Self::MAXLEN); Ok(buf) } pub fn len(&self) -> usize { Self::STATICPARTLEN + self.msg.len() } pub fn stamp(&self) -> DateTime { self.stamp } pub fn message(&self) -> String { String::from_utf8_lossy(&self.msg).into_owned() } } pub struct MsgBodyRtc { dt: DateTime, } impl MsgBodyRtc { const MINLEN: usize = 32; const MAXLEN: usize = Self::MINLEN; fn new(dt: DateTime) -> Self { Self { dt } } fn unpack(buf: &[u8]) -> ah::Result { if buf.len() < Self::MINLEN { return Err(err!("Failed to unpack MsgBodyRtc")); } let dt = u64_to_datetime(u64_unpack(&buf[0..8]))?; Ok(Self { dt }) } fn pack(&self) -> ah::Result> { let mut buf = Vec::with_capacity(Self::MAXLEN); buf.extend_from_slice(&u64_pack(datetime_to_u64(self.dt))); assert!(buf.len() <= Self::MAXLEN); buf.resize(Self::MAXLEN, 0); Ok(buf) } pub fn len(&self) -> usize { Self::MAXLEN } pub fn datetime(&self) -> DateTime { self.dt } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum OtaOperation { Start, Intermediate, Finish, } impl TryFrom for OtaOperation { type Error = (); fn try_from(value: u32) -> Result { macro_rules! check { ($value: ident, $id: path) => { if $value == $id as u32 { return Ok($id); } }; } check!(value, OtaOperation::Start); check!(value, OtaOperation::Intermediate); check!(value, OtaOperation::Finish); Err(()) } } impl From for u32 { fn from(val: OtaOperation) -> Self { val as u32 } } pub struct MsgBodyOta { op: OtaOperation, offs: u32, data: Vec, } impl MsgBodyOta { const STATICPARTLEN: usize = U32_LEN + U32_LEN; const MINLEN: usize = Self::STATICPARTLEN; const MAXLEN: usize = MAX_BODY_LEN; pub const MAXDATALEN: usize = Self::MAXLEN - Self::STATICPARTLEN; fn new(op: OtaOperation, offs: u32, data: Vec) -> ah::Result { if data.len() > Self::MAXDATALEN { return Err(err!("MsgBodyOta: Payload is too long")); } Ok(Self { op, offs, data }) } fn unpack(buf: &[u8]) -> ah::Result { if buf.len() < Self::MINLEN { return Err(err!("Failed to unpack MsgBodyOta")); } let Ok(op) = u32_unpack(&buf[0..4]).try_into() else { return Err(err!("Failed to unpack MsgBodyOta")); }; let offs = u32_unpack(&buf[4..8]); let data = buf[8..].to_vec(); Ok(Self { op, offs, data }) } fn pack(&self) -> ah::Result> { let mut buf = Vec::with_capacity(self.len()); buf.extend_from_slice(&u32_pack(self.op.into())); buf.extend_from_slice(&u32_pack(self.offs)); buf.extend_from_slice(&self.data); assert!(buf.len() <= Self::MAXLEN); Ok(buf) } pub fn len(&self) -> usize { Self::STATICPARTLEN + self.data.len() } pub fn op(&self) -> OtaOperation { self.op } pub fn offs(&self) -> u32 { self.offs } pub fn data(&self) -> &[u8] { &self.data } } pub enum Msg { Nop(MsgHdr), Ping(MsgHdr), Pong(MsgHdr), Ack(MsgHdr), GetEnv(MsgHdr), Env(MsgHdr, MsgBodyEnv), GetStatus(MsgHdr), Status(MsgHdr, MsgBodyStatus), Control(MsgHdr, MsgBodyControl), GetSettings(MsgHdr, MsgBodyGetSettings), Settings(MsgHdr, MsgBodySettings), GetLog(MsgHdr), Log(MsgHdr, MsgBodyLog), GetRtc(MsgHdr), Rtc(MsgHdr, MsgBodyRtc), Ota(MsgHdr, MsgBodyOta), } macro_rules! msg_get_hdr { ($msg:expr) => { match $msg { Self::Nop(hdr) => hdr, Self::Ping(hdr) => hdr, Self::Pong(hdr) => hdr, Self::Ack(hdr) => hdr, Self::GetEnv(hdr) => hdr, Self::Env(hdr, _) => hdr, Self::GetStatus(hdr) => hdr, Self::Status(hdr, _) => hdr, Self::Control(hdr, _) => hdr, Self::GetSettings(hdr, _) => hdr, Self::Settings(hdr, _) => hdr, Self::GetLog(hdr) => hdr, Self::Log(hdr, _) => hdr, Self::GetRtc(hdr) => hdr, Self::Rtc(hdr, _) => hdr, Self::Ota(hdr, _) => hdr, } }; } impl Msg { pub fn new_nop() -> Msg { Msg::Nop(MsgHdr::new(MSGID_NOP, FLG_OK, 0)) } pub fn new_ping() -> Msg { Msg::Ping(MsgHdr::new(MSGID_PING, FLG_OK, 0)) } pub fn new_pong() -> Msg { Msg::Pong(MsgHdr::new(MSGID_PONG, FLG_OK, 0)) } pub fn new_ack() -> Msg { Msg::Ack(MsgHdr::new(MSGID_ACK, FLG_OK, 0)) } pub fn new_getenv() -> Msg { Msg::GetEnv(MsgHdr::new(MSGID_GETENV, FLG_OK, 0)) } pub fn new_env(temp_c: f32, rel_hum: f32, rel_hum_grad: f32, pres_hpa: f32) -> Msg { let body = MsgBodyEnv::new(temp_c, rel_hum, rel_hum_grad, pres_hpa); Msg::Env(MsgHdr::new(MSGID_ENV, FLG_OK, body.len() as u32), body) } pub fn new_getstatus() -> Msg { Msg::GetStatus(MsgHdr::new(MSGID_GETSTATUS, FLG_OK, 0)) } pub fn new_status( ctrl_state: u32, fan_mode: u32, fan_enable_ctrl: bool, override_active: bool, hum_grad_turnon: bool, hum_grad_shutoff: bool, ) -> Msg { let body = MsgBodyStatus::new( ctrl_state, fan_mode, fan_enable_ctrl, override_active, hum_grad_turnon, hum_grad_shutoff, ); Msg::Status(MsgHdr::new(MSGID_STATUS, FLG_OK, body.len() as u32), body) } pub fn new_control(cmd: ControlCmd, data: u128) -> Msg { let body = MsgBodyControl::new(cmd, data); Msg::Control(MsgHdr::new(MSGID_CONTROL, FLG_OK, body.len() as u32), body) } pub fn new_getsettings(id: SettingsId) -> Msg { let body = MsgBodyGetSettings::new(id); Msg::GetSettings( MsgHdr::new(MSGID_GETSETTINGS, FLG_OK, body.len() as u32), body, ) } pub fn new_settings(value: MsgBodySettings) -> Msg { Msg::Settings( MsgHdr::new(MSGID_SETTINGS, FLG_OK, value.len() as u32), value, ) } pub fn new_getlog() -> Msg { Msg::GetLog(MsgHdr::new(MSGID_GETLOG, FLG_OK, 0)) } pub fn new_log(stamp: DateTime, msg: &str) -> Msg { let body = MsgBodyLog::new(stamp, msg); Msg::Log(MsgHdr::new(MSGID_LOG, FLG_OK, body.len() as u32), body) } pub fn new_getrtc() -> Msg { Msg::GetRtc(MsgHdr::new(MSGID_GETRTC, FLG_OK, 0)) } pub fn new_rtc(dt: DateTime) -> Msg { let body = MsgBodyRtc::new(dt); Msg::Rtc(MsgHdr::new(MSGID_RTC, FLG_OK, body.len() as u32), body) } pub fn new_ota(op: OtaOperation, offs: u32, data: Vec) -> ah::Result { let body = MsgBodyOta::new(op, offs, data)?; Ok(Msg::Ota( MsgHdr::new(MSGID_OTA, FLG_OK, body.len() as u32), body, )) } fn get_hdr_mut(&mut self) -> &mut MsgHdr { msg_get_hdr!(self) } pub fn get_hdr(&self) -> &MsgHdr { msg_get_hdr!(self) } pub fn set_ok(mut self, ok: bool) -> Self { self.get_hdr_mut().set_ok(ok); self } pub fn into_bytes(self) -> ah::Result> { macro_rules! with_body { ($hdr:ident, $body:ident) => {{ let mut bytes = $hdr.pack()?; bytes.extend_from_slice(&$body.pack()?); bytes }}; } let mut bytes = match self { Self::Nop(hdr) => hdr.pack()?, Self::Ping(hdr) => hdr.pack()?, Self::Pong(hdr) => hdr.pack()?, Self::Ack(hdr) => hdr.pack()?, Self::GetEnv(hdr) => hdr.pack()?, Self::Env(hdr, body) => with_body!(hdr, body), Self::GetStatus(hdr) => hdr.pack()?, Self::Status(hdr, body) => with_body!(hdr, body), Self::Control(hdr, body) => with_body!(hdr, body), Self::GetSettings(hdr, body) => with_body!(hdr, body), Self::Settings(hdr, body) => with_body!(hdr, body), Self::GetLog(hdr) => hdr.pack()?, Self::Log(hdr, body) => with_body!(hdr, body), Self::GetRtc(hdr) => hdr.pack()?, Self::Rtc(hdr, body) => with_body!(hdr, body), Self::Ota(hdr, body) => with_body!(hdr, body), }; let crc = MsgCrc::new(crc32(&bytes)).pack()?; bytes.extend_from_slice(&crc); Ok(bytes) } } // vim: ts=4 sw=4 expandtab