cleanup dsmr

This commit is contained in:
Robin Appelman 2026-06-27 00:13:40 +02:00
commit 74463cc56e
5 changed files with 114 additions and 85 deletions

98
src/device/dsmr.rs Normal file
View file

@ -0,0 +1,98 @@
use std::{
fmt::{self, Display},
time::Instant,
};
pub enum DsmrMessageType {
Water,
Gas,
Energy1,
Energy2,
Power,
}
#[derive(Debug)]
pub struct DsmrState {
pub name: String,
pub power: Option<f32>,
pub power_total_tariff_1: Option<f32>,
pub power_total_tariff_2: Option<f32>,
pub gas_total: Option<f32>,
pub water_total: Option<f32>,
pub last_seen: Instant,
}
impl DsmrState {
pub fn new(name: String) -> Self {
DsmrState {
name,
power: None,
power_total_tariff_1: None,
power_total_tariff_2: None,
gas_total: None,
water_total: None,
last_seen: Instant::now(),
}
}
pub fn format_open_metrics(&self) -> impl Display + '_ {
DsmrFormatter { device: self }
}
}
struct DsmrFormatter<'a> {
device: &'a DsmrState,
}
impl Display for DsmrFormatter<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let power_total = self.device.power_total_tariff_1.unwrap_or_default()
+ self.device.power_total_tariff_2.unwrap_or_default();
if power_total > 0.0 {
writeln!(
f,
"power_total_kwh{{name=\"{}\"}} {}",
self.device.name, power_total
)?;
}
if let Some(power) = self.device.power_total_tariff_1 {
writeln!(
f,
"power_total_low_kwh{{name=\"{}\"}} {}",
self.device.name, power
)?;
}
if let Some(power) = self.device.power_total_tariff_2 {
writeln!(
f,
"power_total_high_kwh{{name=\"{}\"}} {}",
self.device.name, power
)?;
}
if let Some(power) = self.device.power {
writeln!(
f,
"power_watts{{name=\"{}\"}} {}",
self.device.name,
power * 1000.0
)?;
}
if let Some(gas) = self.device.gas_total {
writeln!(f, "gas_total_m3{{name=\"{}\"}} {}", self.device.name, gas)?;
}
if let Some(water) = self.device.water_total {
writeln!(
f,
"water_total_m3{{name=\"{}\"}} {}",
self.device.name, water
)?;
}
Ok(())
}
}

View file

@ -49,7 +49,7 @@ impl MiTempDevice {
} }
} }
pub fn format_prometheus(&self) -> impl Display + '_ { pub fn format_open_metrics(&self) -> impl Display + '_ {
MitempFormatter { device: self } MitempFormatter { device: self }
} }
} }

View file

@ -1,7 +1,9 @@
mod dsmr;
mod mitemp; mod mitemp;
mod rf; mod rf;
use color_eyre::{Report, Result, eyre::WrapErr}; use color_eyre::{Report, Result, eyre::WrapErr};
pub use dsmr::{DsmrMessageType, DsmrState};
use jzon::JsonValue; use jzon::JsonValue;
pub use mitemp::MiTempDevice; pub use mitemp::MiTempDevice;
pub use rf::{RfDeviceId, TempState}; pub use rf::{RfDeviceId, TempState};
@ -75,7 +77,10 @@ impl DeviceStates {
pub fn update_dsmr(&mut self, device: Device, ty: DsmrMessageType, payload: &str) { pub fn update_dsmr(&mut self, device: Device, ty: DsmrMessageType, payload: &str) {
if let Ok(value) = payload.parse() { if let Ok(value) = payload.parse() {
let state = self.dsmr_devices.entry(device).or_default(); let state = self
.dsmr_devices
.entry(device)
.or_insert_with_key(|device| DsmrState::new(device.hostname.clone()));
match ty { match ty {
DsmrMessageType::Water => state.water_total = Some(value), DsmrMessageType::Water => state.water_total = Some(value),
DsmrMessageType::Gas => state.gas_total = Some(value), DsmrMessageType::Gas => state.gas_total = Some(value),
@ -230,37 +235,6 @@ impl Default for DeviceState {
} }
} }
pub enum DsmrMessageType {
Water,
Gas,
Energy1,
Energy2,
Power,
}
#[derive(Debug)]
pub struct DsmrState {
pub power: Option<f32>,
pub power_total_tariff_1: Option<f32>,
pub power_total_tariff_2: Option<f32>,
pub gas_total: Option<f32>,
pub water_total: Option<f32>,
pub last_seen: Instant,
}
impl Default for DsmrState {
fn default() -> Self {
DsmrState {
power: None,
power_total_tariff_1: None,
power_total_tariff_2: None,
gas_total: None,
water_total: None,
last_seen: Instant::now(),
}
}
}
impl DeviceState { impl DeviceState {
pub fn update(&mut self, json: JsonValue) { pub fn update(&mut self, json: JsonValue) {
self.last_seen = Instant::now(); self.last_seen = Instant::now();
@ -422,52 +396,6 @@ pub fn format_device_state<W: Write>(
Ok(()) 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();
if power_total > 0.0 {
writeln!(
writer,
"power_total_kwh{{name=\"{}\"}} {}",
device, power_total
)?;
}
if let Some(power) = state.power_total_tariff_1 {
writeln!(
writer,
"power_total_low_kwh{{name=\"{}\"}} {}",
device, power
)?;
}
if let Some(power) = state.power_total_tariff_2 {
writeln!(
writer,
"power_total_high_kwh{{name=\"{}\"}} {}",
device, power
)?;
}
if let Some(power) = state.power {
writeln!(
writer,
"power_watts{{name=\"{}\"}} {}",
device,
power * 1000.0
)?;
}
if let Some(gas) = state.gas_total {
writeln!(writer, "gas_total_m3{{name=\"{}\"}} {}", device, gas)?;
}
if let Some(water) = state.water_total {
writeln!(writer, "water_total_m3{{name=\"{}\"}} {}", device, water)?;
}
Ok(())
}
/// Stores the 6 byte address used to identify Bluetooth devices. /// Stores the 6 byte address used to identify Bluetooth devices.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Default, Ord, PartialOrd)] #[derive(Copy, Clone, Hash, Eq, PartialEq, Default, Ord, PartialOrd)]
#[repr(C)] #[repr(C)]

View file

@ -23,7 +23,7 @@ impl TempState {
} }
} }
pub fn format_prometheus(&self) -> impl Display + '_ { pub fn format_open_metrics(&self) -> impl Display + '_ {
TempFormatter { device: self } TempFormatter { device: self }
} }
} }

View file

@ -4,7 +4,7 @@ mod mqtt;
mod topic; mod topic;
use crate::config::{Config, ListenConfig}; use crate::config::{Config, ListenConfig};
use crate::device::{Device, DeviceStates, format_device_state, format_dsmr_state}; use crate::device::{Device, DeviceStates, format_device_state};
use crate::mqtt::mqtt_stream; use crate::mqtt::mqtt_stream;
use crate::topic::Topic; use crate::topic::Topic;
use clap::Parser; use clap::Parser;
@ -81,14 +81,17 @@ async fn serve(device_states: Arc<Mutex<DeviceStates>>, listen: ListenConfig) {
for (device, state) in state.devices() { for (device, state) in state.devices() {
format_device_state(&mut response, device, state).unwrap(); format_device_state(&mut response, device, state).unwrap();
} }
for (device, state) in state.dsmr_devices() { for (_, state) in state.dsmr_devices() {
format_dsmr_state(&mut response, device.hostname.as_str(), state).unwrap(); write!(&mut response, "{}", state.format_open_metrics())
.expect("formatting failed");
} }
for (_, state) in state.mi_temp() { for (_, state) in state.mi_temp() {
write!(&mut response, "{}", state.format_prometheus()).expect("formatting failed"); write!(&mut response, "{}", state.format_open_metrics())
.expect("formatting failed");
} }
for (_, state) in state.rf_temp() { for (_, state) in state.rf_temp() {
write!(&mut response, "{}", state.format_prometheus()).expect("formatting failed"); write!(&mut response, "{}", state.format_open_metrics())
.expect("formatting failed");
} }
response response
}); });