use super::super::payloads::*;
use super::super::*;
use super::request::{
AvailabilityRequest, CancelReservationRequest, CompositeScheduleRequestV1_6,
ReserveNowRequestV1_6, SendLocalListRequestV1_6,
SetChargingProfileRequestV1_6, UnlockConnectorRequestV1_6,
};
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((stored_key, entry)) =
self.configuration.get_key_value(key.as_str())
{
configuration_key.push(ConfigurationKeyEntry {
key: stored_key,
readonly: entry.read_only,
value: &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,
readonly: entry.read_only,
value: &entry.value,
});
}
GetConfigurationV1_6Response {
configuration_key,
unknown_key,
}
}
pub(in crate::simulator) fn change_configuration_v1_6(
&mut self,
payload: &Value,
) -> ResponseStatus {
let key = payload.get("key").and_then(Value::as_str).unwrap_or("");
let value = payload.get("value").and_then(Value::as_str).unwrap_or("");
if key.is_empty() {
return ResponseStatus::Rejected;
}
if !self.configuration.contains_key(key) {
return ResponseStatus::NotSupported;
}
self.set_configuration_value(key, value)
}
pub(in crate::simulator) fn change_availability_v1_6(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = AvailabilityRequest::parse_v1_6(payload)?;
let Some(target_status) = request.target_status else {
return Ok(ResponseStatus::Rejected);
};
let targets: Vec<u16> = if request.connector.unwrap_or(0) == 0 {
self.connectors.keys().copied().collect()
} else if let Some(connector) = request.connector
&& self.connectors.contains_key(&connector)
{
vec![connector]
} else {
return Ok(ResponseStatus::Rejected);
};
let mut scheduled = false;
let mut changed = Vec::new();
for connector in targets {
let has_active_tx = self
.connectors
.get(&connector)
.and_then(|item| item.transaction.as_ref())
.is_some();
if has_active_tx && target_status == ConnectorStatus::Unavailable {
self.schedule_availability_status(connector, target_status)?;
scheduled = true;
continue;
}
self.apply_availability_status(connector, target_status)?;
changed.push(connector);
}
for connector in changed {
self.enqueue_status_notification(connector)?;
}
self.emit_snapshot();
if scheduled {
Ok(ResponseStatus::Scheduled)
} else {
Ok(ResponseStatus::Accepted)
}
}
pub(in crate::simulator) fn data_transfer_v1_6(
&self,
payload: &Value,
) -> Value {
if payload.get("vendorId").and_then(Value::as_str).is_none() {
return to_value(&DataTransferResponse {
status: ResponseStatus::UnknownVendorId.as_str(),
data: None,
});
}
let data = payload
.get("data")
.and_then(Value::as_str)
.map(|d| Value::String(d.to_owned()));
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 {}", location),
);
self.enqueue_diagnostics_status_notification("Uploading");
self.enqueue_diagnostics_status_notification("Uploaded");
let filename = format!("diagnostics-{}.log", self.config.cp_id);
Ok(to_value(&GetDiagnosticsV1_6Response {
file_name: &filename,
}))
}
pub(in crate::simulator) fn update_firmware_v1_6(
&mut self,
payload: &Value,
) -> Result<()> {
let location = payload
.get("location")
.and_then(Value::as_str)
.ok_or_else(|| anyhow!("location is required."))?;
if payload
.get("retrieveDate")
.and_then(Value::as_str)
.is_none()
{
return Err(anyhow!("retrieveDate is required."));
}
self.log(
UiLogLevel::Info,
format!("Received UpdateFirmware request from {}", location),
);
self.enqueue_firmware_status_notification("Downloading", None);
self.enqueue_firmware_status_notification("Downloaded", None);
self.enqueue_firmware_status_notification("Installing", None);
self.enqueue_firmware_status_notification("Installed", None);
Ok(())
}
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 reserve_connector(
&mut self,
requested_connector: u16,
reservation_id: i64,
) -> Result<ResponseStatus> {
let connector = if requested_connector == 0 {
let mut chosen = None;
for (connector_id, state) in &self.connectors {
let is_reserved = self.connector_has_reservation(*connector_id);
let is_available = !matches!(
state.status,
ConnectorStatus::Unavailable | ConnectorStatus::Faulted
);
if state.transaction.is_none() && !is_reserved && is_available {
chosen = Some(*connector_id);
break;
}
}
let Some(connector_id) = chosen else {
return Ok(ResponseStatus::Occupied);
};
connector_id
} else if self.connectors.contains_key(&requested_connector) {
requested_connector
} else {
return Ok(ResponseStatus::Rejected);
};
if self.reservations.contains_key(&reservation_id) {
return Ok(ResponseStatus::Rejected);
}
if self.connector_has_reservation(connector) {
return Ok(ResponseStatus::Occupied);
}
{
let state = self.connector_mut(connector)?;
if state.transaction.is_some() {
return Ok(ResponseStatus::Occupied);
}
if state.status == ConnectorStatus::Unavailable
|| state.status == ConnectorStatus::Faulted
{
return Ok(ResponseStatus::Unavailable);
}
state.status = ConnectorStatus::Reserved;
}
self.reservations.insert(reservation_id, connector);
self.enqueue_status_notification(connector)?;
self.emit_snapshot();
Ok(ResponseStatus::Accepted)
}
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 cancel_reservation(
&mut self,
reservation_id: i64,
) -> Result<ResponseStatus> {
let Some(connector) = self.reservations.remove(&reservation_id) else {
return Ok(ResponseStatus::Rejected);
};
let has_active_tx = self
.connectors
.get(&connector)
.and_then(|item| item.transaction.as_ref())
.is_some();
if !has_active_tx {
let state = self.connector_mut(connector)?;
state.status = ConnectorStatus::Available;
self.enqueue_status_notification(connector)?;
}
self.emit_snapshot();
Ok(ResponseStatus::Accepted)
}
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)?;
self.local_auth_list_version = request.list_version;
Ok(ResponseStatus::Accepted)
}
pub(in crate::simulator) fn set_charging_profile_v1_6(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = SetChargingProfileRequestV1_6::parse(payload)?;
let targets: Vec<u16> = if request.connector == 0 {
self.connectors.keys().copied().collect()
} else if self.connectors.contains_key(&request.connector) {
vec![request.connector]
} else {
return Ok(ResponseStatus::Rejected);
};
let limit = Self::extract_profile_limit(&request.profile);
for target in targets {
self
.charging_profiles
.insert(target, request.profile.clone());
if let Some(limit_value) = limit {
self.set_offered_limit(target, Some(limit_value))?;
self.apply_charging_profile_state(target)?;
}
}
Ok(ResponseStatus::Accepted)
}
pub(in crate::simulator) fn clear_charging_profile_v1_6(
&mut self,
payload: &Value,
) -> ResponseStatus {
let targets = match self.clear_profile_targets(payload, "connectorId") {
Some(value) => value,
None => return ResponseStatus::Unknown,
};
let profile_id = payload.get("id").and_then(Value::as_i64);
let purpose = payload
.get("chargingProfilePurpose")
.and_then(Value::as_str);
let stack_level = payload.get("stackLevel").and_then(Value::as_i64);
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 clear_profile_targets(
&self,
payload: &Value,
field: &str,
) -> Option<Vec<u16>> {
match optional_u16_field(payload, field) {
Ok(Some(0)) | Ok(None) => Some(self.connectors.keys().copied().collect()),
Ok(Some(connector)) => {
if self.connectors.contains_key(&connector) {
Some(vec![connector])
} else {
None
}
}
Err(_) => None,
}
}
pub(in crate::simulator) fn clear_charging_profiles_matching<F>(
&mut self,
targets: Vec<u16>,
matches_profile: F,
) -> ResponseStatus
where
F: Fn(&Value) -> bool,
{
let mut cleared = Vec::new();
for connector in targets {
let should_remove = self
.charging_profiles
.get(&connector)
.map(&matches_profile)
.unwrap_or(false);
if should_remove {
self.charging_profiles.remove(&connector);
cleared.push(connector);
}
}
if cleared.is_empty() {
return ResponseStatus::Unknown;
}
for connector in cleared {
let _ = self.set_offered_limit(connector, None);
let _ = self.apply_charging_profile_state(connector);
}
ResponseStatus::Accepted
}
pub(in crate::simulator) fn get_composite_schedule_v1_6(
&self,
payload: &Value,
) -> Result<Value> {
let request = CompositeScheduleRequestV1_6::parse(payload)?;
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 charging_rate_unit =
request.charging_rate_unit.as_deref().unwrap_or("W");
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_schedule_period: vec![ChargingSchedulePeriod {
start_period: 0,
limit,
}],
}),
}))
}
pub(in crate::simulator) fn extract_profile_limit(
profile: &Value,
) -> Option<f64> {
let path_1 = profile
.get("chargingSchedule")
.and_then(Value::as_object)
.and_then(|value| value.get("chargingSchedulePeriod"))
.and_then(Value::as_array)
.and_then(|value| value.first())
.and_then(|value| value.get("limit"));
if let Some(limit) = path_1 {
return Self::extract_limit_value(limit);
}
let path_2 = profile
.get("chargingSchedule")
.and_then(Value::as_array)
.and_then(|value| value.first())
.and_then(Value::as_object)
.and_then(|value| value.get("chargingSchedulePeriod"))
.and_then(Value::as_array)
.and_then(|value| value.first())
.and_then(|value| value.get("limit"));
if let Some(limit) = path_2 {
return Self::extract_limit_value(limit);
}
let path_3 = profile
.get("chargingSchedulePeriod")
.and_then(Value::as_array)
.and_then(|value| value.first())
.and_then(|value| value.get("limit"));
if let Some(limit) = path_3 {
return Self::extract_limit_value(limit);
}
None
}
pub(in crate::simulator) fn extract_limit_value(
value: &Value,
) -> Option<f64> {
if let Some(limit) = value.as_f64() {
return Some(limit);
}
value.as_str().and_then(|limit| limit.parse::<f64>().ok())
}
}