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"
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]]
name = "aho-corasick"
version = "0.6.10"
@ -119,12 +107,6 @@ dependencies = [
"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]]
name = "async-stream"
version = "0.3.5"
@ -494,17 +476,6 @@ dependencies = [
"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]]
name = "crossbeam-utils"
version = "0.8.18"
@ -878,15 +849,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
@ -1091,7 +1053,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"equivalent",
"hashbrown 0.14.3",
"hashbrown",
"serde",
]
@ -1359,45 +1321,6 @@ version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "miette"
version = "5.10.0"
@ -1731,12 +1654,6 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
[[package]]
name = "portable-atomic"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -2280,18 +2197,6 @@ dependencies = [
"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]]
name = "security-framework"
version = "2.9.2"
@ -2633,17 +2538,16 @@ dependencies = [
[[package]]
name = "tesla-charge-controller"
version = "1.0.31"
version = "1.1.0"
dependencies = [
"chrono",
"clap 4.4.11",
"env_logger 0.10.1",
"if_chain",
"include_dir",
"lazy_static 1.4.0",
"libmodbus-rs",
"log 0.4.20",
"metrics",
"metrics-prometheus",
"notify-debouncer-mini",
"prometheus",
"rocket",
@ -3463,26 +3367,6 @@ dependencies = [
"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]]
name = "zeroize"
version = "1.7.0"

View file

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

View file

@ -5,24 +5,18 @@ use std::{
};
use chrono::Timelike;
use metrics::{gauge, Gauge, Label};
use serde::{Deserialize, Serialize};
use serialport::SerialPort;
use crate::errors::{PliError, PrintErrors};
use super::{gauge_names, register_pl_metrics};
use crate::{
charge_controllers::gauges::*,
errors::{PliError, PrintErrors},
};
pub struct Pli {
pub state: Arc<RwLock<PlState>>,
port_name: String,
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)]
@ -62,77 +56,36 @@ impl Default for RegulatorState {
}
}
struct RegulatorGauges {
boost: Gauge,
equalise: Gauge,
absorption: Gauge,
float: Gauge,
}
fn set_regulator_gauges(state: &RegulatorState, label: &str) {
let boost = CHARGE_STATE.with_label_values(&[label, "boost"]);
let equalise = CHARGE_STATE.with_label_values(&[label, "equalise"]);
let absorption = CHARGE_STATE.with_label_values(&[label, "absorption"]);
let float = CHARGE_STATE.with_label_values(&[label, "float"]);
impl RegulatorGauges {
fn new(labels: Vec<Label>) -> Self {
let boost = gauge!(
gauge_names::CHARGE_STATE,
[
labels.clone(),
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,
}
}
fn set(&mut self, state: &RegulatorState) {
match state {
RegulatorState::Boost => {
self.boost.set(1.);
self.equalise.set(0.);
self.absorption.set(0.);
self.float.set(0.);
boost.set(1);
equalise.set(0);
absorption.set(0);
float.set(0);
}
RegulatorState::Equalise => {
self.boost.set(0.);
self.equalise.set(1.);
self.absorption.set(0.);
self.float.set(0.);
boost.set(0);
equalise.set(1);
absorption.set(0);
float.set(0);
}
RegulatorState::Absorption => {
self.boost.set(0.);
self.equalise.set(0.);
self.absorption.set(1.);
self.float.set(0.);
boost.set(0);
equalise.set(0);
absorption.set(1);
float.set(0);
}
RegulatorState::Float => {
self.boost.set(0.);
self.equalise.set(0.);
self.absorption.set(0.);
self.float.set(1.);
}
boost.set(0);
equalise.set(0);
absorption.set(0);
float.set(1);
}
}
}
@ -154,44 +107,35 @@ impl Pli {
.timeout(Duration::from_millis(timeout))
.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 {
state: Arc::new(RwLock::new(Default::default())),
port_name: serial_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) {
if let Some(new_state) = self.read_state().some_or_print_with("reading pl state") {
self.battery_voltage.set(new_state.battery_voltage);
self.target_voltage.set(new_state.target_voltage);
self.duty_cycle.set(new_state.duty_cycle);
self.internal_charge_current
BATTERY_VOLTAGE
.with_label_values(&[&self.port_name])
.set(new_state.battery_voltage);
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);
self.internal_load_current
PL_LOAD_CURRENT
.with_label_values(&[&self.port_name])
.set(new_state.internal_load_current);
self.battery_temp.set(new_state.battery_temp);
self.regulator_gauges.set(&new_state.regulator_state);
BATTERY_TEMP
.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;
}

View file

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

View file

@ -51,7 +51,6 @@ async fn main() {
.init();
let args = Args::parse();
let _recorder = metrics_prometheus::install();
match args.command {
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) {
log::warn!("disabling control: {remote_addr:?}");
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:?}"),
}
}
@ -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) {
log::warn!("enabling control: {remote_addr:?}");
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:?}"),
}
}

View file

@ -1,7 +1,8 @@
use std::sync::{Arc, RwLock};
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 crate::{
@ -11,13 +12,34 @@ use crate::{
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 car_state: Arc<RwLock<CarState>>,
pub pl_state: Option<Arc<RwLock<PlState>>>,
pub tcrc_state: Arc<RwLock<TcrcState>>,
pid: PidLoop,
voltage_low: u64,
control_enable_gauge: Gauge,
}
#[allow(dead_code)]
@ -31,6 +53,13 @@ pub struct TcrcState {
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 {
fn default() -> Self {
Self {
@ -41,14 +70,12 @@ impl Default for TcrcState {
impl TeslaChargeRateController {
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 {
car_state,
pl_state,
tcrc_state: Default::default(),
pid: Default::default(),
voltage_low: 0,
control_enable_gauge: gauge!("tcrc_control_enable"),
}
}
@ -82,15 +109,13 @@ impl TeslaChargeRateController {
self.tcrc_state
.write()
.expect("failed to write to tcrc state")
.control_enable = false;
self.control_enable_gauge.set(0.);
.set_control_enable(false);
}
TcrcRequest::EnableAutomaticControl => {
self.tcrc_state
.write()
.expect("failed to write to tcrc state")
.control_enable = true;
self.control_enable_gauge.set(1.);
.set_control_enable(true);
}
}
}
@ -98,38 +123,11 @@ impl TeslaChargeRateController {
struct PidLoop {
previous_error: f64,
proportional_gauge: Gauge,
derivative_gauge: Gauge,
load_gauge: Gauge,
change_request_gauge: Gauge,
}
impl Default for PidLoop {
fn default() -> Self {
describe_gauge!(
"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,
}
Self { previous_error: 0. }
}
}
@ -152,10 +150,10 @@ impl PidLoop {
let offset = proportional_component + derivative_component + extra_offsets;
self.proportional_gauge.set(proportional_component);
self.derivative_gauge.set(derivative_component);
self.load_gauge.set(extra_offsets);
self.change_request_gauge.set(offset);
PROPORTIONAL_GAUGE.set(proportional_component);
DERIVATIVE_GAUGE.set(derivative_component);
LOAD_GAUGE.set(extra_offsets);
CHANGE_REQUEST_GAUGE.set(offset);
let new_target = offset + (charge_state.charge_amps as f64);