taspromto/src/device/rf.rs
2026-06-27 04:02:09 +02:00

155 lines
3.9 KiB
Rust

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<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let str = <Cow<'de, 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<Self, Self::Err> {
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<RfPayload<'a>> {
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()
)
}