use super::super::payloads::*;
use super::super::*;
use super::request::{
AvailabilityRequest, CancelReservationRequest, CompositeScheduleRequest_V2_X,
ReserveNowRequest_V2_X, SendLocalListRequest_V2_X,
SetChargingProfileRequest_V2_X, UnlockConnectorRequest_V2_X,
UpdateFirmwareRequest_V2_X,
};
impl Simulator {
pub(in crate::simulator) fn change_availability_v2_x(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = AvailabilityRequest::parse_v2_x(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_v2_x(
&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").cloned();
to_value(&DataTransferResponse {
status: ResponseStatus::Accepted.as_str(),
data,
})
}
pub(in crate::simulator) fn get_log_v2_x(
&mut self,
payload: &Value,
) -> Result<Value> {
let request_id = payload
.get("requestId")
.and_then(Value::as_i64)
.ok_or_else(|| anyhow!("requestId is required."))?;
let location = payload
.get("log")
.and_then(Value::as_object)
.and_then(|log| log.get("remoteLocation"))
.and_then(Value::as_str)
.ok_or_else(|| anyhow!("log.remoteLocation is required."))?;
self.log(
UiLogLevel::Info,
format!("Received GetLog request for {}", location),
);
self.enqueue_log_status_notification("Uploading", Some(request_id));
self.enqueue_log_status_notification("Uploaded", Some(request_id));
let filename = format!("log-{}.txt", self.config.cp_id);
Ok(to_value(&GetLog_V2_X_Response {
status: ResponseStatus::Accepted.as_str(),
filename: &filename,
}))
}
pub(in crate::simulator) fn get_variables_v2_x(
&self,
payload: &Value,
) -> Result<Value> {
let entries = payload
.get("getVariableData")
.and_then(Value::as_array)
.ok_or_else(|| anyhow!("getVariableData is required."))?;
if entries.is_empty() {
return Err(anyhow!("getVariableData must not be empty."));
}
let mut results = Vec::with_capacity(entries.len());
for entry in entries {
results.push(self.get_variable_result_v2_x(entry)?);
}
Ok(to_value(&GetVariables_V2_X_Response {
get_variable_result: results,
}))
}
pub(in crate::simulator) fn set_variables_v2_x(
&mut self,
payload: &Value,
) -> Result<Value> {
let entries = payload
.get("setVariableData")
.and_then(Value::as_array)
.ok_or_else(|| anyhow!("setVariableData is required."))?;
if entries.is_empty() {
return Err(anyhow!("setVariableData must not be empty."));
}
let mut results = Vec::with_capacity(entries.len());
for entry in entries {
results.push(self.set_variable_result_v2_x(entry)?);
}
Ok(to_value(&SetVariables_V2_X_Response {
set_variable_result: results,
}))
}
pub(in crate::simulator) fn send_local_list_v2_x(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = SendLocalListRequest_V2_X::parse(payload)?;
self.local_auth_list_version = request.version_number;
Ok(ResponseStatus::Accepted)
}
pub(in crate::simulator) fn unlock_connector_v2_x(
&self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = UnlockConnectorRequest_V2_X::parse(payload)?;
if request.connector_id != 1 {
return Ok(ResponseStatus::UnknownConnector);
}
let Some(state) = self.connectors.get(&request.evse_id) else {
return Ok(ResponseStatus::UnknownConnector);
};
if state.transaction.is_some() {
Ok(ResponseStatus::OngoingAuthorizedTransaction)
} else {
Ok(ResponseStatus::Unlocked)
}
}
pub(in crate::simulator) fn update_firmware_v2_x(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = UpdateFirmwareRequest_V2_X::parse(payload)?;
let location = payload
.get("firmware")
.and_then(Value::as_object)
.and_then(|item| item.get("location"))
.and_then(Value::as_str)
.ok_or_else(|| anyhow!("firmware.location is required."))?;
self.log(
UiLogLevel::Info,
format!("Received UpdateFirmware request from {}", location),
);
self.enqueue_firmware_status_notification(
"Downloading",
Some(request.request_id),
);
self.enqueue_firmware_status_notification(
"Downloaded",
Some(request.request_id),
);
self.enqueue_firmware_status_notification(
"Installing",
Some(request.request_id),
);
self.enqueue_firmware_status_notification(
"Installed",
Some(request.request_id),
);
Ok(ResponseStatus::Accepted)
}
pub(in crate::simulator) fn reserve_now_v2_x(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = ReserveNowRequest_V2_X::parse(payload)?;
self
.reserve_connector(request.connector.unwrap_or(0), request.reservation_id)
}
pub(in crate::simulator) fn cancel_reservation_v2_x(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = CancelReservationRequest::parse_v2_x(payload)?;
self.cancel_reservation(request.reservation_id)
}
pub(in crate::simulator) fn set_charging_profile_v2_x(
&mut self,
payload: &Value,
) -> Result<ResponseStatus> {
let request = SetChargingProfileRequest_V2_X::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_v2_x(
&mut self,
payload: &Value,
) -> ResponseStatus {
let profile_id = payload.get("chargingProfileId").and_then(Value::as_i64);
let criteria = payload
.get("chargingProfileCriteria")
.and_then(Value::as_object);
let targets = if let Some(criteria_payload) = criteria {
self.clear_profile_targets(&json!(criteria_payload), "evseId")
} else {
Some(self.connectors.keys().copied().collect())
};
let Some(targets) = targets else {
return ResponseStatus::Unknown;
};
let purpose = criteria
.and_then(|value| value.get("chargingProfilePurpose"))
.and_then(Value::as_str);
let stack_level = criteria
.and_then(|value| value.get("stackLevel"))
.and_then(Value::as_i64);
self.clear_charging_profiles_matching(targets, |profile| {
profile_id.is_none_or(|value| {
profile.get("id").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_v2_x(
&self,
payload: &Value,
) -> Result<Value> {
let request = CompositeScheduleRequest_V2_X::parse(payload)?;
let Some(state) = self.connectors.get(&request.connector) else {
return Ok(to_value(&GetCompositeSchedule_V2_X_Response {
status: ResponseStatus::Rejected.as_str(),
schedule: None,
}));
};
let Some(limit) = state.offered_limit else {
return Ok(to_value(&GetCompositeSchedule_V2_X_Response {
status: ResponseStatus::Rejected.as_str(),
schedule: None,
}));
};
let timestamp = now_timestamp();
Ok(to_value(&GetCompositeSchedule_V2_X_Response {
status: ResponseStatus::Accepted.as_str(),
schedule: Some(CompositeSchedule_V2_X {
evse_id: request.connector,
duration: request.duration,
schedule_start: ×tamp,
charging_rate_unit: "W",
charging_schedule_period: vec![ChargingSchedulePeriod {
start_period: 0,
limit,
}],
}),
}))
}
fn get_variable_result_v2_x(
&self,
entry: &Value,
) -> Result<VariableResult_V2_X> {
let component = variable_component(entry)?;
let variable = variable_name(entry)?;
let attribute_type = variable_attribute_type(entry);
let base = VariableResult_V2_X::from_entry(entry, "UnknownVariable")?;
if !is_supported_variable_component(component) {
return Ok(VariableResult_V2_X {
attribute_status: "UnknownComponent",
..base
});
}
if !is_supported_variable_attribute(attribute_type) {
return Ok(VariableResult_V2_X {
attribute_status: "NotSupportedAttributeType",
..base
});
}
if let Some((_, configuration)) = self.configuration_entry(variable) {
return Ok(VariableResult_V2_X {
attribute_status: ResponseStatus::Accepted.as_str(),
attribute_value: Some(configuration.value.clone()),
..base
});
}
Ok(base)
}
fn set_variable_result_v2_x(
&mut self,
entry: &Value,
) -> Result<VariableResult_V2_X> {
let component = variable_component(entry)?;
let variable = variable_name(entry)?;
let attribute_type = variable_attribute_type(entry);
let attribute_value = entry
.get("attributeValue")
.and_then(Value::as_str)
.ok_or_else(|| {
anyhow!("setVariableData.attributeValue is required.")
})?;
let base = VariableResult_V2_X::from_entry(entry, "UnknownVariable")?;
if !is_supported_variable_component(component) {
return Ok(VariableResult_V2_X {
attribute_status: "UnknownComponent",
..base
});
}
if !is_supported_variable_attribute(attribute_type) {
return Ok(VariableResult_V2_X {
attribute_status: "NotSupportedAttributeType",
..base
});
}
let Some(configuration_key) = self.configuration_key(variable) else {
return Ok(base);
};
Ok(VariableResult_V2_X {
attribute_status: self
.set_configuration_value(&configuration_key, attribute_value)
.as_str(),
..base
})
}
}
fn variable_component(entry: &Value) -> Result<&str> {
entry
.get("component")
.and_then(Value::as_object)
.and_then(|component| component.get("name"))
.and_then(Value::as_str)
.ok_or_else(|| anyhow!("component.name is required."))
}
fn variable_name(entry: &Value) -> Result<&str> {
entry
.get("variable")
.and_then(Value::as_object)
.and_then(|variable| variable.get("name"))
.and_then(Value::as_str)
.ok_or_else(|| anyhow!("variable.name is required."))
}
fn variable_attribute_type(entry: &Value) -> &str {
entry
.get("attributeType")
.and_then(Value::as_str)
.unwrap_or("Actual")
}
fn is_supported_variable_component(component: &str) -> bool {
normalize_identifier(component) == "chargingstation"
}
fn is_supported_variable_attribute(attribute_type: &str) -> bool {
matches!(attribute_type, "Actual" | "Target")
}