use std::time::Duration;
use std::sync::Arc;
use ocpp_service::tls::TlsAcceptor;
use ocpp_service::{Listeners, build_state, config::Config, db, serve};
use tokio::net::TcpListener;
use tokio::sync::watch;
use tracing_subscriber::EnvFilter;
#[tokio::main]
async fn main() {
let json_logs = std::env::var("LOG_FORMAT").is_ok_and(|v| v == "json");
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
if json_logs {
tracing_subscriber::fmt()
.with_env_filter(filter)
.json()
.init();
} else {
tracing_subscriber::fmt().with_env_filter(filter).init();
}
if let Err(e) = run().await {
tracing::error!(error = %e, "fatal");
std::process::exit(1);
}
}
async fn run() -> Result<(), Box<dyn std::error::Error>> {
let cfg = Config::from_env()?;
let db = db::connect(&cfg.database_url, cfg.database_max_connections).await?;
db::migrate(&db).await?;
let stale = db::stations::reset_node(&db, &cfg.node_id).await?;
if stale > 0 {
tracing::info!(
count = stale,
"cleared stale connection state from a previous run"
);
}
let ocpp = match cfg.ocpp_listen {
Some(addr) => Some(TcpListener::bind(addr).await?),
None => None,
};
let ocpp_tls = match (cfg.ocpp_tls_listen, &cfg.tls) {
(Some(addr), Some(settings)) => {
let acceptor = Arc::new(TlsAcceptor::new(settings.clone())?);
spawn_reload_on_sighup(acceptor.clone());
Some((TcpListener::bind(addr).await?, acceptor))
}
_ => None,
};
if ocpp.is_none() && ocpp_tls.is_none() {
return Err(
"no OCPP listener configured (OCPP_LISTEN and OCPP_TLS_LISTEN are both disabled)"
.into(),
);
}
if ocpp_tls.is_some() && cfg.cso_name.is_none() {
tracing::warn!(
"OCPP_CSO_NAME is not set: the organizationName of station certificates is not checked (A00.FR.404)"
);
}
let api = TcpListener::bind(cfg.api_listen).await?;
tracing::info!(ocpp = ?cfg.ocpp_listen, ocpp_tls = ?cfg.ocpp_tls_listen, api = %cfg.api_listen, node = %cfg.node_id, versions = ?cfg.versions, "OCPP service listening");
let (shutdown_tx, shutdown_rx) = watch::channel(false);
let app = build_state(cfg, db, shutdown_rx);
let signal = async move {
shutdown_signal().await;
tracing::info!("shutting down: closing station connections");
let _ = shutdown_tx.send(true);
tokio::time::sleep(Duration::from_millis(500)).await;
};
serve(
app,
Listeners {
ocpp,
ocpp_tls,
api,
},
signal,
)
.await?;
Ok(())
}
fn spawn_reload_on_sighup(acceptor: Arc<TlsAcceptor>) {
#[cfg(unix)]
tokio::spawn(async move {
let Ok(mut hup) = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::hangup())
else {
return;
};
while hup.recv().await.is_some() {
match acceptor.reload() {
Ok(()) => tracing::info!("TLS certificates reloaded"),
Err(e) => {
tracing::error!(error = %e, "TLS reload failed, keeping previous certificates")
}
}
}
});
#[cfg(not(unix))]
let _ = acceptor;
}
async fn shutdown_signal() {
let ctrl_c = async {
let _ = tokio::signal::ctrl_c().await;
};
#[cfg(unix)]
let term = async {
if let Ok(mut s) = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
{
s.recv().await;
}
};
#[cfg(not(unix))]
let term = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {}
_ = term => {}
}
}