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

207 lines
6.2 KiB
Rust

#![feature(let_chains)]
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 controller;
mod gauges;
mod storage;
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 mut config = config::Config::default();
config
.charge_controllers
.push(config::ChargeControllerConfig {
name: String::from("tcp"),
transport: config::Transport::Tcp {
ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 1, 102)),
port: 420,
},
watch_interval_seconds: 0,
variant: config::ChargeControllerVariant::Tristar,
follow_primary: false,
});
config
.charge_controllers
.push(config::ChargeControllerConfig {
name: String::from("serial"),
transport: config::Transport::Serial {
port: "/dev/someport".to_string(),
baud_rate: 69,
},
watch_interval_seconds: 0,
variant: config::ChargeControllerVariant::Tristar,
follow_primary: false,
});
let config = config::ConfigStorage::from_latest(config);
let json = serde_json::to_string_pretty(&config)?;
println!("{json}");
Ok(())
}
}
}
async fn watch(args: Args) -> eyre::Result<()> {
let _w = config::init_config(&args.config);
let (storage, follow_voltage_tx, mut controller_tasks) = {
let config = config::access_config().await;
if config
.charge_controllers
.iter()
.any(|cc| cc.follow_primary && cc.name == config.primary_charge_controller)
{
return Err(eyre::eyre!(
"primary charge controller is set to follow primary!"
));
}
let mut controllers = Vec::new();
let mut map = std::collections::HashMap::new();
let mut follow_voltage_tx = Vec::new();
for config in &config.charge_controllers {
let n = config.name.clone();
match controller::Controller::new(config.clone()).await {
Ok((v, voltage_tx)) => {
map.insert(n, v.get_data_ptr());
controllers.push(v);
if let Some(voltage_tx) = voltage_tx {
follow_voltage_tx.push(voltage_tx);
}
}
Err(e) => log::error!("couldn't connect to {}: {e:?}", n),
}
}
let follow_voltage_tx = controller::MultiTx(follow_voltage_tx);
if let Some(primary) = controllers
.iter_mut()
.find(|c| c.name() == config.primary_charge_controller)
{
primary.set_tx_to_secondary(follow_voltage_tx.clone());
}
drop(config);
let controller_tasks = futures::stream::FuturesUnordered::new();
for controller in controllers {
controller_tasks.push(run_loop(controller));
}
(
storage::AllControllers::new(map),
follow_voltage_tx,
controller_tasks,
)
};
let server = web::rocket(web::ServerState::new(
&config::access_config().await.primary_charge_controller,
storage,
follow_voltage_tx,
));
let server_task = tokio::task::spawn(server.launch());
tokio::select! {
v = controller_tasks.next() => {
match v {
Some(Err(e)) => {
log::error!("{e:?}");
}
_ => {
log::error!("no controller tasks left???");
}
}
}
v = server_task => {
if let Err(e)=v {
log::error!("server exited: {e:#?}");
std::process::exit(1);
} else {
std::process::exit(0);
}
}
}
std::process::exit(1)
}
async fn run_loop(mut controller: controller::Controller) -> eyre::Result<()> {
let mut timeout = tokio::time::interval(controller.timeout_interval());
loop {
if let Some(rx) = controller.get_rx() {
tokio::select! {
_ = timeout.tick() => {
do_refresh(&mut controller).await;
}
Some(command) = rx.recv() => {
if let Err(e) = controller.process_command(command).await {
log::error!("controller {} failed to process command: {e}", controller.name());
}
}
}
} else {
timeout.tick().await;
do_refresh(&mut controller).await;
}
}
}
async fn do_refresh(controller: &mut controller::Controller) {
if let Err(e) = controller.refresh().await {
log::warn!("error reading controller {}: {e:?}", controller.name());
}
}