supervisor: web
This commit is contained in:
parent
2c7aa8641c
commit
70c9188f55
6 changed files with 176 additions and 112 deletions
|
@ -8,34 +8,16 @@ pub struct Config {
|
|||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub enum ChargeControllerConfig {
|
||||
Pl {
|
||||
serial_port: String,
|
||||
baud_rate: u32,
|
||||
timeout_milliseconds: u64,
|
||||
watch_interval_seconds: u64,
|
||||
},
|
||||
Tristar {
|
||||
serial_port: String,
|
||||
baud_rate: u32,
|
||||
watch_interval_seconds: u64,
|
||||
},
|
||||
pub struct ChargeControllerConfig {
|
||||
pub name: String,
|
||||
pub serial_port: String,
|
||||
pub baud_rate: u32,
|
||||
pub watch_interval_seconds: u64,
|
||||
pub variant: ChargeControllerVariant,
|
||||
}
|
||||
|
||||
impl ChargeControllerConfig {
|
||||
pub fn interval(&self) -> u64 {
|
||||
match self {
|
||||
ChargeControllerConfig::Pl {
|
||||
serial_port: _,
|
||||
baud_rate: _,
|
||||
timeout_milliseconds: _,
|
||||
watch_interval_seconds,
|
||||
}
|
||||
| ChargeControllerConfig::Tristar {
|
||||
serial_port: _,
|
||||
baud_rate: _,
|
||||
watch_interval_seconds,
|
||||
} => *watch_interval_seconds,
|
||||
}
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub enum ChargeControllerVariant {
|
||||
Tristar,
|
||||
Pl { timeout_milliseconds: u64 },
|
||||
}
|
||||
|
|
80
charge-controller-supervisor/src/controller.rs
Normal file
80
charge-controller-supervisor/src/controller.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
pub struct Controller {
|
||||
name: String,
|
||||
interval: std::time::Duration,
|
||||
inner: ControllerInner,
|
||||
data: std::sync::Arc<tokio::sync::RwLock<CommonData>>,
|
||||
}
|
||||
|
||||
#[derive(Default, serde::Serialize, Clone)]
|
||||
pub struct CommonData {
|
||||
pub battery_voltage: f64,
|
||||
pub target_voltage: f64,
|
||||
pub battery_temp: f64,
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
pub fn new(config: crate::config::ChargeControllerConfig) -> eyre::Result<Self> {
|
||||
let inner = match config.variant {
|
||||
crate::config::ChargeControllerVariant::Tristar => ControllerInner::Tristar(
|
||||
crate::tristar::Tristar::new(config.serial_port, config.baud_rate)?,
|
||||
),
|
||||
crate::config::ChargeControllerVariant::Pl {
|
||||
timeout_milliseconds,
|
||||
} => ControllerInner::Pl(crate::pl::Pli::new(
|
||||
config.serial_port,
|
||||
config.baud_rate,
|
||||
timeout_milliseconds,
|
||||
)?),
|
||||
};
|
||||
|
||||
let data = CommonData::default();
|
||||
|
||||
let data = std::sync::Arc::new(tokio::sync::RwLock::new(data));
|
||||
|
||||
Ok(Self {
|
||||
name: config.name,
|
||||
interval: std::time::Duration::from_secs(config.watch_interval_seconds),
|
||||
inner,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_data_ptr(&self) -> std::sync::Arc<tokio::sync::RwLock<CommonData>> {
|
||||
self.data.clone()
|
||||
}
|
||||
|
||||
pub async fn refresh(&mut self) -> eyre::Result<()> {
|
||||
let data = self.inner.refresh().await?;
|
||||
*self.data.write().await = data;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn timeout_interval(&self) -> std::time::Duration {
|
||||
self.interval
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ControllerInner {
|
||||
Pl(crate::pl::Pli),
|
||||
Tristar(crate::tristar::Tristar),
|
||||
}
|
||||
|
||||
impl ControllerInner {
|
||||
pub async fn refresh(&mut self) -> eyre::Result<CommonData> {
|
||||
match self {
|
||||
ControllerInner::Pl(pli) => {
|
||||
let pl_data = pli.refresh()?;
|
||||
Ok(pl_data)
|
||||
}
|
||||
ControllerInner::Tristar(tristar) => {
|
||||
let tristar_data = tristar.refresh().await?;
|
||||
Ok(tristar_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ enum Commands {
|
|||
GenerateConfig,
|
||||
}
|
||||
|
||||
mod controller;
|
||||
mod gauges;
|
||||
mod pl;
|
||||
mod tristar;
|
||||
|
@ -50,56 +51,6 @@ async fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
enum Controller {
|
||||
Pl(pl::Pli),
|
||||
Tristar(tristar::Tristar),
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
async fn refresh(&mut self) -> eyre::Result<()> {
|
||||
match self {
|
||||
Controller::Pl(pli) => {
|
||||
pli.refresh()?;
|
||||
}
|
||||
Controller::Tristar(tristar) => {
|
||||
tristar.refresh().await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
match self {
|
||||
Controller::Pl(pli) => pli.name().to_owned(),
|
||||
Controller::Tristar(tristar) => tristar.name().to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl config::ChargeControllerConfig {
|
||||
fn connect(&self) -> eyre::Result<Controller> {
|
||||
match self {
|
||||
config::ChargeControllerConfig::Pl {
|
||||
serial_port,
|
||||
baud_rate,
|
||||
timeout_milliseconds,
|
||||
watch_interval_seconds: _,
|
||||
} => {
|
||||
let pl = pl::Pli::new(serial_port.to_owned(), *baud_rate, *timeout_milliseconds)?;
|
||||
Ok(Controller::Pl(pl))
|
||||
}
|
||||
config::ChargeControllerConfig::Tristar {
|
||||
serial_port,
|
||||
baud_rate,
|
||||
watch_interval_seconds: _,
|
||||
} => {
|
||||
let tristar = tristar::Tristar::new(serial_port.to_owned(), *baud_rate)?;
|
||||
Ok(Controller::Tristar(tristar))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn run() -> eyre::Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
|
@ -118,15 +69,20 @@ async fn watch(args: Args) -> eyre::Result<()> {
|
|||
let config: config::Config = serde_json::from_reader(std::fs::File::open(args.config)?)?;
|
||||
|
||||
let mut controllers = futures::stream::FuturesUnordered::new();
|
||||
let mut map = std::collections::HashMap::new();
|
||||
|
||||
for controller in config.charge_controllers {
|
||||
match controller.connect() {
|
||||
Ok(v) => controllers.push(run_loop(v, controller.interval())),
|
||||
Err(e) => log::error!("couldn't connect to {controller:?}: {e:?}"),
|
||||
for config in config.charge_controllers {
|
||||
let n = config.name.clone();
|
||||
match controller::Controller::new(config) {
|
||||
Ok(v) => {
|
||||
map.insert(n, v.get_data_ptr());
|
||||
controllers.push(run_loop(v));
|
||||
}
|
||||
Err(e) => log::error!("couldn't connect to {}: {e:?}", n),
|
||||
}
|
||||
}
|
||||
|
||||
let server = web::rocket();
|
||||
let server = web::rocket(web::ServerState::new(map));
|
||||
let server_task = tokio::task::spawn(server.launch());
|
||||
|
||||
tokio::select! {
|
||||
|
@ -149,8 +105,8 @@ async fn watch(args: Args) -> eyre::Result<()> {
|
|||
std::process::exit(1)
|
||||
}
|
||||
|
||||
async fn run_loop(mut controller: Controller, timeout_interval: u64) -> eyre::Result<()> {
|
||||
let mut timeout = tokio::time::interval(std::time::Duration::from_secs(timeout_interval));
|
||||
async fn run_loop(mut controller: controller::Controller) -> eyre::Result<()> {
|
||||
let mut timeout = tokio::time::interval(controller.timeout_interval());
|
||||
loop {
|
||||
timeout.tick().await;
|
||||
if let Err(e) = controller.refresh().await {
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
use std::{
|
||||
io::Write,
|
||||
sync::{Arc, RwLock},
|
||||
time::Duration,
|
||||
};
|
||||
use std::{io::Write, time::Duration};
|
||||
|
||||
use chrono::Timelike;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -14,7 +10,7 @@ use crate::gauges::{
|
|||
};
|
||||
|
||||
pub struct Pli {
|
||||
pub state: Arc<RwLock<PlState>>,
|
||||
// pub state: Arc<RwLock<PlState>>,
|
||||
port_name: String,
|
||||
port: Box<dyn SerialPort>,
|
||||
}
|
||||
|
@ -121,13 +117,13 @@ impl Pli {
|
|||
.open()?;
|
||||
|
||||
Ok(Self {
|
||||
state: Arc::new(RwLock::new(Default::default())),
|
||||
// state: Arc::new(RwLock::new(Default::default())),
|
||||
port_name: serial_port,
|
||||
port,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) -> eyre::Result<()> {
|
||||
pub fn refresh(&mut self) -> eyre::Result<crate::controller::CommonData> {
|
||||
let new_state = self.read_state()?;
|
||||
BATTERY_VOLTAGE
|
||||
.with_label_values(&[&self.port_name])
|
||||
|
@ -150,9 +146,11 @@ impl Pli {
|
|||
|
||||
set_regulator_gauges(new_state.regulator_state, &self.port_name);
|
||||
|
||||
*self.state.write().expect("PLI state handler panicked!!") = new_state;
|
||||
|
||||
Ok(())
|
||||
Ok(crate::controller::CommonData {
|
||||
battery_voltage: new_state.battery_voltage,
|
||||
target_voltage: new_state.target_voltage,
|
||||
battery_temp: new_state.battery_temp,
|
||||
})
|
||||
}
|
||||
|
||||
#[expect(dead_code, reason = "writing settings is not yet implemented")]
|
||||
|
@ -254,10 +252,6 @@ impl Pli {
|
|||
Err(eyre::eyre!("read error: result is {}", buf[0]))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.port_name
|
||||
}
|
||||
}
|
||||
|
||||
enum PlRamAddress {
|
||||
|
|
|
@ -42,7 +42,6 @@ impl Scaling {
|
|||
}
|
||||
|
||||
pub struct Tristar {
|
||||
state: TristarState,
|
||||
port_name: String,
|
||||
modbus: tokio_modbus::client::Context,
|
||||
charge_state_gauges: ChargeStateGauges,
|
||||
|
@ -241,7 +240,6 @@ impl Tristar {
|
|||
let modbus = tokio_modbus::client::rtu::attach_slave(modbus_serial, slave);
|
||||
let charge_state_gauges = ChargeStateGauges::new(&serial_port);
|
||||
Ok(Self {
|
||||
state: Default::default(),
|
||||
port_name: serial_port,
|
||||
modbus,
|
||||
charge_state_gauges,
|
||||
|
@ -249,7 +247,7 @@ impl Tristar {
|
|||
})
|
||||
}
|
||||
|
||||
pub async fn refresh(&mut self) -> eyre::Result<()> {
|
||||
pub async fn refresh(&mut self) -> eyre::Result<crate::controller::CommonData> {
|
||||
let new_state = self.get_data().await?;
|
||||
self.consecutive_errors = 0;
|
||||
BATTERY_VOLTAGE
|
||||
|
@ -287,13 +285,12 @@ impl Tristar {
|
|||
.set(new_state.tristar_open_circuit_voltage);
|
||||
|
||||
self.charge_state_gauges.set(new_state.charge_state);
|
||||
self.state = new_state;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.port_name
|
||||
Ok(crate::controller::CommonData {
|
||||
battery_voltage: new_state.battery_voltage,
|
||||
target_voltage: new_state.target_voltage,
|
||||
battery_temp: f64::from(new_state.battery_temp),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_data(&mut self) -> eyre::Result<TristarState> {
|
||||
|
|
|
@ -1,7 +1,60 @@
|
|||
use rocket::{get, routes};
|
||||
use rocket::{get, routes, serde::json::Json, State};
|
||||
|
||||
pub fn rocket() -> rocket::Rocket<rocket::Build> {
|
||||
rocket::build().mount("/", routes![metrics,])
|
||||
pub struct ServerState {
|
||||
map: std::collections::HashMap<
|
||||
String,
|
||||
std::sync::Arc<tokio::sync::RwLock<crate::controller::CommonData>>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl ServerState {
|
||||
pub fn new(
|
||||
map: std::collections::HashMap<
|
||||
String,
|
||||
std::sync::Arc<tokio::sync::RwLock<crate::controller::CommonData>>,
|
||||
>,
|
||||
) -> Self {
|
||||
Self { map }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rocket(state: ServerState) -> rocket::Rocket<rocket::Build> {
|
||||
rocket::build()
|
||||
.manage(state)
|
||||
.mount("/", routes![metrics, interfaces, all_interfaces, interface])
|
||||
}
|
||||
|
||||
#[get("/interfaces")]
|
||||
fn interfaces(state: &State<ServerState>) -> Json<Vec<String>> {
|
||||
Json(state.map.keys().cloned().collect())
|
||||
}
|
||||
|
||||
#[get("/interfaces/data")]
|
||||
async fn all_interfaces(
|
||||
state: &State<ServerState>,
|
||||
) -> Json<Vec<(String, crate::controller::CommonData)>> {
|
||||
let mut data = Vec::new();
|
||||
|
||||
for (k, v) in &state.map {
|
||||
data.push((k.clone(), v.read().await.clone()));
|
||||
}
|
||||
|
||||
Json(data)
|
||||
}
|
||||
|
||||
#[get("/interface/<name>")]
|
||||
async fn interface(
|
||||
name: &str,
|
||||
state: &State<ServerState>,
|
||||
) -> Result<Json<crate::controller::CommonData>, ServerError> {
|
||||
let data = state
|
||||
.map
|
||||
.get(name)
|
||||
.ok_or(ServerError::NotFound)?
|
||||
.read()
|
||||
.await
|
||||
.clone();
|
||||
Ok(Json(data))
|
||||
}
|
||||
|
||||
#[get("/metrics")]
|
||||
|
@ -14,6 +67,7 @@ fn metrics() -> Result<String, ServerError> {
|
|||
|
||||
enum ServerError {
|
||||
Prometheus,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl From<prometheus::Error> for ServerError {
|
||||
|
@ -25,6 +79,7 @@ impl From<prometheus::Error> for ServerError {
|
|||
impl<'a> rocket::response::Responder<'a, 'a> for ServerError {
|
||||
fn respond_to(self, _: &'a rocket::Request<'_>) -> rocket::response::Result<'a> {
|
||||
Err(match self {
|
||||
Self::NotFound => rocket::http::Status::NotFound,
|
||||
ServerError::Prometheus => rocket::http::Status::InternalServerError,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue