use super::super::payloads::{
ChargingSchedulePeriod, ChargingScheduleV1_6, ConfigurationKeyEntry,
DataTransferResponse, GetCompositeScheduleV1_6Response,
GetConfigurationV1_6Response, GetDiagnosticsV1_6Response, to_value,
};
use super::super::{
ChargingRateUnit, ConfigurationKey, ResponseStatus, Result, Simulator,
UiLogLevel, Value, anyhow, now_timestamp, required_string_field,
};
use super::request::{
AvailabilityRequest, CancelReservationRequest,
ClearChargingProfileRequestV1_6, CompositeScheduleRequestV1_6,
DataTransferRequestV1_6, ReserveNowRequestV1_6, SendLocalListRequestV1_6,
SetChargingProfileRequestV1_6, UnlockConnectorRequestV1_6,
};
use crate::sensitive::redact_url_secrets;
impl Simulator {
pub(in crate::simulator) fn configuration_response_v1_6(
&self,
payload: &Value,
) -> GetConfigurationV1_6Response<'_> {
let requested_keys: Option<Vec<String>> =
payload.get("key").and_then(Value::as_array).map(|items| {
items
.iter()
.filter_map(Value::as_str)
.map(ToOwned::to_owned)
.collect()
});
let mut configuration_key = Vec::new();
let mut unknown_key = Vec::new();
if let Some(keys) = requested_keys {
for key in keys {
if let Some(config_key) = ConfigurationKey::parse(&key)
&& let Some(entry) = self.configuration.get(&config_key)
{
configuration_key.push(ConfigurationKeyEntry {
key: config_key.as_str(),
readonly: entry.read_only,
value: configuration_value(config_key, &entry.value),
});
} else {
unknown_key.push(key);
}
}
return GetConfigurationV1_6Response {
configuration_key,
unknown_key,
};
}
for (key, entry) in &self.configuration {
configuration_key.push(ConfigurationKeyEntry {
key: key.as_str(),
readonly: entry.read_only,
value: configuration_value(*key, &entry.value),
});
}
GetConfigurationV1_6Response {
configuration_key,
unknown_key,
}
}
pub(in crate::simulator) fn change_configuration_v1_6(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let key = required_string_field(payload, "key")?;
let value = required_string_field(payload, "value")?;
let Some(configuration_key) = ConfigurationKey::parse(key) else {
return Ok(ResponseStatus::NotSupported);
};
Ok(self.set_configuration_value(configuration_key, value))
}
pub(in crate::simulator) fn change_availability_v1_6(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
self.apply_change_availability(AvailabilityRequest::parse_v1_6(payload)?)
}
pub(in crate::simulator) fn data_transfer_v1_6(
payload: &Value,
) -> Result<Value> {
let request = DataTransferRequestV1_6::parse(payload)?;
let data = request.data.map(Value::String);
Ok(to_value(&DataTransferResponse {
status: ResponseStatus::Accepted.as_str(),
data,
}))
}
pub(in crate::simulator) fn get_diagnostics_v1_6(
&mut self,
payload: &Value,
) -> Result<Value> {
let location = payload
.get("location")
.and_then(Value::as_str)
.ok_or_else(|| anyhow!("location is required."))?;
self.log(
UiLogLevel::Info,
format!(
"Received GetDiagnostics request for {}",
redact_url_secrets(location)
),
);
if !self.supports_file_transfer_location(location) {
return Ok(to_value(&GetDiagnosticsV1_6Response { file_name: None }));
}
self.enqueue_diagnostics_status_notification(
ResponseStatus::Uploading.as_str(),
);
self.enqueue_diagnostics_status_notification(
ResponseStatus::Uploaded.as_str(),
);
let cp_id = self.config.cp_id.as_deref().unwrap_or("unknown");
let filename = format!("diagnostics-{cp_id}.log");
Ok(to_value(&GetDiagnosticsV1_6Response {
file_name: Some(&filename),
}))
}
pub(in crate::simulator) fn reserve_now_v1_6(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = ReserveNowRequestV1_6::parse(payload)?;
self.reserve_connector(request.connector, request.reservation_id)
}
pub(in crate::simulator) fn cancel_reservation_v1_6(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = CancelReservationRequest::parse_v1_6(payload)?;
self.cancel_reservation(request.reservation_id)
}
pub(in crate::simulator) fn unlock_connector_v1_6(
&self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = UnlockConnectorRequestV1_6::parse(payload)?;
let Some(state) = self.connectors.get(&request.connector) else {
return Ok(ResponseStatus::NotSupported);
};
if state.transaction.is_some() {
Ok(ResponseStatus::UnlockFailed)
} else {
Ok(ResponseStatus::Unlocked)
}
}
pub(in crate::simulator) fn send_local_list_v1_6(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = SendLocalListRequestV1_6::parse(payload)?;
Ok(self.apply_local_list_version(request.list_version))
}
pub(in crate::simulator) fn set_charging_profile_v1_6(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = SetChargingProfileRequestV1_6::parse(payload)?;
self.apply_set_charging_profile(request.connector, &request.profile)
}
pub(in crate::simulator) fn clear_charging_profile_v1_6(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = ClearChargingProfileRequestV1_6::parse(payload)?;
let Some(targets) = self.clear_profile_targets(request.connector) else {
return Ok(ResponseStatus::Unknown);
};
let profile_id = request.profile_id;
let purpose = request.purpose.as_deref();
let stack_level = request.stack_level;
Ok(self.clear_charging_profiles_matching(targets, |profile| {
profile_id.is_none_or(|value| {
profile.get("chargingProfileId").and_then(Value::as_i64) == Some(value)
}) && purpose.is_none_or(|value| {
profile
.get("chargingProfilePurpose")
.and_then(Value::as_str)
== Some(value)
}) && stack_level.is_none_or(|value| {
profile.get("stackLevel").and_then(Value::as_i64) == Some(value)
})
}))
}
pub(in crate::simulator) fn get_composite_schedule_v1_6(
&self,
payload: &Value,
) -> Result<Value> {
let request = CompositeScheduleRequestV1_6::parse(payload)?;
let charging_rate_unit = request
.charging_rate_unit
.as_deref()
.map(|value| {
ChargingRateUnit::parse(value)
.ok_or_else(|| anyhow!("chargingRateUnit must be A or W."))
})
.transpose()?
.unwrap_or(ChargingRateUnit::W);
let Some(state) = self.connectors.get(&request.connector) else {
return Ok(to_value(&GetCompositeScheduleV1_6Response {
status: ResponseStatus::Rejected.as_str(),
connector_id: None,
schedule_start: None,
charging_schedule: None,
}));
};
let Some(limit) = state.offered_limit else {
return Ok(to_value(&GetCompositeScheduleV1_6Response {
status: ResponseStatus::Rejected.as_str(),
connector_id: None,
schedule_start: None,
charging_schedule: None,
}));
};
let timestamp = now_timestamp();
Ok(to_value(&GetCompositeScheduleV1_6Response {
status: ResponseStatus::Accepted.as_str(),
connector_id: Some(request.connector),
schedule_start: Some(×tamp),
charging_schedule: Some(ChargingScheduleV1_6 {
duration: request.duration,
charging_rate_unit: charging_rate_unit.as_str(),
charging_schedule_period: vec![ChargingSchedulePeriod {
start_period: 0,
limit,
}],
}),
}))
}
}
fn configuration_value(key: ConfigurationKey, value: &str) -> Option<&str> {
if matches!(
key,
ConfigurationKey::AuthorizationKey | ConfigurationKey::BasicAuthPassword
) {
None
} else {
Some(value)
}
}