161 lines
4.2 KiB
Rust
161 lines
4.2 KiB
Rust
|
use clap::Parser;
|
||
|
use futures::StreamExt;
|
||
|
use std::path::PathBuf;
|
||
|
|
||
|
mod config;
|
||
|
|
||
|
#[derive(clap::Parser, Debug, Clone)]
|
||
|
#[clap(author, version, about, long_about = None)]
|
||
|
struct Args {
|
||
|
#[command(subcommand)]
|
||
|
command: Commands,
|
||
|
#[clap(long, default_value = "/etc/charge-controller-supervisor/config.json")]
|
||
|
config: PathBuf,
|
||
|
}
|
||
|
|
||
|
#[derive(clap::Subcommand, Debug, Clone)]
|
||
|
enum Commands {
|
||
|
/// Run charge controller server
|
||
|
Watch,
|
||
|
/// Print the default config file
|
||
|
GenerateConfig,
|
||
|
}
|
||
|
|
||
|
mod gauges;
|
||
|
mod pl;
|
||
|
mod tristar;
|
||
|
|
||
|
mod web;
|
||
|
|
||
|
pub const CHARGE_CONTROLLER_LABEL: &str = "charge_controller";
|
||
|
pub const PL_LABEL: &str = "pl_device";
|
||
|
pub const TRISTAR_LABEL: &str = "tristar_device";
|
||
|
|
||
|
#[tokio::main]
|
||
|
async fn main() {
|
||
|
env_logger::builder()
|
||
|
.format_module_path(false)
|
||
|
.format_timestamp(
|
||
|
if std::env::var("LOG_TIMESTAMP").is_ok_and(|v| v == "false") {
|
||
|
None
|
||
|
} else {
|
||
|
Some(env_logger::TimestampPrecision::Seconds)
|
||
|
},
|
||
|
)
|
||
|
.init();
|
||
|
|
||
|
if let Err(e) = run().await {
|
||
|
log::error!("{e:?}");
|
||
|
std::process::exit(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
|
||
|
match args.command {
|
||
|
Commands::Watch => watch(args).await,
|
||
|
Commands::GenerateConfig => {
|
||
|
let config = config::Config::default();
|
||
|
let json = serde_json::to_string_pretty(&config)?;
|
||
|
println!("{json}");
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
|
||
|
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:?}"),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let server = web::rocket();
|
||
|
let server_task = tokio::task::spawn(server.launch());
|
||
|
|
||
|
tokio::select! {
|
||
|
v = controllers.next() => {
|
||
|
match v {
|
||
|
Some(Err(e)) => {
|
||
|
log::error!("{e:?}");
|
||
|
std::process::exit(1);
|
||
|
}
|
||
|
_ => {
|
||
|
log::error!("no controller tasks left???");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
v = server_task => {
|
||
|
log::error!("server exited: {v:#?}");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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));
|
||
|
loop {
|
||
|
timeout.tick().await;
|
||
|
if let Err(e) = controller.refresh().await {
|
||
|
log::warn!("error reading controller {}: {e:?}", controller.name());
|
||
|
}
|
||
|
}
|
||
|
}
|