global config + single api endpoint for car state
This commit is contained in:
parent
78fdf7a08d
commit
b4e7140335
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue