global config + single api endpoint for car state

This commit is contained in:
Alex Janka 2024-01-14 09:40:34 +11:00
parent 78fdf7a08d
commit b4e7140335
5 changed files with 38 additions and 57 deletions

View file

@ -1,7 +1,18 @@
use std::{path::PathBuf, sync::OnceLock};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::Coords; use crate::types::Coords;
pub(super) static CONFIG_PATH: OnceLock<PathBuf> = OnceLock::new();
static CONFIG: OnceLock<Config> = OnceLock::new();
pub fn access_config<'a>() -> &'a Config {
CONFIG.get_or_init(|| {
ron::from_str(&std::fs::read_to_string(CONFIG_PATH.get().unwrap()).unwrap()).unwrap()
})
}
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Config { pub struct Config {
pub tesla_watch_interval_seconds: u64, pub tesla_watch_interval_seconds: u64,

View file

@ -6,6 +6,7 @@ extern crate rocket;
use api_interface::TeslaInterface; use api_interface::TeslaInterface;
use charge_controllers::pl::Pli; use charge_controllers::pl::Pli;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use config::{access_config, CONFIG_PATH};
use errors::PrintErrors; use errors::PrintErrors;
use std::path::PathBuf; use std::path::PathBuf;
@ -41,7 +42,8 @@ async fn main() {
env_logger::init(); env_logger::init();
let auth_path = args.config_dir.join("auth"); let auth_path = args.config_dir.join("auth");
let config_path = args.config_dir.join("config");
let _ = CONFIG_PATH.set(args.config_dir.join("config"));
let _recorder = metrics_prometheus::install(); let _recorder = metrics_prometheus::install();
@ -54,9 +56,7 @@ async fn main() {
} }
Commands::Watch => { Commands::Watch => {
if let Some(mut interface) = TeslaInterface::load(auth_path).await.some_or_print() { if let Some(mut interface) = TeslaInterface::load(auth_path).await.some_or_print() {
let config: Config = let config = access_config();
ron::from_str(&std::fs::read_to_string(&config_path).unwrap()).unwrap();
// build the channel that takes messages from the webserver thread to the api thread // build the channel that takes messages from the webserver thread to the api thread
let (api_requests, api_receiver) = async_channel::unbounded(); let (api_requests, api_receiver) = async_channel::unbounded();
// and to the pli thread // and to the pli thread
@ -94,7 +94,6 @@ async fn main() {
}; };
let server_handle = server::launch_server(server::ServerState { let server_handle = server::launch_server(server::ServerState {
config: config.clone(),
car_state: interface.state.clone(), car_state: interface.state.clone(),
pl_state, pl_state,
api_requests, api_requests,

View file

@ -11,9 +11,8 @@ use rocket::{
use crate::{ use crate::{
api_interface::InterfaceRequest, api_interface::InterfaceRequest,
charge_controllers::pl::{PlState, PliRequest}, charge_controllers::pl::{PlState, PliRequest},
config::Config,
errors::ServerError, errors::ServerError,
types::{CarState, ChargeState, ClimateState}, types::CarState,
}; };
use self::static_handler::UiStatic; use self::static_handler::UiStatic;
@ -21,7 +20,6 @@ use self::static_handler::UiStatic;
mod static_handler; mod static_handler;
pub struct ServerState { pub struct ServerState {
pub config: Config,
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 api_requests: Sender<InterfaceRequest>, pub api_requests: Sender<InterfaceRequest>,
@ -51,15 +49,7 @@ fn rocket(state: ServerState) -> rocket::Rocket<rocket::Build> {
.mount("/", fileserver) .mount("/", fileserver)
.mount( .mount(
"/", "/",
routes![ routes![home, car_state, regulator_state, flash, metrics, read_ram,],
home,
charge_state,
flash,
climate_state,
metrics,
read_ram,
regulator_state
],
) )
} }
@ -70,29 +60,12 @@ async fn home(state: &State<ServerState>) -> Result<Json<bool>, ServerError> {
.read()? .read()?
.location_data .location_data
.ok_or(ServerError::NoData)?; .ok_or(ServerError::NoData)?;
Ok(Json(location_data.coords.overlaps(&state.config.coords))) Ok(Json(location_data.home))
} }
#[get("/charge-state")] #[get("/car-state")]
async fn charge_state(state: &State<ServerState>) -> Result<Json<ChargeState>, ServerError> { async fn car_state(state: &State<ServerState>) -> Result<Json<CarState>, ServerError> {
Ok(Json( Ok(Json(*state.car_state.read()?))
state
.car_state
.read()?
.charge_state
.ok_or(ServerError::NoData)?,
))
}
#[get("/climate-state")]
async fn climate_state(state: &State<ServerState>) -> Result<Json<ClimateState>, ServerError> {
Ok(Json(
state
.car_state
.read()?
.climate_state
.ok_or(ServerError::NoData)?,
))
} }
#[post("/flash")] #[post("/flash")]

View file

@ -1,9 +1,9 @@
use chrono::DateTime; use chrono::DateTime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::errors::TeslaStateParseError; use crate::{config::access_config, errors::TeslaStateParseError};
#[derive(Default)] #[derive(Default, Clone, Copy, Serialize, Deserialize, Debug)]
pub struct CarState { pub struct CarState {
pub charge_state: Option<ChargeState>, pub charge_state: Option<ChargeState>,
pub location_data: Option<LocationData>, pub location_data: Option<LocationData>,
@ -66,8 +66,8 @@ impl From<teslatte::vehicles::ChargeState> for ChargeState {
#[derive(Clone, Copy, Serialize, Deserialize, Debug)] #[derive(Clone, Copy, Serialize, Deserialize, Debug)]
pub struct LocationData { pub struct LocationData {
pub coords: Coords,
pub gps_as_of: DateTime<chrono::Utc>, pub gps_as_of: DateTime<chrono::Utc>,
pub home: bool,
} }
impl TryFrom<teslatte::vehicles::DriveState> for LocationData { impl TryFrom<teslatte::vehicles::DriveState> for LocationData {
@ -83,7 +83,8 @@ impl TryFrom<teslatte::vehicles::DriveState> for LocationData {
latitude: value.latitude.ok_or(TeslaStateParseError::NoValue)?, latitude: value.latitude.ok_or(TeslaStateParseError::NoValue)?,
longitude: value.longitude.ok_or(TeslaStateParseError::NoValue)?, longitude: value.longitude.ok_or(TeslaStateParseError::NoValue)?,
}; };
Ok(Self { coords, gps_as_of }) let home = coords.overlaps(&access_config().coords);
Ok(Self { gps_as_of, home })
} }
} }

View file

@ -35,23 +35,23 @@
let favicon = document.getElementById("favicon"); let favicon = document.getElementById("favicon");
favicon.setAttribute("href", "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>" + "⏳" + "</text></svg>"); favicon.setAttribute("href", "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>" + "⏳" + "</text></svg>");
fetch(api_url + "/charge-state") fetch(api_url + "/car-state")
.then((response) => response.json()) .then((response) => response.json())
.then((json) => update_charge_state(json)); .then((json) => update_state(json));
fetch(api_url + "/home").then((response) => response.json()).then((response) => update_home(response));
} }
function update_charge_state(charge_state) { function update_state(state) {
let favicon = document.getElementById("favicon"); let favicon = document.getElementById("favicon");
favicon.setAttribute("href", "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>" + get_emoji(charge_state) + "</text></svg>"); favicon.setAttribute("href", "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>" + get_emoji(state.charge_state) + "</text></svg>");
var info_div = document.getElementById("info"); var info_div = document.getElementById("info");
while (info_div.childElementCount > 0) { info_div.removeChild(info_div.firstChild) } while (info_div.childElementCount > 0) { info_div.removeChild(info_div.firstChild) }
var arr = ["Battery " + charge_state.battery_level + "%", "Range: " + (charge_state.battery_range * 1.60934).toFixed(1) + "km", "Charging at " + charge_state.charge_rate.toFixed(1) + " amps", "Charging until battery at " + charge_state.charge_limit_soc + "%", var arr = ["Battery " + state.charge_state.battery_level + "%", "Range: " + (state.charge_state.battery_range * 1.60934).toFixed(1) + "km", "Charging at " + state.charge_state.charge_rate.toFixed(1) + " amps", "Charging until battery at " + state.charge_state.charge_limit_soc + "%",
]; ];
for (line in arr) { for (line in arr) {
el = document.createElement('p'); el = document.createElement('p');
@ -64,22 +64,19 @@
el.appendChild(document.createTextNode("Full charge state:")); el.appendChild(document.createTextNode("Full charge state:"));
state_json = document.createElement('pre'); state_json = document.createElement('pre');
state_json.appendChild(document.createTextNode(JSON.stringify(charge_state, null, '\t'))); state_json.appendChild(document.createTextNode(JSON.stringify(state.charge_state, null, '\t')));
el.appendChild(state_json); el.appendChild(state_json);
info_div.appendChild(el); info_div.appendChild(el);
}
function update_home(response) {
var info_div = document.getElementById("info");
el = document.createElement('p'); home = document.createElement('p');
if (response) { if (state.location_data.home) {
el.appendChild(document.createTextNode("At home")); home.appendChild(document.createTextNode("At home"));
} else { } else {
el.appendChild(document.createTextNode("Not home")); home.appendChild(document.createTextNode("Not home"));
} }
info_div.appendChild(el); info_div.appendChild(home);
} }
function get_emoji(charge_state) { function get_emoji(charge_state) {