mirror of
https://codeberg.org/icewind/taspromto.git
synced 2026-08-02 04:04:44 +02:00
cleanup rf formatting
This commit is contained in:
parent
a9a2f5270e
commit
16993948cf
4 changed files with 166 additions and 153 deletions
|
|
@ -50,7 +50,7 @@ impl MiTempDevice {
|
|||
}
|
||||
|
||||
pub fn format_prometheus(&self) -> impl Display + '_ {
|
||||
MitempFormatter { device: &self }
|
||||
MitempFormatter { device: self }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
mod mitemp;
|
||||
mod rf;
|
||||
|
||||
pub use crate::device::mitemp::MiTempDevice;
|
||||
use color_eyre::{Report, Result, eyre::WrapErr};
|
||||
use jzon::JsonValue;
|
||||
pub use mitemp::MiTempDevice;
|
||||
pub use rf::{RfDeviceId, TempState};
|
||||
use rumqttc::{AsyncClient, QoS};
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
|
@ -10,11 +12,11 @@ use std::borrow::Cow;
|
|||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||
use std::num::ParseIntError;
|
||||
use std::str::FromStr;
|
||||
use std::time::Instant;
|
||||
use tokio::task::spawn;
|
||||
|
||||
use crate::device::rf::parse_rf_payload;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DeviceStates {
|
||||
pub mi_temp_names: BTreeMap<BDAddr, String>,
|
||||
|
|
@ -92,7 +94,7 @@ impl DeviceStates {
|
|||
let state = self
|
||||
.rf_temp_devices
|
||||
.entry(data.device_id())
|
||||
.or_insert_with(|| TempState::new(name.clone()));
|
||||
.or_insert_with(|| TempState::new(id.clone(), name.clone()));
|
||||
state.humidity = data.humidity;
|
||||
state.temperature = data.temperature;
|
||||
}
|
||||
|
|
@ -121,7 +123,7 @@ impl DeviceStates {
|
|||
let state = self
|
||||
.rf_temp_devices
|
||||
.entry(self.active_rf_temp_id.clone())
|
||||
.or_insert_with(|| TempState::new(name.clone()));
|
||||
.or_insert_with(|| TempState::new(self.active_rf_temp_id.clone(), name.clone()));
|
||||
match field {
|
||||
"temperature_C" => state.temperature = payload.parse().unwrap_or_default(),
|
||||
"temperature_F" => {
|
||||
|
|
@ -420,53 +422,6 @@ pub fn format_device_state<W: Write>(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TempState {
|
||||
name: String,
|
||||
temperature: f32,
|
||||
humidity: u8,
|
||||
}
|
||||
|
||||
impl TempState {
|
||||
pub fn new(name: String) -> Self {
|
||||
TempState {
|
||||
name,
|
||||
temperature: 0.0,
|
||||
humidity: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_rf_temp_state<W: Write>(
|
||||
mut writer: W,
|
||||
channel: &RfDeviceId,
|
||||
names: &HashMap<RfDeviceId, String>,
|
||||
state: &TempState,
|
||||
) -> fmt::Result {
|
||||
let name = if let Some(name) = names.get(channel) {
|
||||
name
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if state.temperature > 0.0 {
|
||||
writeln!(
|
||||
writer,
|
||||
"sensor_temperature{{model=\"{}\", id=\"{}\", channel=\"{}\", name=\"{}\"}} {}",
|
||||
channel.name, channel.id, channel.channel, name, state.temperature
|
||||
)?;
|
||||
}
|
||||
|
||||
if state.humidity > 0 {
|
||||
writeln!(
|
||||
writer,
|
||||
"sensor_humidity{{model=\"{}\", id=\"{}\", channel=\"{}\", name=\"{}\"}} {}",
|
||||
channel.name, channel.id, channel.channel, name, state.humidity
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn format_dsmr_state<W: Write>(mut writer: W, device: &str, state: &DsmrState) -> fmt::Result {
|
||||
let power_total = state.power_total_tariff_1.unwrap_or_default()
|
||||
+ state.power_total_tariff_2.unwrap_or_default();
|
||||
|
|
@ -733,91 +688,3 @@ pub fn format_pms_state<W: Write>(
|
|||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct RfPayload<'a> {
|
||||
name: &'a str,
|
||||
id: u16,
|
||||
channel: u8,
|
||||
battery: bool,
|
||||
temperature: f32,
|
||||
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 {
|
||||
name: String,
|
||||
id: u16,
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
|
|
|||
151
src/device/rf.rs
Normal file
151
src/device/rf.rs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Display};
|
||||
use std::num::ParseIntError;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Deserializer, de::Error};
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_prometheus(&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()
|
||||
)
|
||||
}
|
||||
19
src/main.rs
19
src/main.rs
|
|
@ -4,9 +4,7 @@ mod mqtt;
|
|||
mod topic;
|
||||
|
||||
use crate::config::{Config, ListenConfig};
|
||||
use crate::device::{
|
||||
Device, DeviceStates, format_device_state, format_dsmr_state, format_rf_temp_state,
|
||||
};
|
||||
use crate::device::{Device, DeviceStates, format_device_state, format_dsmr_state};
|
||||
use crate::mqtt::mqtt_stream;
|
||||
use crate::topic::Topic;
|
||||
use clap::Parser;
|
||||
|
|
@ -43,8 +41,7 @@ async fn main() -> Result<()> {
|
|||
};
|
||||
let mqtt_options = config.mqtt()?;
|
||||
|
||||
let device_states =
|
||||
DeviceStates::new(config.names.mi_temp.clone(), config.names.rf_temp.clone());
|
||||
let device_states = DeviceStates::new(config.names.mi_temp, config.names.rf_temp);
|
||||
let device_states = Arc::new(Mutex::new(device_states));
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
|
|
@ -52,7 +49,7 @@ async fn main() -> Result<()> {
|
|||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
|
||||
spawn(serve(device_states.clone(), config));
|
||||
spawn(serve(device_states.clone(), config.listen));
|
||||
|
||||
loop {
|
||||
let (client, stream) = mqtt_stream(mqtt_options.clone())
|
||||
|
|
@ -73,9 +70,7 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
async fn serve(device_states: Arc<Mutex<DeviceStates>>, config: Config) {
|
||||
let rf_temp_names = config.names.rf_temp;
|
||||
|
||||
async fn serve(device_states: Arc<Mutex<DeviceStates>>, listen: ListenConfig) {
|
||||
let state = warp::any().map(move || device_states.clone());
|
||||
|
||||
let metrics = warp::path!("metrics")
|
||||
|
|
@ -92,14 +87,14 @@ async fn serve(device_states: Arc<Mutex<DeviceStates>>, config: Config) {
|
|||
for (_, state) in state.mi_temp() {
|
||||
write!(&mut response, "{}", state.format_prometheus()).expect("formatting failed");
|
||||
}
|
||||
for (channel, state) in state.rf_temp() {
|
||||
format_rf_temp_state(&mut response, channel, &rf_temp_names, state).unwrap()
|
||||
for (_, state) in state.rf_temp() {
|
||||
write!(&mut response, "{}", state.format_prometheus()).expect("formatting failed");
|
||||
}
|
||||
response
|
||||
});
|
||||
|
||||
let cancel = async { ctrl_c().await.unwrap() };
|
||||
match config.listen {
|
||||
match listen {
|
||||
ListenConfig::Ip { address, port } => {
|
||||
warp::serve(metrics)
|
||||
.bind((address, port))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue