Skip to content

port(m7): CP simulator 2.0.1 — emit NotifyEvent when a CSMS-installed variable monitor trips #545

Description

@duyhuynh-vn

Context

With the inbound CSMS→CP handler wiring for OCPP 2.0.1 now essentially complete (all 39 CSMS-initiated commands are wired or in-flight), the remaining M7 work is the CP→CSMS-initiated async flows the simulator does not yet drive.

The variable-monitoring family is currently a dead-end: the CP accepts and stores monitors via SetVariableMonitoring (#498), ClearVariableMonitoring (#499), SetMonitoringBase (#504), SetMonitoringLevel (#503), and reports the configured set via GetMonitoringReport (#495) → NotifyMonitoringReport. But no monitor ever fires — the CP never emits a NotifyEvent.req. The monitor store in crates/ocpp-cp/src/v201_device_model.rs (monitors: HashMap<i32, MonitorEntry>) even carries a documented "a future emitter can honor it" seam comment for exactly this.

This is the direct async twin of the already-shipped NotifyReport (#487) and NotifyMonitoringReport (#495) streams — same off-CALL-path RemoteCommand queueing discipline.

Python reference

  • ocpp/v201/call.py → class NotifyEvent (generated_at, seq_no, event_data: List[EventDataType], tbc)
  • ocpp/v201/datatypes.py → class EventDataType (event_id, timestamp, trigger, actual_value, event_notification_type, component, variable, optional cause, tech_code, tech_info, cleared, transaction_id, variable_monitoring_id)
  • ocpp/v201/enums.py → EventTriggerEnumType, EventNotificationEnumType

Proposed design

Because a pure simulator has no naturally-changing variable values, the trip must be deterministically injected — matching the simulator's existing opt-in behavior-injection pattern (firmware fault injection, unlock_outcome, etc.):

  • A test/sim hook (e.g. trip_variable_monitor(component, variable, actual_value)) that looks up a matching monitor in the store and, if found, queues a RemoteCommand::V201NotifyEvent.
  • run_v201_notify_event emits a schema-valid NotifyEvent.req with correctly correlated variable_monitoring_id, the monitor's EventTriggerEnumType (Delta/Periodic/UpperThreshold/LowerThreshold derived from the monitor type), event_notification_type, component/variable, and a monotonically increasing seq_no.
  • No matching monitor → no event queued (no-op), consumer-gone rolls the marker back (existing convention).

Acceptance criteria

  • RemoteCommand::V201NotifyEvent variant + run_v201_notify_event emitter, queued off the inbound path (never re-entering the receive loop mid-dispatch).
  • Pure NotifyEvent.req / EventDataType page builder in v201_command with a schema-validity test against the bundled FINAL JSON Schema.
  • Trigger derivation maps each stored MonitorType to the correct EventTriggerEnumType.
  • Tests: trip-with-match emits one correlated event; trip-with-no-match is a no-op; seq_no increments across events; hostile/oversized injected actual_value never panics (trust boundary — value is stringly reported, never parsed).
  • cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test --workspace all green.

Milestone: M7. Target ≤ ~500 LOC; if larger, ship the emitter + happy path and split threshold/delta derivation into a follow-up.


Filed by the nightly dev during Sunday issue grooming (2026-08-23). Direction tracked in #256.

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