prometheus + climate state!
This commit is contained in:
parent
3706d7a527
commit
d58633ca54
141
Cargo.lock
generated
141
Cargo.lock
generated
|
@ -17,6 +17,18 @@ 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",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
|
@ -95,6 +107,12 @@ version = "1.0.77"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9d19de80eff169429ac1e9f48fffb163916b448a44e8e046186232046d9e1f9"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.1.1"
|
||||
|
@ -406,6 +424,17 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.18"
|
||||
|
@ -722,6 +751,15 @@ 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"
|
||||
|
@ -905,7 +943,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"hashbrown 0.14.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
@ -1023,6 +1061,45 @@ 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"
|
||||
|
@ -1306,6 +1383,12 @@ 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"
|
||||
|
@ -1340,6 +1423,27 @@ dependencies = [
|
|||
"yansi 1.0.0-rc.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometheus"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fnv",
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
"parking_lot",
|
||||
"protobuf",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "2.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
|
||||
|
||||
[[package]]
|
||||
name = "psl-types"
|
||||
version = "2.0.11"
|
||||
|
@ -1754,6 +1858,18 @@ 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",
|
||||
"syn 2.0.43",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.9.2"
|
||||
|
@ -2056,6 +2172,9 @@ dependencies = [
|
|||
"chrono",
|
||||
"clap",
|
||||
"include_dir",
|
||||
"metrics",
|
||||
"metrics-prometheus",
|
||||
"prometheus",
|
||||
"rocket",
|
||||
"ron",
|
||||
"serde",
|
||||
|
@ -2769,6 +2888,26 @@ 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",
|
||||
"syn 2.0.43",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.7.0"
|
||||
|
|
|
@ -24,3 +24,6 @@ anyhow = "1.0"
|
|||
include_dir = "0.7"
|
||||
chrono = "0.4"
|
||||
async-channel = "2.1"
|
||||
metrics = "0.22"
|
||||
metrics-prometheus = "0.6.0"
|
||||
prometheus = "0.13"
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use anyhow::{Context, Result};
|
||||
use metrics::{describe_gauge, gauge, Gauge, Unit};
|
||||
use metrics_prometheus::Recorder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
|
@ -13,12 +15,41 @@ use teslatte::{
|
|||
|
||||
use crate::{errors::*, types::CarState};
|
||||
|
||||
struct Metrics {
|
||||
battery_level: Gauge,
|
||||
charge_rate: Gauge,
|
||||
inside_temp: Gauge,
|
||||
}
|
||||
|
||||
impl Metrics {
|
||||
fn new() -> (Self, Recorder) {
|
||||
let recorder = metrics_prometheus::install();
|
||||
|
||||
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_inside_temp", "Inside temperature");
|
||||
let inside_temp = gauge!("tesla_inside_temp");
|
||||
|
||||
(
|
||||
Self {
|
||||
battery_level,
|
||||
charge_rate,
|
||||
inside_temp,
|
||||
},
|
||||
recorder,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TeslaInterface {
|
||||
pub state: Arc<RwLock<CarState>>,
|
||||
api: FleetApi,
|
||||
vehicle: Box<teslatte::vehicles::VehicleData>,
|
||||
last_refresh: Instant,
|
||||
auth_path: PathBuf,
|
||||
metrics: Metrics,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
|
@ -52,12 +83,15 @@ impl TeslaInterface {
|
|||
.next()
|
||||
.context("No vehicles attached to account!")?;
|
||||
|
||||
let (metrics, _recorder) = Metrics::new();
|
||||
|
||||
let interface = Self {
|
||||
state: Arc::new(RwLock::new(Default::default())),
|
||||
api,
|
||||
last_refresh,
|
||||
auth_path,
|
||||
vehicle,
|
||||
metrics,
|
||||
};
|
||||
interface.save_key()?;
|
||||
|
||||
|
@ -91,23 +125,30 @@ impl TeslaInterface {
|
|||
}
|
||||
|
||||
async fn refresh_state(&mut self) {
|
||||
match get_state(&self.api, self.vehicle.id.clone()).await {
|
||||
match get_state(&self.api, self.vehicle.id).await {
|
||||
Ok(new_state) => {
|
||||
self.last_refresh = Instant::now();
|
||||
let mut state = self.state.write().expect("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);
|
||||
state.charge_state = Some(new_charge_state);
|
||||
}
|
||||
if let Some(new_location_data) = new_state.location_data {
|
||||
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);
|
||||
state.climate_state = Some(new_climate_state);
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("Error getting charge state: {e:#?}"),
|
||||
Err(e) => eprintln!("Error getting state: {e:#?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
async fn refresh_keys(&mut self) {
|
||||
if Instant::now().duration_since(self.last_refresh) >= REFRESH_INTERVAL {
|
||||
match self.api.refresh().await {
|
||||
|
@ -125,25 +166,36 @@ impl TeslaInterface {
|
|||
}
|
||||
|
||||
async fn get_state(api: &FleetApi, vehicle_id: VehicleId) -> Result<CarState> {
|
||||
let vehicle_data = api
|
||||
let charge_state = api
|
||||
.vehicle_data(&GetVehicleData {
|
||||
vehicle_id: vehicle_id.clone(),
|
||||
vehicle_id,
|
||||
endpoints: vec![Endpoint::ChargeState].into(),
|
||||
// endpoints: vec![Endpoint::VehicleDataCombo].into(),
|
||||
})
|
||||
.await?;
|
||||
let charge_state = vehicle_data.charge_state.map(|v| v.into());
|
||||
.await?
|
||||
.charge_state
|
||||
.map(|v| v.into());
|
||||
|
||||
let vehicle_data = api
|
||||
let location_data = api
|
||||
.vehicle_data(&GetVehicleData {
|
||||
vehicle_id,
|
||||
endpoints: vec![Endpoint::LocationData].into(),
|
||||
})
|
||||
.await?;
|
||||
let location_data = vehicle_data.drive_state.and_then(|v| v.try_into().ok());
|
||||
.await?
|
||||
.drive_state
|
||||
.and_then(|v| v.try_into().ok());
|
||||
|
||||
let climate_state = api
|
||||
.vehicle_data(&GetVehicleData {
|
||||
vehicle_id,
|
||||
endpoints: vec![Endpoint::ClimateState].into(),
|
||||
})
|
||||
.await?
|
||||
.climate_state
|
||||
.and_then(|v| v.try_into().ok());
|
||||
|
||||
Ok(CarState {
|
||||
charge_state,
|
||||
location_data,
|
||||
climate_state,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use rocket::{
|
|||
use crate::{
|
||||
api_interface::InterfaceRequest,
|
||||
config::Config,
|
||||
types::{CarState, ChargeState},
|
||||
types::{CarState, ChargeState, ClimateState},
|
||||
};
|
||||
|
||||
use self::static_handler::UiStatic;
|
||||
|
@ -42,7 +42,10 @@ fn rocket(state: ServerState) -> rocket::Rocket<rocket::Build> {
|
|||
.attach(Cors)
|
||||
.manage(state)
|
||||
.mount("/", fileserver)
|
||||
.mount("/", routes![home, charge_state, flash])
|
||||
.mount(
|
||||
"/",
|
||||
routes![home, charge_state, flash, climate_state, metrics],
|
||||
)
|
||||
}
|
||||
|
||||
#[get("/home")]
|
||||
|
@ -56,11 +59,23 @@ async fn charge_state(state: &State<ServerState>) -> Option<Json<ChargeState>> {
|
|||
Some(Json(state.state.read().ok()?.charge_state?))
|
||||
}
|
||||
|
||||
#[get("/climate-state")]
|
||||
async fn climate_state(state: &State<ServerState>) -> Option<Json<ClimateState>> {
|
||||
Some(Json(state.state.read().ok()?.climate_state?))
|
||||
}
|
||||
|
||||
#[post("/flash")]
|
||||
async fn flash(state: &State<ServerState>) {
|
||||
let _ = state.api_requests.send(InterfaceRequest::FlashLights).await;
|
||||
}
|
||||
|
||||
#[get("/metrics")]
|
||||
fn metrics() -> Option<String> {
|
||||
prometheus::TextEncoder::new()
|
||||
.encode_to_string(&prometheus::default_registry().gather())
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub struct Cors;
|
||||
|
||||
#[rocket::async_trait]
|
||||
|
|
18
src/types.rs
18
src/types.rs
|
@ -6,6 +6,24 @@ use serde::{Deserialize, Serialize};
|
|||
pub struct CarState {
|
||||
pub charge_state: Option<ChargeState>,
|
||||
pub location_data: Option<LocationData>,
|
||||
pub climate_state: Option<ClimateState>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||
pub struct ClimateState {
|
||||
pub inside_temp: f64,
|
||||
}
|
||||
|
||||
impl TryFrom<teslatte::vehicles::ClimateState> for ClimateState {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: teslatte::vehicles::ClimateState) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
inside_temp: value
|
||||
.inside_temp
|
||||
.context("no temperature in climate data")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||
|
|
Loading…
Reference in a new issue