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 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)]
pub struct Config {
pub tesla_watch_interval_seconds: u64,

View file

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

View file

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

View file

@ -1,9 +1,9 @@
use chrono::DateTime;
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 charge_state: Option<ChargeState>,
pub location_data: Option<LocationData>,
@ -66,8 +66,8 @@ impl From<teslatte::vehicles::ChargeState> for ChargeState {
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
pub struct LocationData {
pub coords: Coords,
pub gps_as_of: DateTime<chrono::Utc>,
pub home: bool,
}
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)?,
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");
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((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");
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");
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) {
el = document.createElement('p');
@ -64,22 +64,19 @@
el.appendChild(document.createTextNode("Full charge state:"));
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);
info_div.appendChild(el);
}
function update_home(response) {
var info_div = document.getElementById("info");
el = document.createElement('p');
if (response) {
el.appendChild(document.createTextNode("At home"));
home = document.createElement('p');
if (state.location_data.home) {
home.appendChild(document.createTextNode("At home"));
} 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) {