gauge revamp... fingers crossed
All checks were successful
Build .deb on release / Build-Deb (push) Successful in 1m52s

This commit is contained in:
Alex Janka 2024-01-28 10:24:00 +11:00
parent 9d9755515d
commit 65e86c2359
11 changed files with 422 additions and 838 deletions

122
Cargo.lock generated
View file

@ -17,18 +17,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
dependencies = [
"cfg-if 1.0.0",
"once_cell",
"version_check",
"zerocopy",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.6.10" version = "0.6.10"
@ -119,12 +107,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "arc-swap"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
[[package]] [[package]]
name = "async-stream" name = "async-stream"
version = "0.3.5" version = "0.3.5"
@ -494,17 +476,6 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "crossbeam-epoch"
version = "0.9.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"crossbeam-utils",
]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.18" version = "0.8.18"
@ -878,15 +849,6 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "hashbrown"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038"
dependencies = [
"ahash",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.3" version = "0.14.3"
@ -1091,7 +1053,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.14.3", "hashbrown",
"serde", "serde",
] ]
@ -1359,45 +1321,6 @@ version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "metrics"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77b9e10a211c839210fd7f99954bda26e5f8e26ec686ad68da6a32df7c80e782"
dependencies = [
"ahash",
"portable-atomic",
]
[[package]]
name = "metrics-prometheus"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3e1316f9ef05b91f4d0e0a0da5b620ba919d336280b83b36be096b86c030fdd"
dependencies = [
"arc-swap",
"metrics",
"metrics-util",
"once_cell",
"prometheus",
"sealed",
"smallvec",
"thiserror",
]
[[package]]
name = "metrics-util"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2670b8badcc285d486261e2e9f1615b506baff91427b61bd336a472b65bbf5ed"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
"hashbrown 0.13.1",
"metrics",
"num_cpus",
]
[[package]] [[package]]
name = "miette" name = "miette"
version = "5.10.0" version = "5.10.0"
@ -1731,12 +1654,6 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
[[package]]
name = "portable-atomic"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
[[package]] [[package]]
name = "powerfmt" name = "powerfmt"
version = "0.2.0" version = "0.2.0"
@ -2280,18 +2197,6 @@ dependencies = [
"untrusted", "untrusted",
] ]
[[package]]
name = "sealed"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a8caec23b7800fb97971a1c6ae365b6239aaeddfb934d6265f8505e795699d"
dependencies = [
"heck",
"proc-macro2",
"quote 1.0.33",
"syn 2.0.43",
]
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.9.2" version = "2.9.2"
@ -2633,17 +2538,16 @@ dependencies = [
[[package]] [[package]]
name = "tesla-charge-controller" name = "tesla-charge-controller"
version = "1.0.31" version = "1.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap 4.4.11", "clap 4.4.11",
"env_logger 0.10.1", "env_logger 0.10.1",
"if_chain", "if_chain",
"include_dir", "include_dir",
"lazy_static 1.4.0",
"libmodbus-rs", "libmodbus-rs",
"log 0.4.20", "log 0.4.20",
"metrics",
"metrics-prometheus",
"notify-debouncer-mini", "notify-debouncer-mini",
"prometheus", "prometheus",
"rocket", "rocket",
@ -3463,26 +3367,6 @@ dependencies = [
"is-terminal", "is-terminal",
] ]
[[package]]
name = "zerocopy"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote 1.0.33",
"syn 2.0.43",
]
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.7.0" version = "1.7.0"

View file

@ -1,6 +1,6 @@
[package] [package]
name = "tesla-charge-controller" name = "tesla-charge-controller"
version = "1.0.31" version = "1.1.0"
edition = "2021" edition = "2021"
license = "MITNFA" license = "MITNFA"
description = "Controls Tesla charge rate based on solar charge data" description = "Controls Tesla charge rate based on solar charge data"
@ -23,8 +23,6 @@ thiserror = "1.0"
rocket = { version = "0.5", features = ["json"] } rocket = { version = "0.5", features = ["json"] }
include_dir = "0.7" include_dir = "0.7"
chrono = "0.4" chrono = "0.4"
metrics = "0.22"
metrics-prometheus = "0.6.0"
prometheus = "0.13" prometheus = "0.13"
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
@ -32,3 +30,4 @@ serialport = "4.3"
libmodbus-rs = "0.8.3" libmodbus-rs = "0.8.3"
if_chain = "1.0.2" if_chain = "1.0.2"
notify-debouncer-mini = { version = "0.4.1", default-features = false } notify-debouncer-mini = { version = "0.4.1", default-features = false }
lazy_static = "1.4"

View file

@ -1,4 +1,8 @@
use metrics::{describe_gauge, gauge, Gauge, Label, Unit}; use lazy_static::lazy_static;
use prometheus::{
core::{AtomicI64, GenericGauge},
register_gauge, register_int_gauge, register_int_gauge_vec, Gauge, IntGauge, IntGaugeVec,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
path::PathBuf, path::PathBuf,
@ -20,153 +24,82 @@ use crate::{
types::{CarState, FromDriveState}, types::{CarState, FromDriveState},
}; };
struct Metrics { lazy_static! {
battery_level: Gauge, pub static ref BATTERY_LEVEL: IntGauge =
charge_rate: Gauge, register_int_gauge!("tesla_battery_level", "Battery level",).unwrap();
charge_request: Gauge, pub static ref CHARGE_RATE: Gauge =
charge_enable_request: Gauge, register_gauge!("tesla_charge_rate", "Charge rate",).unwrap();
charger_connected: Gauge, pub static ref CHARGE_REQUEST: IntGauge =
inside_temp: Gauge, register_int_gauge!("tesla_charge_request", "Requested charge rate",).unwrap();
outside_temp: Gauge, pub static ref CHARGE_ENABLE_REQUEST: IntGauge =
battery_heater: Gauge, register_int_gauge!("tesla_charge_enable_request", "Charge enable request",).unwrap();
climate_on: Gauge, pub static ref CHARGER_CONNECTED: IntGauge =
preconditioning: Gauge, register_int_gauge!("tesla_charger_connected", "Charger connected",).unwrap();
remote_heater_control_enabled: Gauge, pub static ref INSIDE_TEMP: Gauge =
is_auto_conditioning_on: Gauge, register_gauge!("tesla_inside_temp", "Inside temperature",).unwrap();
driver_temp_setting: Gauge, pub static ref OUTSIDE_TEMP: Gauge =
passenger_temp_setting: Gauge, register_gauge!("tesla_outside_temp", "Outside temperature",).unwrap();
tesla_online: Gauge, pub static ref BATTERY_HEATER: IntGauge =
charging_state: ChargingStateGauges, register_int_gauge!("tesla_battery_heater", "Battery heater",).unwrap();
cabin_overheat_protection: CabinOverheatProtectionGauges, pub static ref CLIMATE_ON: IntGauge =
hvac_auto: HvacAutoRequestGauges, register_int_gauge!("tesla_climate_on", "Climate control",).unwrap();
home: Gauge, pub static ref PRECONDITIONING: IntGauge =
sentry_mode: Gauge, register_int_gauge!("tesla_preconditioning", "Preconditioning",).unwrap();
sentry_mode_available: Gauge, pub static ref REMOTE_HEATER_CONTROL_ENABLED: IntGauge = register_int_gauge!(
charger_actual_current: Gauge, "tesla_remote_heater_control_enabled",
charger_phases: Gauge, "Remote heater control enabled",
charger_pilot_current: Gauge, )
charger_power: Gauge, .unwrap();
charger_voltage: Gauge, pub static ref IS_AUTO_CONDITIONING_ON: IntGauge =
} register_int_gauge!("tesla_is_auto_conditioning_on", "Auto conditioning on",).unwrap();
pub static ref DRIVER_TEMP_SETTING: Gauge =
impl Metrics { register_gauge!("tesla_driver_temp_setting", "Driver temp",).unwrap();
fn new() -> Self { pub static ref PASSENGER_TEMP_SETTING: Gauge =
describe_gauge!("tesla_battery_level", Unit::Percent, "Battery level"); register_gauge!("tesla_passenger_temp_setting", "Passenger temp",).unwrap();
let battery_level = gauge!("tesla_battery_level"); pub static ref TESLA_ONLINE: IntGauge =
describe_gauge!("tesla_charge_rate", "Charge rate"); register_int_gauge!("tesla_online", "Tesla online",).unwrap();
let charge_rate = gauge!("tesla_charge_rate"); pub static ref HOME: IntGauge = register_int_gauge!("tesla_home", "Is home",).unwrap();
describe_gauge!("tesla_charge_request", "Requested charge rate"); pub static ref SENTRY_MODE: IntGauge =
let charge_request = gauge!("tesla_charge_request"); register_int_gauge!("tesla_sentry_mode", "Sentry mode",).unwrap();
describe_gauge!("tesla_charge_enable_request", "Charge enable request"); pub static ref SENTRY_MODE_AVAILABLE: IntGauge =
let charge_enable_request = gauge!("tesla_charge_enable_request"); register_int_gauge!("tesla_sentry_mode_available", "Sentry mode available",).unwrap();
describe_gauge!("tesla_charger_connected", "Charger connected"); pub static ref CHARGER_ACTUAL_CURRENT: IntGauge =
let charger_connected = gauge!("tesla_charger_connected"); register_int_gauge!("tesla_charger_actual_current", "Charger actual current",).unwrap();
describe_gauge!("tesla_inside_temp", "Inside temperature"); pub static ref CHARGER_PHASES: IntGauge =
let inside_temp = gauge!("tesla_inside_temp"); register_int_gauge!("tesla_charger_phases", "Charger phases",).unwrap();
describe_gauge!("tesla_outside_temp", "Outside temperature"); pub static ref CHARGER_PILOT_CURRENT: IntGauge =
let outside_temp = gauge!("tesla_outside_temp"); register_int_gauge!("tesla_charger_pilot_current", "Charger pilot current",).unwrap();
describe_gauge!("tesla_battery_heater", "Battery heater"); pub static ref CHARGER_POWER: IntGauge =
let battery_heater = gauge!("tesla_battery_heater"); register_int_gauge!("tesla_charger_power", "Charger power",).unwrap();
describe_gauge!("tesla_climate_on", "Climate control"); pub static ref CHARGER_VOLTAGE: IntGauge =
let climate_on = gauge!("tesla_climate_on"); register_int_gauge!("tesla_charger_voltage", "Charger voltage",).unwrap();
describe_gauge!("tesla_preconditioning", "Preconditioning"); pub static ref CHARGING_STATE: IntGaugeVec =
let preconditioning = gauge!("tesla_preconditioning"); register_int_gauge_vec!("tesla_charging_state", "Tesla charging state", &["state"])
describe_gauge!( .unwrap();
"tesla_remote_heater_control_enabled", pub static ref CABIN_OVERHEAT_PROTECTION: IntGaugeVec = register_int_gauge_vec!(
"Remote heater control enabled" "tesla_cabin_overheat_protection_state",
); "Cabin overheat protection state",
let remote_heater_control_enabled = gauge!("tesla_remote_heater_control_enabled"); &["state"]
describe_gauge!("tesla_is_auto_conditioning_on", "Auto conditioning on"); )
let is_auto_conditioning_on = gauge!("tesla_is_auto_conditioning_on"); .unwrap();
describe_gauge!("tesla_driver_temp_setting", "Driver temp"); pub static ref HVAC_AUTO: IntGaugeVec =
let driver_temp_setting = gauge!("tesla_driver_temp_setting"); register_int_gauge_vec!("tesla_hvac_auto_request", "HVAC auto", &["state"]).unwrap();
describe_gauge!("tesla_passenger_temp_setting", "Passenger temp");
let passenger_temp_setting = gauge!("tesla_passenger_temp_setting");
describe_gauge!("tesla_online", "Tesla online");
let tesla_online = gauge!("tesla_online");
describe_gauge!("tesla_charging_state", "Tesla charging state");
let charging_state = ChargingStateGauges::new();
describe_gauge!(
"tesla_cabin_overheat_protection_state",
"Cabin overheat protection state"
);
let cabin_overheat_protection = CabinOverheatProtectionGauges::new();
describe_gauge!("tesla_hvac_auto_request", "HVAC auto");
let hvac_auto = HvacAutoRequestGauges::new();
describe_gauge!("tesla_home", "Is home");
let home = gauge!("tesla_home");
describe_gauge!("tesla_sentry_mode", "Sentry mode");
let sentry_mode = gauge!("tesla_sentry_mode");
describe_gauge!("tesla_sentry_mode_available", "Sentry mode available");
let sentry_mode_available = gauge!("tesla_sentry_mode_available");
describe_gauge!("tesla_charger_actual_current", "Charger actual current");
let charger_actual_current = gauge!("tesla_charger_actual_current");
describe_gauge!("tesla_charger_phases", "Charger phases");
let charger_phases = gauge!("tesla_charger_phases");
describe_gauge!("tesla_charger_pilot_current", "Charger pilot current");
let charger_pilot_current = gauge!("tesla_charger_pilot_current");
describe_gauge!("tesla_charger_power", "Charger power");
let charger_power = gauge!("tesla_charger_power");
describe_gauge!("tesla_charger_voltage", "Charger voltage");
let charger_voltage = gauge!("tesla_charger_voltage");
Self {
battery_level,
charge_rate,
charge_request,
charge_enable_request,
charger_connected,
inside_temp,
outside_temp,
battery_heater,
climate_on,
preconditioning,
remote_heater_control_enabled,
is_auto_conditioning_on,
driver_temp_setting,
passenger_temp_setting,
tesla_online,
charging_state,
cabin_overheat_protection,
hvac_auto,
home,
sentry_mode,
sentry_mode_available,
charger_actual_current,
charger_phases,
charger_pilot_current,
charger_power,
charger_voltage,
}
}
} }
struct ChargingStateGauges { struct ChargingStateGauges {
charging: Gauge, charging: GenericGauge<AtomicI64>,
stopped: Gauge, stopped: GenericGauge<AtomicI64>,
disconnected: Gauge, disconnected: GenericGauge<AtomicI64>,
other: Gauge, other: GenericGauge<AtomicI64>,
} }
impl ChargingStateGauges { impl ChargingStateGauges {
fn new() -> Self { fn new() -> Self {
let charging = gauge!( let charging = CHARGING_STATE.with_label_values(&["charging"]);
"tesla_charging_state", let stopped = CHARGING_STATE.with_label_values(&["stopped"]);
vec![Label::new("state", String::from("charging"))] let disconnected = CHARGING_STATE.with_label_values(&["disconnected"]);
); let other = CHARGING_STATE.with_label_values(&["other"]);
let stopped = gauge!(
"tesla_charging_state",
vec![Label::new("state", String::from("stopped"))]
);
let disconnected = gauge!(
"tesla_charging_state",
vec![Label::new("state", String::from("disconnected"))]
);
let other = gauge!(
"tesla_charging_state",
vec![Label::new("state", String::from("other"))]
);
Self { Self {
charging, charging,
stopped, stopped,
@ -178,58 +111,47 @@ impl ChargingStateGauges {
fn set(&mut self, state: ChargingState) { fn set(&mut self, state: ChargingState) {
match state { match state {
ChargingState::Charging => { ChargingState::Charging => {
self.charging.set(1.); self.charging.set(1);
self.stopped.set(0.); self.stopped.set(0);
self.disconnected.set(0.); self.disconnected.set(0);
self.other.set(0.); self.other.set(0);
} }
ChargingState::Stopped => { ChargingState::Stopped => {
self.charging.set(0.); self.charging.set(0);
self.stopped.set(1.); self.stopped.set(1);
self.disconnected.set(0.); self.disconnected.set(0);
self.other.set(0.); self.other.set(0);
} }
ChargingState::Disconnected => { ChargingState::Disconnected => {
self.charging.set(0.); self.charging.set(0);
self.stopped.set(0.); self.stopped.set(0);
self.disconnected.set(1.); self.disconnected.set(1);
self.other.set(0.); self.other.set(0);
} }
ChargingState::Other => { ChargingState::Other => {
self.charging.set(0.); self.charging.set(0);
self.stopped.set(0.); self.stopped.set(0);
self.disconnected.set(0.); self.disconnected.set(0);
self.other.set(1.); self.other.set(1);
} }
} }
} }
} }
struct CabinOverheatProtectionGauges { struct CabinOverheatProtectionGauges {
off: Gauge, off: GenericGauge<AtomicI64>,
on: Gauge, on: GenericGauge<AtomicI64>,
fan_only: Gauge, fan_only: GenericGauge<AtomicI64>,
unknown: Gauge, unknown: GenericGauge<AtomicI64>,
} }
impl CabinOverheatProtectionGauges { impl CabinOverheatProtectionGauges {
fn new() -> Self { fn new() -> Self {
let off = gauge!( let off = CABIN_OVERHEAT_PROTECTION.with_label_values(&["off"]);
"tesla_cabin_overheat_protection_state", let on = CABIN_OVERHEAT_PROTECTION.with_label_values(&["on"]);
vec![Label::new("state", String::from("off"))] let fan_only = CABIN_OVERHEAT_PROTECTION.with_label_values(&["fan_only"]);
); let unknown = CABIN_OVERHEAT_PROTECTION.with_label_values(&["unknown"]);
let on = gauge!(
"tesla_cabin_overheat_protection_state",
vec![Label::new("state", String::from("on"))]
);
let fan_only = gauge!(
"tesla_cabin_overheat_protection_state",
vec![Label::new("state", String::from("fan_only"))]
);
let unknown = gauge!(
"tesla_cabin_overheat_protection_state",
vec![Label::new("state", String::from("unknown"))]
);
Self { Self {
off, off,
on, on,
@ -241,53 +163,45 @@ impl CabinOverheatProtectionGauges {
fn set(&mut self, state: CabinOverheatProtection) { fn set(&mut self, state: CabinOverheatProtection) {
match state { match state {
CabinOverheatProtection::Off => { CabinOverheatProtection::Off => {
self.off.set(1.); self.off.set(1);
self.on.set(0.); self.on.set(0);
self.fan_only.set(0.); self.fan_only.set(0);
self.unknown.set(0.); self.unknown.set(0);
} }
CabinOverheatProtection::On => { CabinOverheatProtection::On => {
self.off.set(0.); self.off.set(0);
self.on.set(1.); self.on.set(1);
self.fan_only.set(0.); self.fan_only.set(0);
self.unknown.set(0.); self.unknown.set(0);
} }
CabinOverheatProtection::FanOnly => { CabinOverheatProtection::FanOnly => {
self.off.set(0.); self.off.set(0);
self.on.set(0.); self.on.set(0);
self.fan_only.set(1.); self.fan_only.set(1);
self.unknown.set(0.); self.unknown.set(0);
} }
CabinOverheatProtection::Unknown => { CabinOverheatProtection::Unknown => {
self.off.set(0.); self.off.set(0);
self.on.set(0.); self.on.set(0);
self.fan_only.set(0.); self.fan_only.set(0);
self.unknown.set(1.); self.unknown.set(1);
} }
} }
} }
} }
struct HvacAutoRequestGauges { struct HvacAutoRequestGauges {
override_g: Gauge, override_g: GenericGauge<AtomicI64>,
on: Gauge, on: GenericGauge<AtomicI64>,
unknown: Gauge, unknown: GenericGauge<AtomicI64>,
} }
impl HvacAutoRequestGauges { impl HvacAutoRequestGauges {
fn new() -> Self { fn new() -> Self {
let override_g = gauge!( let override_g = HVAC_AUTO.with_label_values(&["override"]);
"tesla_hvac_auto_request", let on = HVAC_AUTO.with_label_values(&["on"]);
vec![Label::new("state", String::from("override"))] let unknown = HVAC_AUTO.with_label_values(&["unknown"]);
);
let on = gauge!(
"tesla_hvac_auto_request",
vec![Label::new("state", String::from("on"))]
);
let unknown = gauge!(
"tesla_hvac_auto_request",
vec![Label::new("state", String::from("unknown"))]
);
Self { Self {
override_g, override_g,
on, on,
@ -298,19 +212,19 @@ impl HvacAutoRequestGauges {
fn set(&mut self, state: HvacAutoRequest) { fn set(&mut self, state: HvacAutoRequest) {
match state { match state {
HvacAutoRequest::Override => { HvacAutoRequest::Override => {
self.override_g.set(1.); self.override_g.set(1);
self.on.set(0.); self.on.set(0);
self.unknown.set(0.); self.unknown.set(0);
} }
HvacAutoRequest::On => { HvacAutoRequest::On => {
self.override_g.set(0.); self.override_g.set(0);
self.on.set(1.); self.on.set(1);
self.unknown.set(0.); self.unknown.set(0);
} }
HvacAutoRequest::Unknown => { HvacAutoRequest::Unknown => {
self.override_g.set(0.); self.override_g.set(0);
self.on.set(0.); self.on.set(0);
self.unknown.set(1.); self.unknown.set(1);
} }
} }
} }
@ -322,8 +236,10 @@ pub struct TeslaInterface {
vehicle: Box<teslatte::vehicles::VehicleData>, vehicle: Box<teslatte::vehicles::VehicleData>,
last_refresh: Instant, last_refresh: Instant,
auth_path: PathBuf, auth_path: PathBuf,
metrics: Metrics,
monitored_values: MonitoredValues, monitored_values: MonitoredValues,
charging_state: ChargingStateGauges,
cabin_overheat_protection: CabinOverheatProtectionGauges,
hvac_auto: HvacAutoRequestGauges,
} }
#[derive(Default)] #[derive(Default)]
@ -366,16 +282,16 @@ impl TeslaInterface {
.next() .next()
.ok_or(AuthLoadError::NoVehicles)?; .ok_or(AuthLoadError::NoVehicles)?;
let metrics = Metrics::new();
let interface = Self { let interface = Self {
state: Arc::new(RwLock::new(Default::default())), state: Arc::new(RwLock::new(Default::default())),
api, api,
last_refresh, last_refresh,
auth_path, auth_path,
vehicle, vehicle,
metrics,
monitored_values: Default::default(), monitored_values: Default::default(),
charging_state: ChargingStateGauges::new(),
cabin_overheat_protection: CabinOverheatProtectionGauges::new(),
hvac_auto: HvacAutoRequestGauges::new(),
}; };
interface.save_key()?; interface.save_key()?;
@ -423,97 +339,65 @@ impl TeslaInterface {
async fn refresh_state(&mut self) { async fn refresh_state(&mut self) {
match self.get_state().await { match self.get_state().await {
Ok(new_state) => { Ok(new_state) => {
self.metrics.tesla_online.set(1.); TESLA_ONLINE.set(1);
let mut state = self let mut state = self
.state .state
.write() .write()
.expect("Tesla API state handler panicked!!"); .expect("Tesla API state handler panicked!!");
if let Some(new_charge_state) = new_state.charge_state { if let Some(new_charge_state) = new_state.charge_state {
self.metrics BATTERY_LEVEL.set(new_charge_state.battery_level);
.battery_level CHARGE_RATE.set(new_charge_state.charge_rate);
.set(new_charge_state.battery_level as f64); CHARGE_REQUEST.set(new_charge_state.charge_current_request);
self.metrics.charge_rate.set(new_charge_state.charge_rate); CHARGE_ENABLE_REQUEST.set(bi(new_charge_state.charge_enable_request));
self.metrics CHARGER_CONNECTED.set(bi(new_charge_state.charger_connected));
.charge_request self.charging_state.set(new_charge_state.charging_state);
.set(new_charge_state.charge_current_request as f64);
self.metrics
.charge_enable_request
.set(bf(new_charge_state.charge_enable_request));
self.metrics
.charger_connected
.set(bf(new_charge_state.charger_connected));
self.metrics
.charging_state
.set(new_charge_state.charging_state);
if let Some(v) = new_charge_state.charger_actual_current { if let Some(v) = new_charge_state.charger_actual_current {
self.metrics.charger_actual_current.set(v as f64); CHARGER_ACTUAL_CURRENT.set(v);
} }
if let Some(v) = new_charge_state.charger_phases { if let Some(v) = new_charge_state.charger_phases {
self.metrics.charger_phases.set(v as f64); CHARGER_PHASES.set(v);
} }
if let Some(v) = new_charge_state.charger_pilot_current { if let Some(v) = new_charge_state.charger_pilot_current {
self.metrics.charger_pilot_current.set(v as f64); CHARGER_PILOT_CURRENT.set(v);
} }
if let Some(v) = new_charge_state.charger_power { if let Some(v) = new_charge_state.charger_power {
self.metrics.charger_power.set(v as f64); CHARGER_POWER.set(v);
} }
if let Some(v) = new_charge_state.charger_voltage { if let Some(v) = new_charge_state.charger_voltage {
self.metrics.charger_voltage.set(v as f64); CHARGER_VOLTAGE.set(v);
} }
state.charge_state = Some(new_charge_state); state.charge_state = Some(new_charge_state);
} }
if let Some(new_location_data) = new_state.location_data { if let Some(new_location_data) = new_state.location_data {
self.metrics.home.set(bf(new_location_data.home)); HOME.set(bi(new_location_data.home));
state.location_data = Some(new_location_data); state.location_data = Some(new_location_data);
} }
if let Some(new_climate_state) = new_state.climate_state { if let Some(new_climate_state) = new_state.climate_state {
self.metrics.inside_temp.set(new_climate_state.inside_temp); INSIDE_TEMP.set(new_climate_state.inside_temp);
self.metrics OUTSIDE_TEMP.set(new_climate_state.outside_temp);
.outside_temp BATTERY_HEATER.set(bi(new_climate_state.battery_heater));
.set(new_climate_state.outside_temp); CLIMATE_ON.set(bi(new_climate_state.climate_on));
self.metrics PRECONDITIONING.set(bi(new_climate_state.preconditioning));
.battery_heater REMOTE_HEATER_CONTROL_ENABLED
.set(bf(new_climate_state.battery_heater)); .set(bi(new_climate_state.remote_heater_control_enabled));
self.metrics IS_AUTO_CONDITIONING_ON.set(bi(new_climate_state.is_auto_conditioning_on));
.climate_on DRIVER_TEMP_SETTING.set(new_climate_state.driver_temp_setting);
.set(bf(new_climate_state.climate_on)); PASSENGER_TEMP_SETTING.set(new_climate_state.passenger_temp_setting);
self.metrics self.cabin_overheat_protection
.preconditioning
.set(bf(new_climate_state.preconditioning));
self.metrics
.remote_heater_control_enabled
.set(bf(new_climate_state.remote_heater_control_enabled));
self.metrics
.is_auto_conditioning_on
.set(bf(new_climate_state.is_auto_conditioning_on));
self.metrics
.driver_temp_setting
.set(new_climate_state.driver_temp_setting);
self.metrics
.passenger_temp_setting
.set(new_climate_state.passenger_temp_setting);
self.metrics
.cabin_overheat_protection
.set(new_climate_state.cabin_overheat_protection); .set(new_climate_state.cabin_overheat_protection);
self.metrics self.hvac_auto.set(new_climate_state.hvac_auto_request);
.hvac_auto
.set(new_climate_state.hvac_auto_request);
state.climate_state = Some(new_climate_state); state.climate_state = Some(new_climate_state);
} }
if let Some(new_vehicle_state) = new_state.vehicle_state { if let Some(new_vehicle_state) = new_state.vehicle_state {
self.metrics SENTRY_MODE.set(obi(new_vehicle_state.sentry_mode));
.sentry_mode SENTRY_MODE_AVAILABLE.set(obi(new_vehicle_state.sentry_mode_available));
.set(obf(new_vehicle_state.sentry_mode));
self.metrics
.sentry_mode_available
.set(obf(new_vehicle_state.sentry_mode_available));
state.vehicle_state = Some(new_vehicle_state); state.vehicle_state = Some(new_vehicle_state);
} }
} }
Err(e) => { Err(e) => {
self.metrics.tesla_online.set(0.); TESLA_ONLINE.set(0);
match e { match e {
teslatte::error::TeslatteError::DecodeJsonError { teslatte::error::TeslatteError::DecodeJsonError {
source, source,
@ -616,14 +500,14 @@ impl TeslaInterface {
} }
} }
fn bf(value: bool) -> f64 { fn bi(value: bool) -> i64 {
if value { if value {
1.0 1
} else { } else {
0.0 0
} }
} }
fn obf(value: Option<bool>) -> f64 { fn obi(value: Option<bool>) -> i64 {
value.map_or(-1., bf) value.map_or(-1, bi)
} }

View file

@ -1,20 +0,0 @@
pub const CHARGE_CONTROLLER_LABEL: &str = "charge_controller";
pub const PL_LABEL: &str = "pl_device";
pub const TRISTAR_LABEL: &str = "tristar_device";
pub const BATTERY_VOLTAGE: &str = "battery_voltage";
pub const TARGET_VOLTAGE: &str = "target_voltage";
pub const INPUT_CURRENT: &str = "input_current";
pub const CHARGE_STATE: &str = "charge_state";
pub const BATTERY_TEMP: &str = "battery_temp";
pub const PL_DUTY_CYCLE: &str = "pl_duty_cycle";
pub const PL_LOAD_CURRENT: &str = "pl_internal_load_current";
pub const TRISTAR_INPUT_VOLTAGE: &str = "tristar_input_voltage";
pub const TRISTAR_CHARGE_CURRENT: &str = "tristar_charge_current";
pub const TRISTAR_POWER_OUT: &str = "tristar_power_out";
pub const TRISTAR_POWER_IN: &str = "tristar_power_in";
pub const TRISTAR_MAX_ARRAY_POWER: &str = "tristar_max_array_power";
pub const TRISTAR_MAX_ARRAY_VOLTAGE: &str = "tristar_max_array_voltage";
pub const TRISTAR_OPEN_CIRCUIT_VOLTAGE: &str = "tristar_open_circuit_voltage";

View file

@ -0,0 +1,70 @@
use super::{CHARGE_CONTROLLER_LABEL, PL_LABEL, TRISTAR_LABEL};
use lazy_static::lazy_static;
use prometheus::{register_gauge_vec, register_int_gauge_vec, GaugeVec, IntGaugeVec};
lazy_static! {
pub static ref BATTERY_VOLTAGE: GaugeVec = register_gauge_vec!(
"battery_voltage",
"Battery voltage",
&[CHARGE_CONTROLLER_LABEL]
)
.unwrap();
pub static ref TARGET_VOLTAGE: GaugeVec = register_gauge_vec!(
"target_voltage",
"Target voltage",
&[CHARGE_CONTROLLER_LABEL]
)
.unwrap();
pub static ref INPUT_CURRENT: GaugeVec = register_gauge_vec!(
"input_current",
"Internal charge current",
&[CHARGE_CONTROLLER_LABEL]
)
.unwrap();
pub static ref CHARGE_STATE: IntGaugeVec = register_int_gauge_vec!(
"charge_state",
"Regulator state",
&[CHARGE_CONTROLLER_LABEL, "state"]
)
.unwrap();
pub static ref BATTERY_TEMP: GaugeVec = register_gauge_vec!(
"battery_temp",
"Battery temperature",
&[CHARGE_CONTROLLER_LABEL]
)
.unwrap();
pub static ref PL_DUTY_CYCLE: GaugeVec =
register_gauge_vec!("pl_duty_cycle", "Duty cycle", &[PL_LABEL]).unwrap();
pub static ref PL_LOAD_CURRENT: GaugeVec = register_gauge_vec!(
"pl_internal_load_current",
"Internal load current",
&[PL_LABEL]
)
.unwrap();
pub static ref TRISTAR_INPUT_VOLTAGE: GaugeVec =
register_gauge_vec!("tristar_input_voltage", "Input voltage", &[TRISTAR_LABEL]).unwrap();
pub static ref TRISTAR_CHARGE_CURRENT: GaugeVec =
register_gauge_vec!("tristar_charge_current", "Charge current", &[TRISTAR_LABEL]).unwrap();
pub static ref TRISTAR_POWER_OUT: GaugeVec =
register_gauge_vec!("tristar_power_out", "Power out", &[TRISTAR_LABEL]).unwrap();
pub static ref TRISTAR_POWER_IN: GaugeVec =
register_gauge_vec!("tristar_power_in", "Power in", &[TRISTAR_LABEL]).unwrap();
pub static ref TRISTAR_MAX_ARRAY_POWER: GaugeVec = register_gauge_vec!(
"tristar_max_array_power",
"Maximum array power",
&[TRISTAR_LABEL]
)
.unwrap();
pub static ref TRISTAR_MAX_ARRAY_VOLTAGE: GaugeVec = register_gauge_vec!(
"tristar_max_array_voltage",
"Maximum array voltage",
&[TRISTAR_LABEL]
)
.unwrap();
pub static ref TRISTAR_OPEN_CIRCUIT_VOLTAGE: GaugeVec = register_gauge_vec!(
"tristar_open_circuit_voltage",
"Open circuit voltage",
&[TRISTAR_LABEL]
)
.unwrap();
}

View file

@ -1,54 +1,7 @@
use std::sync::OnceLock; pub mod gauges;
use metrics::{describe_gauge, Unit};
mod gauge_names;
pub mod pl; pub mod pl;
pub mod tristar; pub mod tristar;
static HAS_REGISTERED_SHARED: OnceLock<()> = OnceLock::new(); pub const CHARGE_CONTROLLER_LABEL: &str = "charge_controller";
static HAS_REGISTERED_PL: OnceLock<()> = OnceLock::new(); pub const PL_LABEL: &str = "pl_device";
static HAS_REGISTERED_TRISTAR: OnceLock<()> = OnceLock::new(); pub const TRISTAR_LABEL: &str = "tristar_device";
fn register_metrics() {
if HAS_REGISTERED_SHARED.get().is_none() {
describe_gauge!(gauge_names::BATTERY_VOLTAGE, "Battery voltage");
describe_gauge!(gauge_names::TARGET_VOLTAGE, "Target voltage");
describe_gauge!(gauge_names::INPUT_CURRENT, "Internal charge current");
describe_gauge!(gauge_names::CHARGE_STATE, "Regulator state");
describe_gauge!(gauge_names::BATTERY_TEMP, "Battery temperature");
HAS_REGISTERED_SHARED.get_or_init(|| ());
}
}
pub fn register_pl_metrics() {
register_metrics();
if HAS_REGISTERED_PL.get().is_none() {
describe_gauge!(gauge_names::PL_DUTY_CYCLE, Unit::Percent, "Duty cycle");
describe_gauge!(gauge_names::PL_LOAD_CURRENT, "Internal load current");
HAS_REGISTERED_PL.get_or_init(|| ());
}
}
pub fn register_tristar_metrics() {
register_metrics();
if HAS_REGISTERED_TRISTAR.get().is_none() {
describe_gauge!(gauge_names::TRISTAR_INPUT_VOLTAGE, "Input voltage");
describe_gauge!(gauge_names::TRISTAR_CHARGE_CURRENT, "Charge current");
describe_gauge!(gauge_names::TRISTAR_POWER_OUT, "Power out");
describe_gauge!(gauge_names::TRISTAR_POWER_IN, "Power in");
describe_gauge!(gauge_names::TRISTAR_MAX_ARRAY_POWER, "Maximum array power");
describe_gauge!(
gauge_names::TRISTAR_MAX_ARRAY_VOLTAGE,
"Maximum array voltage"
);
describe_gauge!(
gauge_names::TRISTAR_OPEN_CIRCUIT_VOLTAGE,
"Open circuit voltage"
);
HAS_REGISTERED_TRISTAR.get_or_init(|| ());
}
}

View file

@ -5,24 +5,18 @@ use std::{
}; };
use chrono::Timelike; use chrono::Timelike;
use metrics::{gauge, Gauge, Label};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serialport::SerialPort; use serialport::SerialPort;
use crate::errors::{PliError, PrintErrors}; use crate::{
charge_controllers::gauges::*,
use super::{gauge_names, register_pl_metrics}; errors::{PliError, PrintErrors},
};
pub struct Pli { pub struct Pli {
pub state: Arc<RwLock<PlState>>, pub state: Arc<RwLock<PlState>>,
port_name: String,
port: Box<dyn SerialPort>, port: Box<dyn SerialPort>,
battery_voltage: Gauge,
target_voltage: Gauge,
duty_cycle: Gauge,
internal_charge_current: Gauge,
internal_load_current: Gauge,
battery_temp: Gauge,
regulator_gauges: RegulatorGauges,
} }
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
@ -62,77 +56,36 @@ impl Default for RegulatorState {
} }
} }
struct RegulatorGauges { fn set_regulator_gauges(state: &RegulatorState, label: &str) {
boost: Gauge, let boost = CHARGE_STATE.with_label_values(&[label, "boost"]);
equalise: Gauge, let equalise = CHARGE_STATE.with_label_values(&[label, "equalise"]);
absorption: Gauge, let absorption = CHARGE_STATE.with_label_values(&[label, "absorption"]);
float: Gauge, let float = CHARGE_STATE.with_label_values(&[label, "float"]);
}
impl RegulatorGauges { match state {
fn new(labels: Vec<Label>) -> Self { RegulatorState::Boost => {
let boost = gauge!( boost.set(1);
gauge_names::CHARGE_STATE, equalise.set(0);
[ absorption.set(0);
labels.clone(), float.set(0);
vec![Label::new("state", String::from("boost"))]
]
.concat()
);
let equalise = gauge!(
gauge_names::CHARGE_STATE,
[
labels.clone(),
vec![Label::new("state", String::from("equalise"))]
]
.concat()
);
let absorption = gauge!(
gauge_names::CHARGE_STATE,
[
labels.clone(),
vec![Label::new("state", String::from("absorption"))]
]
.concat()
);
let float = gauge!(
gauge_names::CHARGE_STATE,
[labels, vec![Label::new("state", String::from("float"))]].concat()
);
Self {
boost,
equalise,
absorption,
float,
} }
} RegulatorState::Equalise => {
boost.set(0);
fn set(&mut self, state: &RegulatorState) { equalise.set(1);
match state { absorption.set(0);
RegulatorState::Boost => { float.set(0);
self.boost.set(1.); }
self.equalise.set(0.); RegulatorState::Absorption => {
self.absorption.set(0.); boost.set(0);
self.float.set(0.); equalise.set(0);
} absorption.set(1);
RegulatorState::Equalise => { float.set(0);
self.boost.set(0.); }
self.equalise.set(1.); RegulatorState::Float => {
self.absorption.set(0.); boost.set(0);
self.float.set(0.); equalise.set(0);
} absorption.set(0);
RegulatorState::Absorption => { float.set(1);
self.boost.set(0.);
self.equalise.set(0.);
self.absorption.set(1.);
self.float.set(0.);
}
RegulatorState::Float => {
self.boost.set(0.);
self.equalise.set(0.);
self.absorption.set(0.);
self.float.set(1.);
}
} }
} }
} }
@ -154,44 +107,35 @@ impl Pli {
.timeout(Duration::from_millis(timeout)) .timeout(Duration::from_millis(timeout))
.open()?; .open()?;
register_pl_metrics();
let device_labels = vec![
Label::new(gauge_names::CHARGE_CONTROLLER_LABEL, serial_port.clone()),
Label::new(gauge_names::PL_LABEL, serial_port),
];
let battery_voltage = gauge!(gauge_names::BATTERY_VOLTAGE, device_labels.clone());
let target_voltage = gauge!(gauge_names::TARGET_VOLTAGE, device_labels.clone());
let duty_cycle = gauge!(gauge_names::PL_DUTY_CYCLE, device_labels.clone());
let internal_charge_current = gauge!(gauge_names::INPUT_CURRENT, device_labels.clone());
let internal_load_current = gauge!(gauge_names::PL_LOAD_CURRENT, device_labels.clone());
let battery_temp = gauge!(gauge_names::BATTERY_TEMP, device_labels.clone());
Ok(Self { Ok(Self {
state: Arc::new(RwLock::new(Default::default())), state: Arc::new(RwLock::new(Default::default())),
port_name: serial_port,
port, port,
battery_voltage,
target_voltage,
duty_cycle,
internal_charge_current,
internal_load_current,
battery_temp,
regulator_gauges: RegulatorGauges::new(device_labels),
}) })
} }
pub fn refresh(&mut self) { pub fn refresh(&mut self) {
if let Some(new_state) = self.read_state().some_or_print_with("reading pl state") { if let Some(new_state) = self.read_state().some_or_print_with("reading pl state") {
self.battery_voltage.set(new_state.battery_voltage); BATTERY_VOLTAGE
self.target_voltage.set(new_state.target_voltage); .with_label_values(&[&self.port_name])
self.duty_cycle.set(new_state.duty_cycle); .set(new_state.battery_voltage);
self.internal_charge_current TARGET_VOLTAGE
.with_label_values(&[&self.port_name])
.set(new_state.target_voltage);
PL_DUTY_CYCLE
.with_label_values(&[&self.port_name])
.set(new_state.duty_cycle);
INPUT_CURRENT
.with_label_values(&[&self.port_name])
.set(new_state.internal_charge_current); .set(new_state.internal_charge_current);
self.internal_load_current PL_LOAD_CURRENT
.with_label_values(&[&self.port_name])
.set(new_state.internal_load_current); .set(new_state.internal_load_current);
self.battery_temp.set(new_state.battery_temp); BATTERY_TEMP
self.regulator_gauges.set(&new_state.regulator_state); .with_label_values(&[&self.port_name])
.set(new_state.battery_temp);
set_regulator_gauges(&new_state.regulator_state, &self.port_name);
*self.state.write().expect("PLI state handler panicked!!") = new_state; *self.state.write().expect("PLI state handler panicked!!") = new_state;
} }

View file

@ -1,9 +1,7 @@
use libmodbus_rs::{Modbus, ModbusClient, ModbusRTU}; use libmodbus_rs::{Modbus, ModbusClient, ModbusRTU};
use metrics::{gauge, Gauge, Label}; use prometheus::core::{AtomicI64, GenericGauge};
use crate::errors::TristarError; use crate::{charge_controllers::gauges::*, errors::TristarError};
use super::{gauge_names, register_tristar_metrics};
const DEVICE_ID: u8 = 0x01; const DEVICE_ID: u8 = 0x01;
const RAM_DATA_SIZE: u16 = 0x005B; const RAM_DATA_SIZE: u16 = 0x005B;
@ -42,70 +40,10 @@ impl Scaling {
pub struct Tristar { pub struct Tristar {
state: TristarState, state: TristarState,
gauges: TristarGauges, port_name: String,
modbus: Modbus, modbus: Modbus,
data_in: [u16; RAM_ARRAY_SIZE], data_in: [u16; RAM_ARRAY_SIZE],
} charge_state_gauges: ChargeStateGauges,
pub struct TristarGauges {
battery_voltage: Gauge,
target_voltage: Gauge,
input_current: Gauge,
battery_temp: Gauge,
charge_state: ChargeStateGauges,
tristar_input_voltage: Gauge,
tristar_charge_current: Gauge,
tristar_power_out: Gauge,
tristar_power_in: Gauge,
tristar_max_array_power: Gauge,
tristar_max_array_voltage: Gauge,
tristar_open_circuit_voltage: Gauge,
}
impl TristarGauges {
fn new(serial_port: String) -> Self {
let device_labels = vec![
Label::new(gauge_names::CHARGE_CONTROLLER_LABEL, serial_port.clone()),
Label::new(gauge_names::TRISTAR_LABEL, serial_port),
];
let battery_voltage = gauge!(gauge_names::BATTERY_VOLTAGE, device_labels.clone());
let target_voltage = gauge!(gauge_names::TARGET_VOLTAGE, device_labels.clone());
let input_current = gauge!(gauge_names::INPUT_CURRENT, device_labels.clone());
let battery_temp = gauge!(gauge_names::BATTERY_TEMP, device_labels.clone());
let charge_state = ChargeStateGauges::new(device_labels.clone());
let tristar_input_voltage =
gauge!(gauge_names::TRISTAR_INPUT_VOLTAGE, device_labels.clone());
let tristar_charge_current =
gauge!(gauge_names::TRISTAR_CHARGE_CURRENT, device_labels.clone());
let tristar_power_out = gauge!(gauge_names::TRISTAR_POWER_OUT, device_labels.clone());
let tristar_power_in = gauge!(gauge_names::TRISTAR_POWER_IN, device_labels.clone());
let tristar_max_array_power =
gauge!(gauge_names::TRISTAR_MAX_ARRAY_POWER, device_labels.clone());
let tristar_max_array_voltage = gauge!(
gauge_names::TRISTAR_MAX_ARRAY_VOLTAGE,
device_labels.clone()
);
let tristar_open_circuit_voltage = gauge!(
gauge_names::TRISTAR_OPEN_CIRCUIT_VOLTAGE,
device_labels.clone()
);
Self {
battery_voltage,
target_voltage,
input_current,
battery_temp,
charge_state,
tristar_input_voltage,
tristar_charge_current,
tristar_power_out,
tristar_power_in,
tristar_max_array_power,
tristar_max_array_voltage,
tristar_open_circuit_voltage,
}
}
} }
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
@ -183,109 +121,32 @@ impl From<u16> for ChargeState {
} }
struct ChargeStateGauges { struct ChargeStateGauges {
start: Gauge, start: GenericGauge<AtomicI64>,
night_check: Gauge, night_check: GenericGauge<AtomicI64>,
disconnect: Gauge, disconnect: GenericGauge<AtomicI64>,
night: Gauge, night: GenericGauge<AtomicI64>,
fault: Gauge, fault: GenericGauge<AtomicI64>,
mppt: Gauge, mppt: GenericGauge<AtomicI64>,
absorption: Gauge, absorption: GenericGauge<AtomicI64>,
float: Gauge, float: GenericGauge<AtomicI64>,
equalize: Gauge, equalize: GenericGauge<AtomicI64>,
slave: Gauge, slave: GenericGauge<AtomicI64>,
unknown: Gauge, unknown: GenericGauge<AtomicI64>,
} }
impl ChargeStateGauges { impl ChargeStateGauges {
fn new(labels: Vec<Label>) -> Self { fn new(label: &str) -> Self {
let start = gauge!( let start = CHARGE_STATE.with_label_values(&[label, "start"]);
gauge_names::CHARGE_STATE, let night_check = CHARGE_STATE.with_label_values(&[label, "night_check"]);
[ let disconnect = CHARGE_STATE.with_label_values(&[label, "disconnect"]);
labels.clone(), let night = CHARGE_STATE.with_label_values(&[label, "night"]);
vec![Label::new("state", String::from("start"))] let fault = CHARGE_STATE.with_label_values(&[label, "fault"]);
] let mppt = CHARGE_STATE.with_label_values(&[label, "mppt"]);
.concat() let absorption = CHARGE_STATE.with_label_values(&[label, "absorption"]);
); let float = CHARGE_STATE.with_label_values(&[label, "float"]);
let night_check = gauge!( let equalize = CHARGE_STATE.with_label_values(&[label, "equalize"]);
gauge_names::CHARGE_STATE, let slave = CHARGE_STATE.with_label_values(&[label, "slave"]);
[ let unknown = CHARGE_STATE.with_label_values(&[label, "unknown"]);
labels.clone(),
vec![Label::new("state", String::from("night_check"))]
]
.concat()
);
let disconnect = gauge!(
gauge_names::CHARGE_STATE,
[
labels.clone(),
vec![Label::new("state", String::from("disconnect"))]
]
.concat()
);
let night = gauge!(
gauge_names::CHARGE_STATE,
[
labels.clone(),
vec![Label::new("state", String::from("night"))]
]
.concat()
);
let fault = gauge!(
gauge_names::CHARGE_STATE,
[
labels.clone(),
vec![Label::new("state", String::from("fault"))]
]
.concat()
);
let mppt = gauge!(
gauge_names::CHARGE_STATE,
[
labels.clone(),
vec![Label::new("state", String::from("mppt"))]
]
.concat()
);
let absorption = gauge!(
gauge_names::CHARGE_STATE,
[
labels.clone(),
vec![Label::new("state", String::from("absorption"))]
]
.concat()
);
let float = gauge!(
gauge_names::CHARGE_STATE,
[
labels.clone(),
vec![Label::new("state", String::from("float"))]
]
.concat()
);
let equalize = gauge!(
gauge_names::CHARGE_STATE,
[
labels.clone(),
vec![Label::new("state", String::from("equalize"))]
]
.concat()
);
let slave = gauge!(
gauge_names::CHARGE_STATE,
[
labels.clone(),
vec![Label::new("state", String::from("slave"))]
]
.concat()
);
let unknown = gauge!(
gauge_names::CHARGE_STATE,
[
labels.clone(),
vec![Label::new("state", String::from("unknown"))]
]
.concat()
);
Self { Self {
start, start,
night_check, night_check,
@ -302,64 +163,64 @@ impl ChargeStateGauges {
} }
fn zero_all(&mut self) { fn zero_all(&mut self) {
self.start.set(0.); self.start.set(0);
self.night_check.set(0.); self.night_check.set(0);
self.disconnect.set(0.); self.disconnect.set(0);
self.night.set(0.); self.night.set(0);
self.fault.set(0.); self.fault.set(0);
self.mppt.set(0.); self.mppt.set(0);
self.absorption.set(0.); self.absorption.set(0);
self.float.set(0.); self.float.set(0);
self.equalize.set(0.); self.equalize.set(0);
self.slave.set(0.); self.slave.set(0);
self.unknown.set(0.); self.unknown.set(0);
} }
fn set(&mut self, state: ChargeState) { fn set(&mut self, state: ChargeState) {
match state { match state {
ChargeState::Start => { ChargeState::Start => {
self.zero_all(); self.zero_all();
self.start.set(1.); self.start.set(1);
} }
ChargeState::NightCheck => { ChargeState::NightCheck => {
self.zero_all(); self.zero_all();
self.night_check.set(1.); self.night_check.set(1);
} }
ChargeState::Disconnect => { ChargeState::Disconnect => {
self.zero_all(); self.zero_all();
self.disconnect.set(1.); self.disconnect.set(1);
} }
ChargeState::Night => { ChargeState::Night => {
self.zero_all(); self.zero_all();
self.night.set(1.); self.night.set(1);
} }
ChargeState::Fault => { ChargeState::Fault => {
self.zero_all(); self.zero_all();
self.fault.set(1.); self.fault.set(1);
} }
ChargeState::Mppt => { ChargeState::Mppt => {
self.zero_all(); self.zero_all();
self.mppt.set(1.); self.mppt.set(1);
} }
ChargeState::Absorption => { ChargeState::Absorption => {
self.zero_all(); self.zero_all();
self.absorption.set(1.); self.absorption.set(1);
} }
ChargeState::Float => { ChargeState::Float => {
self.zero_all(); self.zero_all();
self.float.set(1.); self.float.set(1);
} }
ChargeState::Equalize => { ChargeState::Equalize => {
self.zero_all(); self.zero_all();
self.equalize.set(1.); self.equalize.set(1);
} }
ChargeState::Slave => { ChargeState::Slave => {
self.zero_all(); self.zero_all();
self.slave.set(1.); self.slave.set(1);
} }
ChargeState::Unknown => { ChargeState::Unknown => {
self.zero_all(); self.zero_all();
self.unknown.set(1.); self.unknown.set(1);
} }
} }
} }
@ -373,12 +234,13 @@ impl Tristar {
let mut modbus = Modbus::new_rtu(&serial_port, baud_rate, parity, data_bit, stop_bit)?; let mut modbus = Modbus::new_rtu(&serial_port, baud_rate, parity, data_bit, stop_bit)?;
modbus.set_slave(DEVICE_ID)?; modbus.set_slave(DEVICE_ID)?;
modbus.connect()?; modbus.connect()?;
register_tristar_metrics(); let charge_state_gauges = ChargeStateGauges::new(&serial_port);
Ok(Self { Ok(Self {
state: Default::default(), state: Default::default(),
gauges: TristarGauges::new(serial_port.clone()), port_name: serial_port,
modbus, modbus,
data_in: [0; RAM_ARRAY_SIZE], data_in: [0; RAM_ARRAY_SIZE],
charge_state_gauges,
}) })
} }
@ -387,30 +249,41 @@ impl Tristar {
.get_data() .get_data()
.map(|scaling| TristarState::from_ram(scaling, &self.data_in)) .map(|scaling| TristarState::from_ram(scaling, &self.data_in))
{ {
self.gauges.battery_voltage.set(new_state.battery_voltage); BATTERY_VOLTAGE
self.gauges.target_voltage.set(new_state.target_voltage); .with_label_values(&[&self.port_name])
self.gauges.input_current.set(new_state.input_current); .set(new_state.battery_voltage);
self.gauges.battery_temp.set(new_state.battery_temp as f64); TARGET_VOLTAGE
self.gauges.charge_state.set(new_state.charge_state); .with_label_values(&[&self.port_name])
self.gauges .set(new_state.target_voltage);
.tristar_input_voltage INPUT_CURRENT
.with_label_values(&[&self.port_name])
.set(new_state.input_current);
BATTERY_TEMP
.with_label_values(&[&self.port_name])
.set(new_state.battery_temp.into());
TRISTAR_INPUT_VOLTAGE
.with_label_values(&[&self.port_name])
.set(new_state.tristar_input_voltage); .set(new_state.tristar_input_voltage);
self.gauges TRISTAR_CHARGE_CURRENT
.tristar_charge_current .with_label_values(&[&self.port_name])
.set(new_state.tristar_charge_current); .set(new_state.tristar_charge_current);
self.gauges TRISTAR_POWER_OUT
.tristar_power_out .with_label_values(&[&self.port_name])
.set(new_state.tristar_power_out); .set(new_state.tristar_power_out);
self.gauges.tristar_power_in.set(new_state.tristar_power_in); TRISTAR_POWER_IN
self.gauges .with_label_values(&[&self.port_name])
.tristar_max_array_power .set(new_state.tristar_power_in);
TRISTAR_MAX_ARRAY_POWER
.with_label_values(&[&self.port_name])
.set(new_state.tristar_max_array_power); .set(new_state.tristar_max_array_power);
self.gauges TRISTAR_MAX_ARRAY_VOLTAGE
.tristar_max_array_voltage .with_label_values(&[&self.port_name])
.set(new_state.tristar_max_array_voltage); .set(new_state.tristar_max_array_voltage);
self.gauges TRISTAR_OPEN_CIRCUIT_VOLTAGE
.tristar_open_circuit_voltage .with_label_values(&[&self.port_name])
.set(new_state.tristar_open_circuit_voltage); .set(new_state.tristar_open_circuit_voltage);
self.charge_state_gauges.set(new_state.charge_state);
self.state = new_state; self.state = new_state;
} }
} }

View file

@ -51,7 +51,6 @@ async fn main() {
.init(); .init();
let args = Args::parse(); let args = Args::parse();
let _recorder = metrics_prometheus::install();
match args.command { match args.command {
Commands::GenerateConfig => { Commands::GenerateConfig => {

View file

@ -129,7 +129,7 @@ async fn flash(state: &State<ServerState>, remote_addr: std::net::IpAddr) {
async fn disable_control(state: &State<ServerState>, remote_addr: std::net::IpAddr) { async fn disable_control(state: &State<ServerState>, remote_addr: std::net::IpAddr) {
log::warn!("disabling control: {remote_addr:?}"); log::warn!("disabling control: {remote_addr:?}");
match state.tcrc_state.write() { match state.tcrc_state.write() {
Ok(mut handle) => handle.control_enable = false, Ok(mut handle) => handle.set_control_enable(false),
Err(e) => log::error!("Error disabling control: {e:?}"), Err(e) => log::error!("Error disabling control: {e:?}"),
} }
} }
@ -138,7 +138,7 @@ async fn disable_control(state: &State<ServerState>, remote_addr: std::net::IpAd
async fn enable_control(state: &State<ServerState>, remote_addr: std::net::IpAddr) { async fn enable_control(state: &State<ServerState>, remote_addr: std::net::IpAddr) {
log::warn!("enabling control: {remote_addr:?}"); log::warn!("enabling control: {remote_addr:?}");
match state.tcrc_state.write() { match state.tcrc_state.write() {
Ok(mut handle) => handle.control_enable = true, Ok(mut handle) => handle.set_control_enable(true),
Err(e) => log::error!("Error enabling control: {e:?}"), Err(e) => log::error!("Error enabling control: {e:?}"),
} }
} }

View file

@ -1,7 +1,8 @@
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use if_chain::if_chain; use if_chain::if_chain;
use metrics::{describe_gauge, gauge, Gauge}; use lazy_static::lazy_static;
use prometheus::{register_gauge, register_int_gauge, Gauge, IntGauge};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
@ -11,13 +12,34 @@ use crate::{
types::{CarState, ChargeState}, types::{CarState, ChargeState},
}; };
lazy_static! {
pub static ref CONTROL_ENABLE_GAUGE: IntGauge =
register_int_gauge!("tcrc_control_enable", "Enable Tesla charge rate control",).unwrap();
pub static ref PROPORTIONAL_GAUGE: Gauge = register_gauge!(
"tcrc_proportional",
"Proportional component of requested change to charge rate",
)
.unwrap();
pub static ref DERIVATIVE_GAUGE: Gauge = register_gauge!(
"tcrc_derivative",
"Derivative component of requested change to charge rate",
)
.unwrap();
pub static ref LOAD_GAUGE: Gauge = register_gauge!(
"tcrc_load",
"Fudge factor from internal load of requested change to charge rate",
)
.unwrap();
pub static ref CHANGE_REQUEST_GAUGE: Gauge =
register_gauge!("tcrc_change_request", "Requested change to charge rate",).unwrap();
}
pub struct TeslaChargeRateController { pub struct TeslaChargeRateController {
pub car_state: Arc<RwLock<CarState>>, pub car_state: Arc<RwLock<CarState>>,
pub pl_state: Option<Arc<RwLock<PlState>>>, pub pl_state: Option<Arc<RwLock<PlState>>>,
pub tcrc_state: Arc<RwLock<TcrcState>>, pub tcrc_state: Arc<RwLock<TcrcState>>,
pid: PidLoop, pid: PidLoop,
voltage_low: u64, voltage_low: u64,
control_enable_gauge: Gauge,
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -31,6 +53,13 @@ pub struct TcrcState {
pub control_enable: bool, pub control_enable: bool,
} }
impl TcrcState {
pub fn set_control_enable(&mut self, to: bool) {
self.control_enable = to;
CONTROL_ENABLE_GAUGE.set(if to { 1 } else { 0 });
}
}
impl Default for TcrcState { impl Default for TcrcState {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -41,14 +70,12 @@ impl Default for TcrcState {
impl TeslaChargeRateController { impl TeslaChargeRateController {
pub fn new(car_state: Arc<RwLock<CarState>>, pl_state: Option<Arc<RwLock<PlState>>>) -> Self { pub fn new(car_state: Arc<RwLock<CarState>>, pl_state: Option<Arc<RwLock<PlState>>>) -> Self {
describe_gauge!("tcrc_control_enable", "Enable Tesla charge rate control");
Self { Self {
car_state, car_state,
pl_state, pl_state,
tcrc_state: Default::default(), tcrc_state: Default::default(),
pid: Default::default(), pid: Default::default(),
voltage_low: 0, voltage_low: 0,
control_enable_gauge: gauge!("tcrc_control_enable"),
} }
} }
@ -82,15 +109,13 @@ impl TeslaChargeRateController {
self.tcrc_state self.tcrc_state
.write() .write()
.expect("failed to write to tcrc state") .expect("failed to write to tcrc state")
.control_enable = false; .set_control_enable(false);
self.control_enable_gauge.set(0.);
} }
TcrcRequest::EnableAutomaticControl => { TcrcRequest::EnableAutomaticControl => {
self.tcrc_state self.tcrc_state
.write() .write()
.expect("failed to write to tcrc state") .expect("failed to write to tcrc state")
.control_enable = true; .set_control_enable(true);
self.control_enable_gauge.set(1.);
} }
} }
} }
@ -98,38 +123,11 @@ impl TeslaChargeRateController {
struct PidLoop { struct PidLoop {
previous_error: f64, previous_error: f64,
proportional_gauge: Gauge,
derivative_gauge: Gauge,
load_gauge: Gauge,
change_request_gauge: Gauge,
} }
impl Default for PidLoop { impl Default for PidLoop {
fn default() -> Self { fn default() -> Self {
describe_gauge!( Self { previous_error: 0. }
"tcrc_proportional",
"Proportional component of requested change to charge rate"
);
let proportional_gauge = gauge!("tcrc_proportional");
describe_gauge!(
"tcrc_proportional",
"Derivative component of requested change to charge rate"
);
let derivative_gauge = gauge!("tcrc_derivative");
describe_gauge!(
"tcrc_load",
"Fudge factor from internal load of requested change to charge rate"
);
let load_gauge = gauge!("tcrc_load");
describe_gauge!("tcrc_change_request", "Requested change to charge rate");
let change_request_gauge = gauge!("tcrc_change_request");
Self {
previous_error: 0.,
proportional_gauge,
derivative_gauge,
load_gauge,
change_request_gauge,
}
} }
} }
@ -152,10 +150,10 @@ impl PidLoop {
let offset = proportional_component + derivative_component + extra_offsets; let offset = proportional_component + derivative_component + extra_offsets;
self.proportional_gauge.set(proportional_component); PROPORTIONAL_GAUGE.set(proportional_component);
self.derivative_gauge.set(derivative_component); DERIVATIVE_GAUGE.set(derivative_component);
self.load_gauge.set(extra_offsets); LOAD_GAUGE.set(extra_offsets);
self.change_request_gauge.set(offset); CHANGE_REQUEST_GAUGE.set(offset);
let new_target = offset + (charge_state.charge_amps as f64); let new_target = offset + (charge_state.charge_amps as f64);