use ocpp16::{
ChargingProfile, ChargingProfileKindType, ChargingRateUnitType, ChargingSchedule,
RecurrencyKindType,
};
use serde_json::{Value, json};
use super::model::{
self, Kind, Period, Profile, Purpose, Recurrency, RuleViolation, Schedule, Unit,
};
pub fn schedule(s: &ChargingSchedule) -> Schedule {
Schedule {
start: s.start_schedule,
duration: s.duration,
unit: match s.charging_rate_unit {
ChargingRateUnitType::A => Unit::A,
ChargingRateUnitType::W => Unit::W,
},
periods: s
.charging_schedule_period
.iter()
.map(|p| Period {
start: p.start_period,
limit: Some(p.limit),
phases: p.number_phases,
})
.collect(),
}
}
pub fn normalize(connector_id: i64, p: &ChargingProfile) -> Profile {
Profile {
id: p.charging_profile_id,
evse: connector_id,
purpose: Purpose::parse(p.charging_profile_purpose.as_str())
.expect("1.6 purposes are known"),
stack_level: p.stack_level,
kind: match p.charging_profile_kind {
ChargingProfileKindType::Absolute => Kind::Absolute,
ChargingProfileKindType::Recurring => Kind::Recurring,
ChargingProfileKindType::Relative => Kind::Relative,
},
recurrency: p.recurrency_kind.map(|r| match r {
RecurrencyKindType::Daily => Recurrency::Daily,
RecurrencyKindType::Weekly => Recurrency::Weekly,
}),
valid_from: p.valid_from,
valid_to: p.valid_to,
transaction: p.transaction_id.map(|t| t.to_string()),
schedule: schedule(&p.charging_schedule),
dyn_updated_at: None,
}
}
pub fn decode(v: &Value) -> Result<ChargingProfile, String> {
ocpp_core::decode_value(v.clone()).map_err(|e| e.to_string())
}
pub fn prepare(
connector_id: i64,
mut p: ChargingProfile,
active_tx: Option<&str>,
) -> Result<(Profile, Value), RuleViolation> {
if p.charging_profile_purpose.as_str() == "TxProfile" {
let Some(active) = active_tx else {
return Err(RuleViolation::new(
"1.6 §3.13.1",
"a TxProfile requires an active transaction on the connector",
));
};
let active: i64 = active.parse().map_err(|_| {
RuleViolation::new("1.6 §5.16", "active transaction id is not an integer")
})?;
match p.transaction_id {
None => p.transaction_id = Some(active),
Some(t) if t != active => {
return Err(RuleViolation::new(
"1.6 §5.16",
format!(
"transactionId {t} is not the active transaction {active} of the connector"
),
));
}
Some(_) => {}
}
}
let profile = normalize(connector_id, &p);
model::check_structure(&profile)?;
let payload = json!({ "connectorId": connector_id, "csChargingProfiles": p });
Ok((profile, payload))
}
pub fn check_remote_start_profile(p: &ChargingProfile) -> Result<(), RuleViolation> {
if p.charging_profile_purpose.as_str() != "TxProfile" || p.transaction_id.is_some() {
return Err(RuleViolation::new(
"1.6 §5.16.2",
"the chargingProfile of RemoteStartTransaction must be a TxProfile without transactionId",
));
}
Ok(())
}