feat(ocpp-cp): honor v201 TriggerMessage(PublishFirmwareStatusNotification) by re-reporting the latest publish-firmware status (M7) - #588
Open
duyhuynh-vn wants to merge 1 commit into
Conversation
…ation) by re-reporting the latest publish-firmware status (M7) Closes #585. The publish-to-local-cache twin of the TriggerMessage(FirmwareStatusNotification) re-report (#586): a CSMS asking TriggerMessage(requestedMessage = PublishFirmwareStatusNotification) now gets the station's latest publish status re-reported as one PublishFirmwareStatusNotification, without re-running a publish. Previously the v201 TriggerMessage handler classified it NotImplemented even though the simulator models PublishFirmware. - V201PublishFirmwareStore now retains a single latest V201PublishFirmwareStatusReport (status + optional requestId + optional location) alongside its in-flight set, recorded at the send_v201_publish_firmware_status choke point (before the send, so the station's notion advances even if the CALL fails) and retained past the in-flight marker's clear. Defaults to Idle / no requestId / no location. - v201_publish_firmware_status_report() builds the re-report with an optional requestId (Idle omits it); the async-progress v201_publish_firmware_status_notification() becomes a Some(request_id) wrapper over it. - v201_trigger_message_status reclassifies PublishFirmwareStatusNotification from NotImplemented to Accepted; send_v201_triggered_message dispatches to the new pure-snapshot trigger_v201_publish_firmware_status_notification(). The terminal Published carries the cached-image location URIs, so the snapshot retains location to reproduce that terminal shape faithfully; never-published reports Idle with requestId and location omitted (schema-valid, not null). Ports ocpp.v201.call.PublishFirmwareStatusNotification / TriggerMessage and PublishFirmwareStatusEnumType / MessageTriggerEnumType from the mobilityhouse reference. Store units, builder + schema-validity, trigger-classification, and end-to-end trigger tests (idle-when-none, latest-recorded snapshot with location + no new stream, terminal recorded across the real stream) all added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QivXv2JfX9f8ZjijhLRvFD
This was referenced Sep 28, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Honors
TriggerMessage(requestedMessage = PublishFirmwareStatusNotification)on the v201 CP simulator by re-reporting the station's latest publish-firmware status — the publish-to-local-cache twin of the just-landedTriggerMessage(FirmwareStatusNotification)re-report (#586). Advances M7 (OCPP 2.0.1).Closes #585Real use case
A site host acts as a Local Controller: the CSMS sends
PublishFirmwareso the controller downloads a firmware image once and caches it on the LAN for the chargers behind it, then watches the asyncPublishFirmwareStatusNotificationstream (Idle → DownloadScheduled → Downloading → Downloaded → Published). If the back office restarts or loses that progress CALL, an operator needs to re-confirm where the publish stands — has the image finished caching, and at which LAN URIs? They sendTriggerMessage(PublishFirmwareStatusNotification)and expect the controller to answer with its current publish status (e.g.Publishedwith the cached-imagelocationURIs), without kicking off a second download. Before this change the simulator classified that triggerNotImplemented, so the controller couldn't answer even though it models the publish flow.What changed
crates/ocpp-cp/src/v201_publish_firmware.rs—V201PublishFirmwareStorenow retains a single latestV201PublishFirmwareStatusReport { status, location, request_id }alongside its in-flight set.record_reported()/last_reported()added; defaults toIdle/ norequestId/ nolocation. The snapshot is deliberately not cleared when an in-flight id settles, so a finishedPublished(with its cached-imagelocation) stays reportable. With independent per-id streams, "latest" is the most recent emit across all of them — the station's current publish status.crates/ocpp-cp/src/v201_command.rs— newv201_publish_firmware_status_report(status, location, request_id: Option<i32>)builds the re-report (IdleomitsrequestIdandlocation, absent-not-null per the schema'sskip_serializing_if); the async-progressv201_publish_firmware_status_notification()becomes aSome(request_id)wrapper over it.v201_trigger_message_statusreclassifiesPublishFirmwareStatusNotificationfromNotImplemented→Accepted.crates/ocpp-cp/src/lib.rs—send_v201_publish_firmware_statusrecords the status at its single emit choke point before the send (so the station's notion advances even if the CALL fails).send_v201_triggered_messagedispatches to the new pure-snapshottrigger_v201_publish_firmware_status_notification(), which starts no publish and leaves the in-flight set untouched. Runs on the command-consumer task, off the inbound-CALL path.Failure modes / trust boundary: the trigger and its
requestIdare CSMS-supplied but stored opaquely asi32(never parsed/indexed — extremes can't panic);locationis simulator-supplied, never attacker input. Never-published →Idle(norequestId/location). A send failure is logged best-effort, never propagated. 1.6J is unaffected.What was ported
ocpp/v201/call.py—class PublishFirmwareStatusNotification,class TriggerMessage.ocpp/v201/enums.py—PublishFirmwareStatusEnumType,MessageTriggerEnumType.publish_firmware_status_notification.Test plan
cargo fmt --all --check✅cargo clippy --workspace --all-targets -- -D warnings✅cargo test --workspace✅ (836 ocpp-cp lib tests + all integration suites, 0 failures)New tests:
Idle/none/none;record_reportedtracks latest (incl.Publishedretaining itslocation); terminal survives the in-flight clear; extremerequestIds don't panic...._reportomitsrequestId/locationwhenNone; carries both whenSome; wrapper equivalence; schema-valid across status ×requestId(±location) combinations.PublishFirmwareStatusNotificationnow in theAcceptedset; counts updated (7 accepted / 4 not-implemented); totality preserved.Idlewhen no publish ran; latest-recordedPublishedsnapshot re-reports status +requestId+locationand opens no stream; the real publish stream records itsPublishedterminal (with locations) for re-report.Acceptance criteria
TriggerMessage(PublishFirmwareStatusNotification)isAcceptedand originates one schema-validPublishFirmwareStatusNotificationwith the latest status.Idle.PublishFirmwareflow or 1.6J.cargo fmt --check,cargo clippy --all-targets -- -D warnings,cargo test --workspaceall green.Known gaps / notes
SignChargingStationCertificate,SignV2GCertificate,SignCombinedCertificate) remainNotImplemented— a distinct, heavier slice (they must originate a freshSignCertificateCSR flow, not re-report a status), to be tracked separately once the status-re-report family (port(m7): CP simulator 2.0.1 — honor TriggerMessage(LogStatusNotification) by re-reporting the latest log-upload status #584LogStatusNotification, in flight as feat(ocpp-cp): honor v201 TriggerMessage(LogStatusNotification) by re-reporting the latest log-upload status (M7) #587) lands.TriggerMessageclassification arm as in-flight feat(ocpp-cp): honor v201 TriggerMessage(LogStatusNotification) by re-reporting the latest log-upload status (M7) #587 (LogStatusNotification); a trivial merge conflict is expected when both land and is resolved by keeping both reclassifications.🤖 Generated with Claude Code
https://claude.ai/code/session_01QivXv2JfX9f8ZjijhLRvFD
Generated by Claude Code