ocppsim 0.2.0

A terminal OCPP-J charge point simulator.
Documentation
use super::super::payloads::{
  ListVersion_V2_X_Response, RequestStartTransactionResponse, to_value,
};
use super::super::{
  IncomingAction_V2_X, OcppErrorCode, ResponseStatus, Result, Simulator,
  TriggerMessage_V2_X, UiLogLevel, Value, WsWrite, json,
};
use super::request::{
  RequestStartTransactionRequest_V2_X, RequestStopTransactionRequest_V2_X,
  TriggerMessageRequest_V2_X,
};

impl Simulator {
  /// Dispatches an inbound OCPP 2.x CALL action and sends its response.
  pub(in crate::simulator) async fn handle_incoming_call_v2_x(
    &mut self,
    write: &mut WsWrite,
    message_id: &str,
    action: &str,
    payload: Value,
  ) -> Result<()> {
    let Some(parsed_action) = IncomingAction_V2_X::parse(action) else {
      return self
        .handle_unknown_incoming_action_v2_x(write, message_id, action)
        .await;
    };

    self
      .handle_parsed_incoming_call_v2_x_primary(
        write,
        message_id,
        parsed_action,
        &payload,
      )
      .await
  }

  async fn handle_parsed_incoming_call_v2_x_primary(
    &mut self,
    write: &mut WsWrite,
    message_id: &str,
    action: IncomingAction_V2_X,
    payload: &Value,
  ) -> Result<()> {
    match action {
      IncomingAction_V2_X::ChangeAvailability => dispatch_status!(
        self,
        write,
        message_id,
        self.change_availability_v2_x(payload)
      ),
      IncomingAction_V2_X::ClearCache => {
        self
          .send_status_response(write, message_id, ResponseStatus::Accepted)
          .await?;
      }
      IncomingAction_V2_X::DataTransfer => {
        self
          .send_call_result(
            write,
            message_id,
            Self::data_transfer_v2_x(payload),
          )
          .await?;
      }
      IncomingAction_V2_X::CertificateSigned => dispatch_status!(
        self,
        write,
        message_id,
        self.certificate_signed_v2_x(payload)
      ),
      IncomingAction_V2_X::DeleteCertificate => dispatch_status!(
        self,
        write,
        message_id,
        self.delete_certificate_from_payload(payload)
      ),
      IncomingAction_V2_X::GetInstalledCertificateIds => dispatch_response!(
        self,
        write,
        message_id,
        self.get_installed_certificate_ids_v2_x(payload)
      ),
      IncomingAction_V2_X::GetLocalListVersion => {
        self
          .send_call_result(
            write,
            message_id,
            to_value(&ListVersion_V2_X_Response {
              version_number: self.local_auth_list_version,
            }),
          )
          .await?;
      }
      IncomingAction_V2_X::GetLog => {
        dispatch_response!(self, write, message_id, self.get_log_v2_x(payload));
      }
      IncomingAction_V2_X::GetVariables => dispatch_response!(
        self,
        write,
        message_id,
        self.get_variables_v2_x(payload)
      ),
      IncomingAction_V2_X::InstallCertificate => dispatch_status!(
        self,
        write,
        message_id,
        self.install_certificate_from_payload(payload)
      ),
      IncomingAction_V2_X::RequestStartTransaction => {
        self
          .handle_request_start_transaction_call_v2_x(
            write, message_id, payload,
          )
          .await?;
      }
      IncomingAction_V2_X::RequestStopTransaction => {
        self
          .handle_request_stop_transaction_call_v2_x(write, message_id, payload)
          .await?;
      }
      other => {
        self
          .handle_parsed_incoming_call_v2_x_secondary(
            write, message_id, other, payload,
          )
          .await?;
      }
    }
    Ok(())
  }

  async fn handle_parsed_incoming_call_v2_x_secondary(
    &mut self,
    write: &mut WsWrite,
    message_id: &str,
    action: IncomingAction_V2_X,
    payload: &Value,
  ) -> Result<()> {
    match action {
      IncomingAction_V2_X::ReserveNow => dispatch_status!(
        self,
        write,
        message_id,
        self.reserve_now_v2_x(payload)
      ),
      IncomingAction_V2_X::CancelReservation => dispatch_status!(
        self,
        write,
        message_id,
        self.cancel_reservation_v2_x(payload)
      ),
      IncomingAction_V2_X::SendLocalList => dispatch_status!(
        self,
        write,
        message_id,
        self.send_local_list_v2_x(payload)
      ),
      IncomingAction_V2_X::SetChargingProfile => dispatch_status!(
        self,
        write,
        message_id,
        self.set_charging_profile_v2_x(payload)
      ),
      IncomingAction_V2_X::SetVariables => dispatch_response!(
        self,
        write,
        message_id,
        self.set_variables_v2_x(payload)
      ),
      IncomingAction_V2_X::ClearChargingProfile => {
        let status = self.clear_charging_profile_v2_x(payload);
        self.send_status_response(write, message_id, status).await?;
      }
      IncomingAction_V2_X::GetCompositeSchedule => dispatch_response!(
        self,
        write,
        message_id,
        self.get_composite_schedule_v2_x(payload)
      ),
      IncomingAction_V2_X::TriggerMessage => {
        self
          .handle_trigger_message_call_v2_x(write, message_id, payload)
          .await?;
      }
      IncomingAction_V2_X::UnlockConnector => dispatch_status!(
        self,
        write,
        message_id,
        self.unlock_connector_v2_x(payload)
      ),
      IncomingAction_V2_X::UpdateFirmware => dispatch_status!(
        self,
        write,
        message_id,
        self.update_firmware_v2_x(payload)
      ),
      IncomingAction_V2_X::Reset => {
        self.handle_reset_only_call(write, message_id).await?;
      }
      IncomingAction_V2_X::ChangeAvailability
      | IncomingAction_V2_X::ClearCache
      | IncomingAction_V2_X::DataTransfer
      | IncomingAction_V2_X::CertificateSigned
      | IncomingAction_V2_X::DeleteCertificate
      | IncomingAction_V2_X::GetInstalledCertificateIds
      | IncomingAction_V2_X::GetLocalListVersion
      | IncomingAction_V2_X::GetLog
      | IncomingAction_V2_X::GetVariables
      | IncomingAction_V2_X::InstallCertificate
      | IncomingAction_V2_X::RequestStartTransaction
      | IncomingAction_V2_X::RequestStopTransaction => unreachable!(),
    }
    Ok(())
  }

  async fn handle_request_start_transaction_call_v2_x(
    &mut self,
    write: &mut WsWrite,
    message_id: &str,
    payload: &Value,
  ) -> Result<()> {
    let request = match RequestStartTransactionRequest_V2_X::parse(payload) {
      Ok(value) => value,
      Err(error) => {
        return self
          .send_formation_violation(write, message_id, &error.to_string())
          .await;
      }
    };
    let Some(connector) = self
      .resolve_start_connector_or_reject(write, message_id, request.connector)
      .await?
    else {
      return Ok(());
    };

    if let Some(existing) = self.active_transaction_uid(connector) {
      return self
        .send_call_result(
          write,
          message_id,
          to_value(&RequestStartTransactionResponse {
            status: ResponseStatus::Accepted.as_str(),
            transaction_id: Some(&existing),
          }),
        )
        .await;
    }

    let status = if self
      .start_transaction(
        connector,
        request.id_token,
        true,
        Some(request.remote_start_id),
        true,
      )
      .is_ok()
    {
      ResponseStatus::Accepted
    } else {
      ResponseStatus::Rejected
    };
    self.send_status_response(write, message_id, status).await
  }

  async fn handle_request_stop_transaction_call_v2_x(
    &mut self,
    write: &mut WsWrite,
    message_id: &str,
    payload: &Value,
  ) -> Result<()> {
    let request = match RequestStopTransactionRequest_V2_X::parse(payload) {
      Ok(value) => value,
      Err(error) => {
        return self
          .send_formation_violation(write, message_id, &error.to_string())
          .await;
      }
    };
    let status = if let Some(connector) =
      self.find_transaction_by_uid(&request.transaction_id)
    {
      if self.stop_transaction(connector, None, true, true).is_ok() {
        ResponseStatus::Accepted
      } else {
        ResponseStatus::Rejected
      }
    } else {
      ResponseStatus::Rejected
    };
    self.send_status_response(write, message_id, status).await
  }

  async fn handle_trigger_message_call_v2_x(
    &mut self,
    write: &mut WsWrite,
    message_id: &str,
    payload: &Value,
  ) -> Result<()> {
    let request = match TriggerMessageRequest_V2_X::parse(payload) {
      Ok(value) => value,
      Err(error) => {
        return self
          .send_formation_violation(write, message_id, &error.to_string())
          .await;
      }
    };
    let Some(message) = TriggerMessage_V2_X::parse(
      &request.requested_message,
      self.config.protocol,
    ) else {
      return self
        .send_status_response(write, message_id, ResponseStatus::NotImplemented)
        .await;
    };
    match self.trigger_message_v2_x(message, request.connector) {
      Ok(status) => self.send_status_response(write, message_id, status).await,
      Err(error) => {
        self
          .send_formation_violation(write, message_id, &error.to_string())
          .await
      }
    }
  }

  async fn handle_unknown_incoming_action_v2_x(
    &mut self,
    write: &mut WsWrite,
    message_id: &str,
    action: &str,
  ) -> Result<()> {
    let (code, description) = if IncomingAction_V2_X::is_known_unsupported(
      action,
      self.config.protocol,
    ) {
      self.log(
        UiLogLevel::Warn,
        format!(
          "Action `{action}` is known for {} but is not supported.",
          self.config.protocol.label()
        ),
      );
      (
        OcppErrorCode::NotSupported.as_str(),
        format!("Action `{action}` is outside the supported subset."),
      )
    } else {
      self.log(
        UiLogLevel::Warn,
        format!("Action `{action}` is not implemented."),
      );
      (
        OcppErrorCode::NotImplemented.as_str(),
        format!("Action `{action}` is not implemented."),
      )
    };
    self
      .send_call_error(write, message_id, code, &description, json!({}))
      .await
  }
}