use std::time::Duration;
use ocpp_core::OcppVersion;
use serde_json::{Value, json};
use crate::error::AppError;
use crate::hub::{CallFailure, SendError};
use crate::ocpp::{v16, v21, v201};
use crate::state::App;
pub fn validate(version: OcppVersion, action: &str, payload: Value) -> Result<Value, AppError> {
match version {
OcppVersion::V16 => v16::validate_command(action, payload),
OcppVersion::V201 => v201::validate_command(action, payload),
OcppVersion::V21 => v21::validate_command(action, payload),
}
.map_err(|e| {
AppError::BadRequest(format!(
"invalid {action} for OCPP {version}: {} ({})",
e.description, e.code
))
})
}
pub async fn call(
app: &App,
identity: &str,
version: OcppVersion,
action: &str,
payload: Value,
) -> Result<Value, AppError> {
let payload = validate(version, action, payload)?;
let wait = app.cfg.call_timeout * 2 + Duration::from_secs(5);
match app.hub.call(identity, action, payload, wait).await {
Err(SendError::NotConnected) => Err(AppError::NotConnected { node: None }),
Err(SendError::QueueFull) => Err(AppError::Unavailable(
"too many commands queued for this station".into(),
)),
Ok(Ok(response)) => Ok(response),
Ok(Err(CallFailure::Error(e))) => Err(AppError::Upstream(e)),
Ok(Err(CallFailure::InvalidResponse { violation, raw })) => Err(AppError::Upstream(
ocpp_core::RpcError::new(
violation.kind.error_code(),
format!("station response is invalid: {violation}"),
)
.with_details(json!({ "response": raw })),
)),
Ok(Err(CallFailure::Timeout)) => Err(AppError::GatewayTimeout(format!(
"{action} not answered in time"
))),
Ok(Err(CallFailure::Disconnected)) => {
Err(AppError::Unavailable("station disconnected".into()))
}
}
}
pub async fn connected_version(app: &App, identity: &str) -> Result<OcppVersion, AppError> {
if let Some(h) = app.hub.get(identity) {
return Ok(h.version);
}
let station = crate::db::stations::get_by_identity(&app.db, identity)
.await?
.ok_or_else(|| AppError::NotFound(format!("charging station '{identity}' not found")))?;
Err(AppError::NotConnected {
node: station
.connected
.then_some(station.connected_node)
.flatten(),
})
}
pub fn status_of(response: &Value) -> &str {
response.get("status").and_then(Value::as_str).unwrap_or("")
}