Skip to content

port(m7): CP simulator 2.0.1 — honor TriggerMessage(FirmwareStatusNotification) by re-reporting the latest firmware status #583

Description

@duyhuynh-vn

Context

A code-verified gap in the OCPP 2.0.1 CP simulator's TriggerMessage support. The v201 handler classifies six requested-message types as NotImplemented (crates/ocpp-cp/src/v201_command.rs, v201_trigger_message_status):

LogStatusNotification | FirmwareStatusNotification | SignChargingStationCertificate
| SignV2GCertificate | SignCombinedCertificate | PublishFirmwareStatusNotification
  => TriggerMessageStatusEnumType::NotImplemented

But the simulator does model firmware updates: UpdateFirmware drives an async FirmwareStatusNotification(Downloading → Downloaded → Installing → Installed) progress stream (run_v201_firmware_update, Issue #532/#534). So a CSMS asking TriggerMessage(requestedMessage = FirmwareStatusNotification) should get the station's current firmware status — exactly as the 1.6J side already does: the 1.6J CP holds a firmware_status: Arc<RwLock<FirmwareStatus>> and TriggerMessage(FirmwareStatusNotification) re-reports it "without re-running the update" (crates/ocpp-cp/src/lib.rs).

The v201 side can't do this yet because nothing persists the latest status: V201FirmwareUpdateStore (crates/ocpp-cp/src/v201_firmware_update.rs) tracks only the in-flight requestId, not a FirmwareStatusEnumType. This issue closes that gap for the FirmwareStatusNotification trigger (the direct v201 twin of the existing 1.6J behavior). The sibling triggers are tracked separately (LogStatusNotification, PublishFirmwareStatusNotification, and the certificate-signing triggers).

Scope (small slice)

  • Persist the latest reported v201 firmware status + correlating requestId (mirroring the 1.6J firmware_status field), colocated in V201FirmwareUpdateStore. Default { status: Idle, requestId: None }. Recorded at the single emit choke point (send_v201_firmware_status), so the full lifecycle (interim + terminal, happy path and injected failures) is captured.
  • Reclassify FirmwareStatusNotification from NotImplemented to Accepted in v201_trigger_message_status.
  • Add the dispatch arm in send_v201_triggered_message: read the latest status and emit one FirmwareStatusNotification(status, requestId) — a pure snapshot re-report, no new update kicked off, in-flight store untouched.
  • requestId is Option<i32> on the wire (skip_serializing_if): omitted for a never-ran Idle report, present for any status that came from an UpdateFirmware.

Failure modes / trust boundary

  • Never ran an update → report Idle, requestId omitted (Idle is a valid FirmwareStatusEnumType; the spec omits requestId for a status not tied to a specific request).
  • Update completed (Installed) → in-flight store is cleared, but the last-reported status is retained → re-report Installed + its requestId.
  • Injected failure (DownloadFailed / InstallationFailed) → re-report that terminal status.
  • Mid-flight trigger → reports the most recent interim status (a snapshot), never restarts the rollout.
  • The re-report burns no requestId, allocates nothing, and never re-enters the receive loop (runs on the command-consumer task, like the other triggers).
  • A 1.6J charge point is unaffected (separate classifier + firmware_status field).

Test matrix

  • Store: a new store's last-reported is { Idle, None }; recording a status updates it.
  • Pure builder: an Idle report omits requestId and is schema-valid; a Some(id) report carries it and is schema-valid.
  • Policy: v201_trigger_message_status(FirmwareStatusNotification) == Accepted.
  • Handler (mock CSMS): TriggerMessage(FirmwareStatusNotification) with no prior update → response Accepted, then one FirmwareStatusNotification(Idle) with no requestId.
  • Handler: after an UpdateFirmware runs to Installed, the trigger re-reports Installed + that requestId.

Acceptance criteria

  • TriggerMessage(FirmwareStatusNotification) is classified Accepted and originates exactly one schema-valid FirmwareStatusNotification carrying the station's latest firmware status.
  • The latest status (+ correlating requestId) is tracked across the full update lifecycle; a never-ran station reports Idle with requestId omitted.
  • The re-report is a pure snapshot — it starts no update and leaves the in-flight store unchanged.
  • No regression to the async UpdateFirmware → FirmwareStatusNotification progress stream or the 1.6J trigger behavior.
  • cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test --workspace all green.

Reference

mobilityhouse/ocpp is protocol-only, so the behavior ported is the Charging Station side; the wire tokens are pinned by the already-ported types:

  • ocpp/v201/enums.py — FirmwareStatusEnumType, MessageTriggerEnumType.firmware_status_notification.
  • ocpp/v201/call.py — class FirmwareStatusNotification, class TriggerMessage.
  • Existing Rust: crates/ocpp-cp/src/v201_firmware_update.rs, crates/ocpp-cp/src/v201_command.rs (v201_trigger_message_status, v201_firmware_status_notification), crates/ocpp-cp/src/lib.rs (send_v201_triggered_message, send_v201_firmware_status, run_v201_firmware_update). 1.6J precedent: the firmware_status field + TriggerMessage(FirmwareStatusNotification) arm in lib.rs.

Milestone / labels

M7 — OCPP 2.0.1. Labels: port, m7. Small (one store field + methods, one policy line, one dispatch arm, one builder variant, tests — well under the ~500-LOC target).


🌙 Filed by the nightly dev during Sunday grooming (2026-09-27): the M7 earliest-incomplete queue had dropped below 3 unblocked, well-scoped port issues (#579 is in-flight as PR #581; #582 is blocked on it). This is a code-verified gap — the v201 TriggerMessage handler reports FirmwareStatusNotification as NotImplemented despite the simulator modeling firmware updates — and the direct twin of behavior the 1.6J CP already has. Broader M7→M8 direction remains tracked in #256.

Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions