use std::borrow::Cow; use std::fmt::{self, Display}; use std::num::ParseIntError; use std::str::FromStr; use serde::{Deserialize, Deserializer, de::Error}; use crate::device::FormattableDevice; #[derive(Debug)] pub struct TempState { pub name: String, pub id: RfDeviceId, pub temperature: f32, pub humidity: u8, } impl TempState { pub fn new(id: RfDeviceId, name: String) -> Self { TempState { name, id, temperature: 0.0, humidity: 0, } } } impl FormattableDevice for TempState { fn format_open_metrics(&self) -> impl Display + '_ { TempFormatter { device: self } } } struct TempFormatter<'a> { device: &'a TempState, } impl Display for TempFormatter<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.device.temperature > 0.0 { writeln!( f, "sensor_temperature{{model=\"{}\", id=\"{}\", channel=\"{}\", name=\"{}\"}} {}", self.device.id.name, self.device.id.id, self.device.id.channel, self.device.name, self.device.temperature )?; } if self.device.humidity > 0 { writeln!( f, "sensor_humidity{{model=\"{}\", id=\"{}\", channel=\"{}\", name=\"{}\"}} {}", self.device.id.name, self.device.id.id, self.device.id.channel, self.device.name, self.device.humidity )?; } Ok(()) } } #[derive(Debug, PartialEq)] pub struct RfPayload<'a> { pub name: &'a str, pub id: u16, pub channel: u8, pub battery: bool, pub temperature: f32, pub humidity: u8, } impl<'a> RfPayload<'a> { pub fn device_id(&self) -> RfDeviceId { RfDeviceId { name: self.name.into(), id: self.id, channel: self.channel, } } } #[derive(Hash, PartialEq, Eq, Debug, Default, Clone)] pub struct RfDeviceId { pub name: String, pub id: u16, pub channel: u8, } impl<'de> Deserialize<'de> for RfDeviceId { fn deserialize(deserializer: D) -> std::result::Result where D: Deserializer<'de>, { let str = >::deserialize(deserializer)?; Self::from_str(&str).map_err(D::Error::custom) } } impl FromStr for RfDeviceId { type Err = ParseIntError; fn from_str(s: &str) -> Result { let mut parts = s.splitn(3, ':'); let name = parts.next().unwrap_or_default(); let id = parts.next().unwrap_or_default().parse()?; let channel = parts.next().unwrap_or_default().parse()?; Ok(RfDeviceId { name: name.into(), id, channel, }) } } pub fn parse_rf_payload<'a>(payload: &'a str) -> Option> { let mut parts = payload.split(";").skip(2); let name = parts.next()?; let id = parts.next()?.strip_prefix("ID=")?.parse().ok()?; let channel = parts.next()?.strip_prefix("CHN=")?.parse().ok()?; let battery = parts.next()?.strip_prefix("BAT=")? == "OK"; let temperature = parts.next()?.strip_prefix("TEMP=")?; let temperature = u32::from_str_radix(temperature, 16).ok()?; let humidity = parts.next()?.strip_prefix("HUM=")?.parse().ok()?; Some(RfPayload { name, id, channel, battery, temperature: temperature as f32 / 10.0, humidity, }) } #[test] fn test_rf_payload() { assert_eq!( RfPayload { name: "Bresser-3CH", id: 49, channel: 1, battery: true, temperature: 16.1, humidity: 58 }, parse_rf_payload("20;1E;Bresser-3CH;ID=49;CHN=0001;BAT=OK;TEMP=00a1;HUM=58;").unwrap() ) }