mirror of
https://codeberg.org/icewind/taspromto.git
synced 2026-08-02 12:14:45 +02:00
cleanup tasmota formatting
This commit is contained in:
parent
74463cc56e
commit
7955380859
3 changed files with 398 additions and 373 deletions
|
|
@ -1,11 +1,13 @@
|
||||||
mod dsmr;
|
mod dsmr;
|
||||||
mod mitemp;
|
mod mitemp;
|
||||||
mod rf;
|
mod rf;
|
||||||
|
mod tasmota;
|
||||||
|
|
||||||
use color_eyre::{Report, Result, eyre::WrapErr};
|
use color_eyre::{Report, Result, eyre::WrapErr};
|
||||||
pub use dsmr::{DsmrMessageType, DsmrState};
|
pub use dsmr::{DsmrMessageType, DsmrState};
|
||||||
use jzon::JsonValue;
|
use jzon::JsonValue;
|
||||||
pub use mitemp::MiTempDevice;
|
pub use mitemp::MiTempDevice;
|
||||||
|
use rf::parse_rf_payload;
|
||||||
pub use rf::{RfDeviceId, TempState};
|
pub use rf::{RfDeviceId, TempState};
|
||||||
use rumqttc::{AsyncClient, QoS};
|
use rumqttc::{AsyncClient, QoS};
|
||||||
use serde::de::Error;
|
use serde::de::Error;
|
||||||
|
|
@ -13,12 +15,11 @@ use serde::{Deserialize, Deserializer};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{self, Debug, Display, Formatter, Write};
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
pub use tasmota::DeviceState;
|
||||||
use tokio::task::spawn;
|
use tokio::task::spawn;
|
||||||
|
|
||||||
use crate::device::rf::parse_rf_payload;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct DeviceStates {
|
pub struct DeviceStates {
|
||||||
pub mi_temp_names: BTreeMap<BDAddr, String>,
|
pub mi_temp_names: BTreeMap<BDAddr, String>,
|
||||||
|
|
@ -52,7 +53,10 @@ impl DeviceStates {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, device: Device, json: JsonValue) {
|
pub fn update(&mut self, device: Device, json: JsonValue) {
|
||||||
let device = self.devices.entry(device).or_default();
|
let device = self
|
||||||
|
.devices
|
||||||
|
.entry(device)
|
||||||
|
.or_insert_with_key(|device| DeviceState::new(device.hostname.clone()));
|
||||||
|
|
||||||
for (key, value) in json.entries() {
|
for (key, value) in json.entries() {
|
||||||
if let Some(addr) = key.strip_prefix("MJ_HT_V1") {
|
if let Some(addr) = key.strip_prefix("MJ_HT_V1") {
|
||||||
|
|
@ -196,206 +200,6 @@ impl Device {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DeviceState {
|
|
||||||
pub state: Option<bool>,
|
|
||||||
pub name: String,
|
|
||||||
pub power_watts: Option<f32>,
|
|
||||||
pub power_yesterday: Option<f32>,
|
|
||||||
pub power_today: Option<f32>,
|
|
||||||
pub power_total: Option<f32>,
|
|
||||||
pub power_total_low: Option<f32>,
|
|
||||||
pub power_total_high: Option<f32>,
|
|
||||||
pub gas_total: Option<f32>,
|
|
||||||
pub co2: Option<f32>,
|
|
||||||
pub pms_state: Option<PMSState>,
|
|
||||||
pub last_seen: Instant,
|
|
||||||
pub firmware: String,
|
|
||||||
pub version: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DeviceState {
|
|
||||||
fn default() -> Self {
|
|
||||||
DeviceState {
|
|
||||||
state: Default::default(),
|
|
||||||
name: Default::default(),
|
|
||||||
power_watts: Default::default(),
|
|
||||||
power_yesterday: Default::default(),
|
|
||||||
power_today: Default::default(),
|
|
||||||
power_total: Default::default(),
|
|
||||||
power_total_low: Default::default(),
|
|
||||||
power_total_high: Default::default(),
|
|
||||||
gas_total: Default::default(),
|
|
||||||
co2: Default::default(),
|
|
||||||
pms_state: Default::default(),
|
|
||||||
last_seen: Instant::now(),
|
|
||||||
firmware: Default::default(),
|
|
||||||
version: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeviceState {
|
|
||||||
pub fn update(&mut self, json: JsonValue) {
|
|
||||||
self.last_seen = Instant::now();
|
|
||||||
|
|
||||||
if json["DeviceName"].is_string() && !json["DeviceName"].is_empty() {
|
|
||||||
self.name = json["DeviceName"].to_string();
|
|
||||||
}
|
|
||||||
if json["POWER"].is_string() && !json["POWER"].is_empty() {
|
|
||||||
self.state = Some(json["POWER"] == "ON");
|
|
||||||
}
|
|
||||||
if let Some(power) = json["ENERGY"]["Power"].as_number().map(f32::from) {
|
|
||||||
self.power_watts = Some(power);
|
|
||||||
}
|
|
||||||
if let Some(yesterday) = json["ENERGY"]["Yesterday"].as_number().map(f32::from) {
|
|
||||||
self.power_yesterday = Some(yesterday);
|
|
||||||
}
|
|
||||||
if let Some(today) = json["ENERGY"]["Today"].as_number().map(f32::from) {
|
|
||||||
self.power_today = Some(today);
|
|
||||||
}
|
|
||||||
if let Some(co2) = json["MHZ19B"]["CarbonDioxide"].as_number().map(f32::from) {
|
|
||||||
if co2 > 1.0 {
|
|
||||||
self.co2 = Some(co2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(power) = json["OBIS"]["Power"].as_number().map(f32::from) {
|
|
||||||
self.power_watts = Some(power);
|
|
||||||
}
|
|
||||||
if let Some(total) = json["OBIS"]["Total"].as_number().map(f32::from) {
|
|
||||||
self.power_total = Some(total);
|
|
||||||
}
|
|
||||||
if let Some(total) = json["OBIS"]["Total_high"].as_number().map(f32::from) {
|
|
||||||
self.power_total_high = Some(total);
|
|
||||||
}
|
|
||||||
if let Some(total) = json["OBIS"]["Total_low"].as_number().map(f32::from) {
|
|
||||||
self.power_total_low = Some(total);
|
|
||||||
}
|
|
||||||
if let Some(gas) = json["OBIS"]["Gas_total"].as_number().map(f32::from) {
|
|
||||||
self.gas_total = Some(gas);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(version) = json["StatusFWR"]["Version"].as_str() {
|
|
||||||
self.firmware = version.into();
|
|
||||||
if let Some(version) = version
|
|
||||||
.rfind('.')
|
|
||||||
.map(|index| &version[0..index])
|
|
||||||
.and_then(|s| s.parse().ok())
|
|
||||||
{
|
|
||||||
self.version = version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if json["PMS5003"].is_object() {
|
|
||||||
let pms = self.pms_state.get_or_insert(PMSState::default());
|
|
||||||
pms.update(&json["PMS5003"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format_device_state<W: Write>(
|
|
||||||
mut writer: W,
|
|
||||||
device: &Device,
|
|
||||||
state: &DeviceState,
|
|
||||||
) -> fmt::Result {
|
|
||||||
if state.name.is_empty() {
|
|
||||||
println!("{} has no name set, skipping", device.hostname);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"tasmota_online{{tasmota_id=\"{}\", name=\"{}\"}} 1",
|
|
||||||
device.hostname, state.name
|
|
||||||
)?;
|
|
||||||
if let Some(switch_state) = state.state {
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"switch_state{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname,
|
|
||||||
state.name,
|
|
||||||
if switch_state { 1 } else { 0 }
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(power_watts) = state.power_watts {
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"power_watts{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, state.name, power_watts
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(power_yesterday) = state.power_yesterday {
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"power_yesterday_kwh{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, state.name, power_yesterday
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(power_today) = state.power_today {
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"power_today_kwh{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, state.name, power_today
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(power_total) = state.power_total {
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"power_total_kwh{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, state.name, power_total
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(power_total) = state.power_total_high {
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"power_total_high_kwh{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, state.name, power_total
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(power_total) = state.power_total_low {
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"power_total_low_kwh{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, state.name, power_total
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(gas_total) = state.gas_total {
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"gas_total_m3{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, state.name, gas_total
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(co2) = state.co2 {
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"sensor_co2{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, state.name, co2
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(pms) = state.pms_state.as_ref() {
|
|
||||||
format_pms_state(&mut writer, device, state, pms)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !state.firmware.is_empty() {
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
r#"tasmota_version{{tasmota_id="{}", name="{}", firmware="{}", version="{}"}} 1"#,
|
|
||||||
device.hostname, state.name, state.firmware, state.version
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)]
|
||||||
|
|
@ -450,169 +254,3 @@ impl Debug for BDAddr {
|
||||||
(self as &dyn Display).fmt(f)
|
(self as &dyn Display).fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//"PMS5003":{"CF1":6,"CF2.5":8,"CF10":8,"PM1":6,"PM2.5":8,"PM10":8,"PB0.3":0,"PB0.5":0,"PB1":0,"PB2.5":0,"PB5":0,"PB10":0}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct PMSState {
|
|
||||||
cf1: u16,
|
|
||||||
cf2_5: u16,
|
|
||||||
cf10: u16,
|
|
||||||
pm1: u16,
|
|
||||||
pm2_5: u16,
|
|
||||||
pm10: u16,
|
|
||||||
pb0_3: u16,
|
|
||||||
pb0_5: u16,
|
|
||||||
pb1: u16,
|
|
||||||
pb2_5: u16,
|
|
||||||
pb5: u16,
|
|
||||||
pb10: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PMSState {
|
|
||||||
pub fn update(&mut self, json: &JsonValue) {
|
|
||||||
if let Some(val) = json["CF1"]
|
|
||||||
.as_number()
|
|
||||||
.and_then(|num| u16::try_from(num).ok())
|
|
||||||
{
|
|
||||||
self.cf1 = val;
|
|
||||||
}
|
|
||||||
if let Some(val) = json["CF2.5"]
|
|
||||||
.as_number()
|
|
||||||
.and_then(|num| u16::try_from(num).ok())
|
|
||||||
{
|
|
||||||
self.cf2_5 = val;
|
|
||||||
}
|
|
||||||
if let Some(val) = json["CF10"]
|
|
||||||
.as_number()
|
|
||||||
.and_then(|num| u16::try_from(num).ok())
|
|
||||||
{
|
|
||||||
self.cf10 = val;
|
|
||||||
}
|
|
||||||
if let Some(val) = json["PM1"]
|
|
||||||
.as_number()
|
|
||||||
.and_then(|num| u16::try_from(num).ok())
|
|
||||||
{
|
|
||||||
self.pm1 = val;
|
|
||||||
}
|
|
||||||
if let Some(val) = json["PM2.5"]
|
|
||||||
.as_number()
|
|
||||||
.and_then(|num| u16::try_from(num).ok())
|
|
||||||
{
|
|
||||||
self.pm2_5 = val;
|
|
||||||
}
|
|
||||||
if let Some(val) = json["PM10"]
|
|
||||||
.as_number()
|
|
||||||
.and_then(|num| u16::try_from(num).ok())
|
|
||||||
{
|
|
||||||
self.pm10 = val;
|
|
||||||
}
|
|
||||||
if let Some(val) = json["PB0.3"]
|
|
||||||
.as_number()
|
|
||||||
.and_then(|num| u16::try_from(num).ok())
|
|
||||||
{
|
|
||||||
self.pb0_3 = val;
|
|
||||||
}
|
|
||||||
if let Some(val) = json["PB0.5"]
|
|
||||||
.as_number()
|
|
||||||
.and_then(|num| u16::try_from(num).ok())
|
|
||||||
{
|
|
||||||
self.pb0_5 = val;
|
|
||||||
}
|
|
||||||
if let Some(val) = json["PB1"]
|
|
||||||
.as_number()
|
|
||||||
.and_then(|num| u16::try_from(num).ok())
|
|
||||||
{
|
|
||||||
self.pb1 = val;
|
|
||||||
}
|
|
||||||
if let Some(val) = json["PB2.5"]
|
|
||||||
.as_number()
|
|
||||||
.and_then(|num| u16::try_from(num).ok())
|
|
||||||
{
|
|
||||||
self.pb2_5 = val;
|
|
||||||
}
|
|
||||||
if let Some(val) = json["PB5"]
|
|
||||||
.as_number()
|
|
||||||
.and_then(|num| u16::try_from(num).ok())
|
|
||||||
{
|
|
||||||
self.pb5 = val;
|
|
||||||
}
|
|
||||||
if let Some(val) = json["PB10"]
|
|
||||||
.as_number()
|
|
||||||
.and_then(|num| u16::try_from(num).ok())
|
|
||||||
{
|
|
||||||
self.pb10 = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format_pms_state<W: Write>(
|
|
||||||
mut writer: W,
|
|
||||||
device: &Device,
|
|
||||||
device_state: &DeviceState,
|
|
||||||
state: &PMSState,
|
|
||||||
) -> fmt::Result {
|
|
||||||
let name = &device_state.name;
|
|
||||||
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"cf1{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, name, state.cf1
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"cf2_5{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, name, state.cf2_5
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"cf10{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, name, state.cf10
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"pm1{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, name, state.pm1
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"pm2_5{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, name, state.pm2_5
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"pm10{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, name, state.pm10
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"pb0_3{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, name, state.pb0_3
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"pb0_5{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, name, state.pb0_5
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"pb1{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, name, state.pb1
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"pb2_5{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, name, state.pb2_5
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"pb5{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, name, state.pb5
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"pb10{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
|
||||||
device.hostname, name, state.pb10
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
||||||
386
src/device/tasmota.rs
Normal file
386
src/device/tasmota.rs
Normal file
|
|
@ -0,0 +1,386 @@
|
||||||
|
use std::{
|
||||||
|
fmt::{self, Display},
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
|
||||||
|
use jzon::JsonValue;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DeviceState {
|
||||||
|
pub state: Option<bool>,
|
||||||
|
pub name: String,
|
||||||
|
pub hostname: String,
|
||||||
|
pub power_watts: Option<f32>,
|
||||||
|
pub power_yesterday: Option<f32>,
|
||||||
|
pub power_today: Option<f32>,
|
||||||
|
pub power_total: Option<f32>,
|
||||||
|
pub power_total_low: Option<f32>,
|
||||||
|
pub power_total_high: Option<f32>,
|
||||||
|
pub gas_total: Option<f32>,
|
||||||
|
pub co2: Option<f32>,
|
||||||
|
pub pms_state: Option<PMSState>,
|
||||||
|
pub last_seen: Instant,
|
||||||
|
pub firmware: String,
|
||||||
|
pub version: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceState {
|
||||||
|
pub fn new(hostname: String) -> Self {
|
||||||
|
DeviceState {
|
||||||
|
hostname,
|
||||||
|
state: Default::default(),
|
||||||
|
name: Default::default(),
|
||||||
|
power_watts: Default::default(),
|
||||||
|
power_yesterday: Default::default(),
|
||||||
|
power_today: Default::default(),
|
||||||
|
power_total: Default::default(),
|
||||||
|
power_total_low: Default::default(),
|
||||||
|
power_total_high: Default::default(),
|
||||||
|
gas_total: Default::default(),
|
||||||
|
co2: Default::default(),
|
||||||
|
pms_state: Default::default(),
|
||||||
|
last_seen: Instant::now(),
|
||||||
|
firmware: Default::default(),
|
||||||
|
version: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, json: JsonValue) {
|
||||||
|
self.last_seen = Instant::now();
|
||||||
|
|
||||||
|
if json["DeviceName"].is_string() && !json["DeviceName"].is_empty() {
|
||||||
|
self.name = json["DeviceName"].to_string();
|
||||||
|
}
|
||||||
|
if json["POWER"].is_string() && !json["POWER"].is_empty() {
|
||||||
|
self.state = Some(json["POWER"] == "ON");
|
||||||
|
}
|
||||||
|
if let Some(power) = json["ENERGY"]["Power"].as_number().map(f32::from) {
|
||||||
|
self.power_watts = Some(power);
|
||||||
|
}
|
||||||
|
if let Some(yesterday) = json["ENERGY"]["Yesterday"].as_number().map(f32::from) {
|
||||||
|
self.power_yesterday = Some(yesterday);
|
||||||
|
}
|
||||||
|
if let Some(today) = json["ENERGY"]["Today"].as_number().map(f32::from) {
|
||||||
|
self.power_today = Some(today);
|
||||||
|
}
|
||||||
|
if let Some(co2) = json["MHZ19B"]["CarbonDioxide"].as_number().map(f32::from) {
|
||||||
|
if co2 > 1.0 {
|
||||||
|
self.co2 = Some(co2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(power) = json["OBIS"]["Power"].as_number().map(f32::from) {
|
||||||
|
self.power_watts = Some(power);
|
||||||
|
}
|
||||||
|
if let Some(total) = json["OBIS"]["Total"].as_number().map(f32::from) {
|
||||||
|
self.power_total = Some(total);
|
||||||
|
}
|
||||||
|
if let Some(total) = json["OBIS"]["Total_high"].as_number().map(f32::from) {
|
||||||
|
self.power_total_high = Some(total);
|
||||||
|
}
|
||||||
|
if let Some(total) = json["OBIS"]["Total_low"].as_number().map(f32::from) {
|
||||||
|
self.power_total_low = Some(total);
|
||||||
|
}
|
||||||
|
if let Some(gas) = json["OBIS"]["Gas_total"].as_number().map(f32::from) {
|
||||||
|
self.gas_total = Some(gas);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(version) = json["StatusFWR"]["Version"].as_str() {
|
||||||
|
self.firmware = version.into();
|
||||||
|
if let Some(version) = version
|
||||||
|
.rfind('.')
|
||||||
|
.map(|index| &version[0..index])
|
||||||
|
.and_then(|s| s.parse().ok())
|
||||||
|
{
|
||||||
|
self.version = version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if json["PMS5003"].is_object() {
|
||||||
|
let pms = self.pms_state.get_or_insert(PMSState::default());
|
||||||
|
pms.update(&json["PMS5003"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_open_metrics(&self) -> impl Display + '_ {
|
||||||
|
TasmotaFormatter { device: self }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TasmotaFormatter<'a> {
|
||||||
|
device: &'a DeviceState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TasmotaFormatter<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if self.device.name.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"tasmota_online{{tasmota_id=\"{}\", name=\"{}\"}} 1",
|
||||||
|
self.device.hostname, self.device.name
|
||||||
|
)?;
|
||||||
|
if let Some(switch_state) = self.device.state {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"switch_state{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname,
|
||||||
|
self.device.name,
|
||||||
|
if switch_state { 1 } else { 0 }
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(power_watts) = self.device.power_watts {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"power_watts{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, self.device.name, power_watts
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(power_yesterday) = self.device.power_yesterday {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"power_yesterday_kwh{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, self.device.name, power_yesterday
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(power_today) = self.device.power_today {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"power_today_kwh{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, self.device.name, power_today
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(power_total) = self.device.power_total {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"power_total_kwh{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, self.device.name, power_total
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(power_total) = self.device.power_total_high {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"power_total_high_kwh{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, self.device.name, power_total
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(power_total) = self.device.power_total_low {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"power_total_low_kwh{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, self.device.name, power_total
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(gas_total) = self.device.gas_total {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"gas_total_m3{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, self.device.name, gas_total
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(co2) = self.device.co2 {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"sensor_co2{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, self.device.name, co2
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pms) = self.device.pms_state.as_ref() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
PmsFormatter {
|
||||||
|
device: self.device,
|
||||||
|
pms
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.device.firmware.is_empty() {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
r#"tasmota_version{{tasmota_id="{}", name="{}", firmware="{}", version="{}"}} 1"#,
|
||||||
|
self.device.hostname, self.device.name, self.device.firmware, self.device.version
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//"PMS5003":{"CF1":6,"CF2.5":8,"CF10":8,"PM1":6,"PM2.5":8,"PM10":8,"PB0.3":0,"PB0.5":0,"PB1":0,"PB2.5":0,"PB5":0,"PB10":0}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct PMSState {
|
||||||
|
cf1: u16,
|
||||||
|
cf2_5: u16,
|
||||||
|
cf10: u16,
|
||||||
|
pm1: u16,
|
||||||
|
pm2_5: u16,
|
||||||
|
pm10: u16,
|
||||||
|
pb0_3: u16,
|
||||||
|
pb0_5: u16,
|
||||||
|
pb1: u16,
|
||||||
|
pb2_5: u16,
|
||||||
|
pb5: u16,
|
||||||
|
pb10: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PMSState {
|
||||||
|
pub fn update(&mut self, json: &JsonValue) {
|
||||||
|
if let Some(val) = json["CF1"]
|
||||||
|
.as_number()
|
||||||
|
.and_then(|num| u16::try_from(num).ok())
|
||||||
|
{
|
||||||
|
self.cf1 = val;
|
||||||
|
}
|
||||||
|
if let Some(val) = json["CF2.5"]
|
||||||
|
.as_number()
|
||||||
|
.and_then(|num| u16::try_from(num).ok())
|
||||||
|
{
|
||||||
|
self.cf2_5 = val;
|
||||||
|
}
|
||||||
|
if let Some(val) = json["CF10"]
|
||||||
|
.as_number()
|
||||||
|
.and_then(|num| u16::try_from(num).ok())
|
||||||
|
{
|
||||||
|
self.cf10 = val;
|
||||||
|
}
|
||||||
|
if let Some(val) = json["PM1"]
|
||||||
|
.as_number()
|
||||||
|
.and_then(|num| u16::try_from(num).ok())
|
||||||
|
{
|
||||||
|
self.pm1 = val;
|
||||||
|
}
|
||||||
|
if let Some(val) = json["PM2.5"]
|
||||||
|
.as_number()
|
||||||
|
.and_then(|num| u16::try_from(num).ok())
|
||||||
|
{
|
||||||
|
self.pm2_5 = val;
|
||||||
|
}
|
||||||
|
if let Some(val) = json["PM10"]
|
||||||
|
.as_number()
|
||||||
|
.and_then(|num| u16::try_from(num).ok())
|
||||||
|
{
|
||||||
|
self.pm10 = val;
|
||||||
|
}
|
||||||
|
if let Some(val) = json["PB0.3"]
|
||||||
|
.as_number()
|
||||||
|
.and_then(|num| u16::try_from(num).ok())
|
||||||
|
{
|
||||||
|
self.pb0_3 = val;
|
||||||
|
}
|
||||||
|
if let Some(val) = json["PB0.5"]
|
||||||
|
.as_number()
|
||||||
|
.and_then(|num| u16::try_from(num).ok())
|
||||||
|
{
|
||||||
|
self.pb0_5 = val;
|
||||||
|
}
|
||||||
|
if let Some(val) = json["PB1"]
|
||||||
|
.as_number()
|
||||||
|
.and_then(|num| u16::try_from(num).ok())
|
||||||
|
{
|
||||||
|
self.pb1 = val;
|
||||||
|
}
|
||||||
|
if let Some(val) = json["PB2.5"]
|
||||||
|
.as_number()
|
||||||
|
.and_then(|num| u16::try_from(num).ok())
|
||||||
|
{
|
||||||
|
self.pb2_5 = val;
|
||||||
|
}
|
||||||
|
if let Some(val) = json["PB5"]
|
||||||
|
.as_number()
|
||||||
|
.and_then(|num| u16::try_from(num).ok())
|
||||||
|
{
|
||||||
|
self.pb5 = val;
|
||||||
|
}
|
||||||
|
if let Some(val) = json["PB10"]
|
||||||
|
.as_number()
|
||||||
|
.and_then(|num| u16::try_from(num).ok())
|
||||||
|
{
|
||||||
|
self.pb10 = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PmsFormatter<'a> {
|
||||||
|
pms: &'a PMSState,
|
||||||
|
device: &'a DeviceState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for PmsFormatter<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let name = &self.device.name;
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"cf1{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, name, self.pms.cf1
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"cf2_5{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, name, self.pms.cf2_5
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"cf10{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, name, self.pms.cf10
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"pm1{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, name, self.pms.pm1
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"pm2_5{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, name, self.pms.pm2_5
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"pm10{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, name, self.pms.pm10
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"pb0_3{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, name, self.pms.pb0_3
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"pb0_5{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, name, self.pms.pb0_5
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"pb1{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, name, self.pms.pb1
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"pb2_5{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, name, self.pms.pb2_5
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"pb5{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, name, self.pms.pb5
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"pb10{{tasmota_id=\"{}\", name=\"{}\"}} {}",
|
||||||
|
self.device.hostname, name, self.pms.pb10
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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};
|
use crate::device::{Device, DeviceStates};
|
||||||
use crate::mqtt::mqtt_stream;
|
use crate::mqtt::mqtt_stream;
|
||||||
use crate::topic::Topic;
|
use crate::topic::Topic;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
@ -78,8 +78,9 @@ async fn serve(device_states: Arc<Mutex<DeviceStates>>, listen: ListenConfig) {
|
||||||
.map(move |state: Arc<Mutex<DeviceStates>>| {
|
.map(move |state: Arc<Mutex<DeviceStates>>| {
|
||||||
let state = state.lock().unwrap();
|
let state = state.lock().unwrap();
|
||||||
let mut response = String::new();
|
let mut response = String::new();
|
||||||
for (device, state) in state.devices() {
|
for (_, state) in state.devices() {
|
||||||
format_device_state(&mut response, device, state).unwrap();
|
write!(&mut response, "{}", state.format_open_metrics())
|
||||||
|
.expect("formatting failed");
|
||||||
}
|
}
|
||||||
for (_, state) in state.dsmr_devices() {
|
for (_, state) in state.dsmr_devices() {
|
||||||
write!(&mut response, "{}", state.format_open_metrics())
|
write!(&mut response, "{}", state.format_open_metrics())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue