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,
|
|
|
|
}
|
|
|
|
|
2024-12-28 20:39:07 +11:00
|
|
|
mod controller;
|
2024-12-28 18:54:21 +11:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2024-12-28 20:39:07 +11:00
|
|
|
let mut map = std::collections::HashMap::new();
|
|
|
|
|
|
|
|
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),
|
2024-12-28 18:54:21 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-28 22:45:31 +11:00
|
|
|
let server = web::rocket(web::ServerState::new(config.primary_charge_controller, map));
|
2024-12-28 18:54:21 +11:00
|
|
|
let server_task = tokio::task::spawn(server.launch());
|
|
|
|
|
|
|
|
tokio::select! {
|
|
|
|
v = controllers.next() => {
|
|
|
|
match v {
|
|
|
|
Some(Err(e)) => {
|
|
|
|
log::error!("{e:?}");
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
log::error!("no controller tasks left???");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
v = server_task => {
|
2024-12-28 22:57:13 +11:00
|
|
|
if let Err(e)=v {
|
|
|
|
log::error!("server exited: {e:#?}");
|
|
|
|
} else {
|
|
|
|
std::process::exit(0);
|
|
|
|
}
|
2024-12-28 18:54:21 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::process::exit(1)
|
|
|
|
}
|
|
|
|
|
2024-12-28 20:39:07 +11:00
|
|
|
async fn run_loop(mut controller: controller::Controller) -> eyre::Result<()> {
|
|
|
|
let mut timeout = tokio::time::interval(controller.timeout_interval());
|
2024-12-28 18:54:21 +11:00
|
|
|
loop {
|
|
|
|
timeout.tick().await;
|
|
|
|
if let Err(e) = controller.refresh().await {
|
|
|
|
log::warn!("error reading controller {}: {e:?}", controller.name());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|