tesla-charge-controller/charge-controller-supervisor/src/main.rs

161 lines
4.2 KiB
Rust
Raw Normal View History

2024-12-28 18:54:21 +11:00
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());
}
}
}