tesla-charge-controller/src/api_interface.rs

281 lines
9.5 KiB
Rust
Raw Normal View History

2024-01-09 11:11:16 +11:00
use metrics::{describe_gauge, gauge, Gauge, Unit};
2024-01-08 12:00:09 +11:00
use serde::{Deserialize, Serialize};
use std::{
path::PathBuf,
sync::{Arc, RwLock},
time::{Duration, Instant},
};
use teslatte::{
auth::{AccessToken, RefreshToken},
vehicles::{Endpoint, GetVehicleData},
2024-01-15 09:37:17 +11:00
FleetApi, FleetVehicleApi,
2024-01-08 12:00:09 +11:00
};
use crate::{errors::*, types::CarState};
2024-01-09 11:11:16 +11:00
struct Metrics {
battery_level: Gauge,
charge_rate: Gauge,
2024-01-09 12:06:18 +11:00
charge_request: Gauge,
2024-01-09 11:11:16 +11:00
inside_temp: Gauge,
2024-01-09 12:06:18 +11:00
outside_temp: Gauge,
battery_heater: Gauge,
2024-01-10 20:30:27 +11:00
tesla_online: Gauge,
2024-01-09 11:11:16 +11:00
}
impl Metrics {
2024-01-10 15:22:28 +11:00
fn new() -> Self {
2024-01-09 11:11:16 +11:00
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");
2024-01-09 12:06:18 +11:00
describe_gauge!("tesla_charge_request", "Requested charge rate");
let charge_request = gauge!("tesla_charge_request");
2024-01-09 11:11:16 +11:00
describe_gauge!("tesla_inside_temp", "Inside temperature");
let inside_temp = gauge!("tesla_inside_temp");
2024-01-09 12:06:18 +11:00
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");
2024-01-10 20:30:27 +11:00
describe_gauge!("tesla_online", "Tesla online");
let tesla_online = gauge!("tesla_online");
2024-01-09 11:11:16 +11:00
2024-01-10 15:22:28 +11:00
Self {
battery_level,
charge_rate,
charge_request,
inside_temp,
outside_temp,
battery_heater,
2024-01-10 20:30:27 +11:00
tesla_online,
2024-01-10 15:22:28 +11:00
}
2024-01-09 11:11:16 +11:00
}
}
2024-01-08 12:00:09 +11:00
pub struct TeslaInterface {
pub state: Arc<RwLock<CarState>>,
api: FleetApi,
vehicle: Box<teslatte::vehicles::VehicleData>,
last_refresh: Instant,
auth_path: PathBuf,
2024-01-09 11:11:16 +11:00
metrics: Metrics,
2024-01-10 11:51:30 +11:00
last_cop_state: String,
2024-01-15 09:37:17 +11:00
last_climate_keeper: String,
last_hvac_auto: String,
2024-01-08 12:00:09 +11:00
}
#[derive(Serialize, Deserialize, Clone)]
struct AuthInfo {
access_token: AccessToken,
refresh_token: Option<RefreshToken>,
}
#[derive(Clone, Copy, Debug)]
2024-01-09 11:50:37 +11:00
// these are the messages that the webserver can send the api thread
pub enum InterfaceRequest {
FlashLights,
}
const KEY_REFRESH_INTERVAL: Duration = Duration::from_secs(6 * 60 * 60);
2024-01-08 12:00:09 +11:00
impl TeslaInterface {
pub async fn load(auth_path: PathBuf) -> Result<Self, AuthLoadError> {
let key: AuthInfo = ron::from_str(&std::fs::read_to_string(&auth_path)?)?;
let mut api = FleetApi::new(key.access_token, key.refresh_token);
api.refresh().await?;
let last_refresh = Instant::now();
2024-01-09 11:33:11 +11:00
info!("Refreshed auth key");
2024-01-08 12:00:09 +11:00
let vehicle = api
.products()
.await?
.into_iter()
.filter_map(|v| match v {
teslatte::products::Product::Vehicle(vehicle) => Some(vehicle),
_ => None,
})
.next()
2024-01-12 08:47:21 +11:00
.ok_or(AuthLoadError::NoVehicles)?;
2024-01-08 12:00:09 +11:00
2024-01-10 15:22:28 +11:00
let metrics = Metrics::new();
2024-01-09 11:11:16 +11:00
2024-01-08 12:00:09 +11:00
let interface = Self {
state: Arc::new(RwLock::new(Default::default())),
api,
last_refresh,
auth_path,
vehicle,
2024-01-09 11:11:16 +11:00
metrics,
2024-01-10 11:51:30 +11:00
last_cop_state: String::new(),
2024-01-15 09:37:17 +11:00
last_climate_keeper: String::new(),
last_hvac_auto: String::new(),
2024-01-08 12:00:09 +11:00
};
interface.save_key()?;
Ok(interface)
}
fn save_key(&self) -> Result<(), SaveError> {
std::fs::write(
self.auth_path.clone(),
ron::ser::to_string(&AuthInfo {
access_token: self.api.access_token.clone(),
refresh_token: self.api.refresh_token.clone(),
})?,
)?;
2024-01-09 11:33:11 +11:00
info!("Auth successfully saved");
2024-01-08 12:00:09 +11:00
Ok(())
}
pub async fn refresh(&mut self) {
2024-01-10 11:51:30 +11:00
info!("refreshing...");
2024-01-08 12:00:09 +11:00
self.refresh_keys().await;
self.refresh_state().await;
}
pub async fn process_request(&mut self, request: InterfaceRequest) {
match request {
InterfaceRequest::FlashLights => {
let _ = self.api.flash_lights(&self.vehicle.vin).await;
}
}
}
2024-01-08 12:00:09 +11:00
async fn refresh_state(&mut self) {
2024-01-15 09:37:17 +11:00
match self.get_state().await {
2024-01-08 12:00:09 +11:00
Ok(new_state) => {
2024-01-10 20:30:27 +11:00
self.metrics.tesla_online.set(1.);
let mut state = self
.state
.write()
.expect("Tesla API state handler panicked!!");
2024-01-08 12:00:09 +11:00
if let Some(new_charge_state) = new_state.charge_state {
2024-01-09 11:11:16 +11:00
self.metrics
.battery_level
.set(new_charge_state.battery_level as f64);
self.metrics.charge_rate.set(new_charge_state.charge_rate);
2024-01-09 12:06:18 +11:00
self.metrics
.charge_request
.set(new_charge_state.charge_current_request as f64);
2024-01-08 12:00:09 +11:00
state.charge_state = Some(new_charge_state);
}
if let Some(new_location_data) = new_state.location_data {
state.location_data = Some(new_location_data);
}
2024-01-09 11:11:16 +11:00
if let Some(new_climate_state) = new_state.climate_state {
self.metrics.inside_temp.set(new_climate_state.inside_temp);
2024-01-09 12:06:18 +11:00
self.metrics
.outside_temp
.set(new_climate_state.outside_temp);
self.metrics
.battery_heater
.set(if new_climate_state.battery_heater {
1.0
} else {
0.0
});
2024-01-09 11:11:16 +11:00
state.climate_state = Some(new_climate_state);
}
2024-01-08 12:00:09 +11:00
}
2024-01-10 20:30:27 +11:00
Err(e) => {
self.metrics.tesla_online.set(0.);
if let RequestError::Teslatte(teslatte::error::TeslatteError::DecodeJsonError {
source: _,
request: _,
body,
}) = &e
{
if body.contains("vehicle is offline or asleep") {
return;
}
}
2024-01-12 09:09:42 +11:00
match e {
RequestError::StdIo(e) => log::error!("Stdio error: {e:?}"),
RequestError::RonSpanned(e) => log::error!("RON parsing error: {e:?}"),
RequestError::Teslatte(teslatte::error::TeslatteError::DecodeJsonError {
2024-01-13 15:14:50 +11:00
source,
2024-01-12 09:09:42 +11:00
request: _,
body,
}) => {
2024-01-13 15:14:50 +11:00
log::error!("Error {source} getting state: {body}")
2024-01-12 09:09:42 +11:00
}
RequestError::Teslatte(e) => log::error!("Teslatte error: {e:?}"),
RequestError::Save(e) => log::error!("Save error: {e:?}"),
}
2024-01-10 20:30:27 +11:00
}
2024-01-08 12:00:09 +11:00
}
}
async fn refresh_keys(&mut self) {
2024-01-09 11:50:37 +11:00
// refresh our Tesla (the company's web servers, not the car) access token
if Instant::now().duration_since(self.last_refresh) >= KEY_REFRESH_INTERVAL {
2024-01-12 08:47:19 +11:00
log::warn!("refreshing keys...");
2024-01-12 09:09:42 +11:00
if self.api.refresh().await.some_or_print().is_some() {
let now = Instant::now();
if self.save_key().some_or_print().is_some() {
self.last_refresh = now;
2024-01-08 12:00:09 +11:00
}
}
}
}
2024-01-15 09:37:17 +11:00
async fn get_state(&mut self) -> Result<CarState, RequestError> {
// Endpoint::VehicleDataCombo or multiple Endpoints in one request
// doesn't seem to reliably get them all,
// so for each endpoint we do a new request
let charge_state = self
.api
.vehicle_data(&GetVehicleData {
vehicle_id: self.vehicle.id,
endpoints: vec![Endpoint::ChargeState].into(),
})
.await?
.charge_state
.map(|v| v.into());
2024-01-08 12:00:09 +11:00
2024-01-15 09:37:17 +11:00
let location_data = self
.api
.vehicle_data(&GetVehicleData {
vehicle_id: self.vehicle.id,
endpoints: vec![Endpoint::LocationData].into(),
})
.await?
.drive_state
.and_then(|v| v.try_into().ok());
2024-01-09 11:11:16 +11:00
2024-01-15 09:37:17 +11:00
let climate_state = self
.api
.vehicle_data(&GetVehicleData {
vehicle_id: self.vehicle.id,
endpoints: vec![Endpoint::ClimateState].into(),
})
.await?
.climate_state
.and_then(|v| {
if self.last_cop_state != v.cabin_overheat_protection {
log::warn!(
"Current cabin overheat protection state: {}",
v.cabin_overheat_protection
);
self.last_cop_state = v.cabin_overheat_protection.clone();
}
if self.last_climate_keeper != v.climate_keeper_mode {
log::warn!("Current climate keeper mode: {}", v.climate_keeper_mode);
self.last_climate_keeper = v.climate_keeper_mode.clone();
}
if self.last_hvac_auto != v.hvac_auto_request {
log::warn!("HVAC auto request set to: {}", v.hvac_auto_request);
self.last_hvac_auto = v.hvac_auto_request.clone();
}
v.try_into().ok()
});
2024-01-08 12:00:09 +11:00
2024-01-15 09:37:17 +11:00
Ok(CarState {
charge_state,
location_data,
climate_state,
})
}
2024-01-08 12:00:09 +11:00
}