You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
port(m7): CP simulator 2.0.1 — auto-trip a variable monitor when an inbound SetVariables write crosses its threshold #573
Follow-up to #545 (PR #570), which added the injection seam for OCPP 2.0.1 variable monitoring: ChargePoint::trip_variable_monitor(component, variable, actual_value) looks up every installed monitor watching a (component, variable) identity and originates a schema-valid NotifyEvent.req. That is a test/application-driven hook — the monitor never trips on its own.
This issue closes the remaining half: make a monitor trip autonomously when a CSMS-originated SetVariables write moves a monitored variable across a threshold the CSMS previously installed via SetVariableMonitoring. This is the real, inbound-CALL-triggered path that the PR #570 body explicitly deferred:
Auto-wiring a trip to a SetVariables write that crosses a threshold (a real inbound-CALL-triggered trip — which would use the RemoteCommand queue) is out of scope; this is the injection seam only.
Because the trip is now raised from inside an inbound-CALL handler (SetVariables), it must be drained through the existing RemoteCommand queue rather than emitted inline (the re-entrancy reason NotifyReport / NotifyMonitoringReport already queue), unlike the CP-initiated hooks that emit inline.
Scope (small slice)
Threshold-kind monitors only: Upper/LowerThreshold (→ Alerting) and Delta (→ Delta). Periodic/PeriodicClockAligned are time-driven, not write-driven — out of scope here.
Only the write path that actually changes a monitored variable's value: an accepted SetVariables write whose new value crosses (enters/leaves the alerting band for thresholds; moves by ≥ the configured delta for Delta) fires exactly one NotifyEvent per matched monitor, reusing the trip_variable_monitor emitter and its trigger-derivation / correlation / monotonic seqNo+eventId logic already landed in port(m7): CP simulator 2.0.1 — emit NotifyEvent when a CSMS-installed variable monitor trips #545.
Rejected SetVariables writes (unknown variable, read-only, out of range) never trip.
Hysteresis: a write that stays on the same side of a threshold as the previous value does not re-fire (no duplicate NotifyEvent storm on repeated same-band writes).
Failure modes / trust boundary
The trip is enqueued as a new RemoteCommand variant (e.g. V201MonitorTrip { .. }) drained by the command-consumer task, so the SetVariables handler returns its SetVariablesResponse without re-entering the receive loop.
No monitor match → no RemoteCommand enqueued, no seqNo burned (silent no-op).
Test matrix
Upper-threshold monitor: write above → one AlertingNotifyEvent; write back below → one event (leaving band); repeated writes on the same side → no re-fire (hysteresis).
Lower-threshold monitor: symmetric.
Delta monitor: write moving by ≥ delta fires; sub-delta write does not.
Ordering: the NotifyEvent is emitted after the SetVariablesResponse, driven off the RemoteCommand queue (assert via a capturing mock CSMS).
Acceptance criteria
An accepted SetVariables write that crosses an installed threshold/delta monitor auto-originates one schema-valid NotifyEvent per matched monitor, via the RemoteCommand queue (not inline).
Periodic/PeriodicClockAligned monitors are not affected; rejected and non-monitored writes never trip; hysteresis prevents same-band re-fire.
Trust boundary preserved: opaque values threaded verbatim; oversized value → outbound-schema Err, no panic.
cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test --workspace all green.
Reference
The mobilityhouse/ocpp Python library is protocol-only (message dataclasses + @on routing), so the behavior being ported here is the Charging Station side, not a Python module. The wire semantics are pinned by the ported types:
The inbound trigger: ocpp/v201/call.pyclass SetVariables / call_result.pySetVariables.
Depends on
#545 / #570 (the trip_variable_monitor emitter + monitors_for_variable store read this builds on). Best implemented after #570 merges.
🌙 Filed by the nightly dev during issue grooming (2026-09-09) — the milestone's earliest-incomplete queue had fewer than 3 unclaimed well-scoped port issues, and this is a genuine, code-verified follow-up gap explicitly deferred by #570 (not queue padding). Direction for the broader M7→M8 track remains tracked in #256.
Context
Follow-up to #545 (PR #570), which added the injection seam for OCPP 2.0.1 variable monitoring:
ChargePoint::trip_variable_monitor(component, variable, actual_value)looks up every installed monitor watching a(component, variable)identity and originates a schema-validNotifyEvent.req. That is a test/application-driven hook — the monitor never trips on its own.This issue closes the remaining half: make a monitor trip autonomously when a CSMS-originated
SetVariableswrite moves a monitored variable across a threshold the CSMS previously installed viaSetVariableMonitoring. This is the real, inbound-CALL-triggered path that the PR #570 body explicitly deferred:Because the trip is now raised from inside an inbound-CALL handler (
SetVariables), it must be drained through the existingRemoteCommandqueue rather than emitted inline (the re-entrancy reasonNotifyReport/NotifyMonitoringReportalready queue), unlike the CP-initiated hooks that emit inline.Scope (small slice)
Upper/LowerThreshold(→Alerting) andDelta(→Delta).Periodic/PeriodicClockAlignedare time-driven, not write-driven — out of scope here.SetVariableswrite whose new value crosses (enters/leaves the alerting band for thresholds; moves by ≥ the configured delta forDelta) fires exactly oneNotifyEventper matched monitor, reusing thetrip_variable_monitoremitter and its trigger-derivation / correlation / monotonicseqNo+eventIdlogic already landed in port(m7): CP simulator 2.0.1 — emit NotifyEvent when a CSMS-installed variable monitor trips #545.SetVariableswrites (unknown variable, read-only, out of range) never trip.NotifyEventstorm on repeated same-band writes).Failure modes / trust boundary
RemoteCommandvariant (e.g.V201MonitorTrip { .. }) drained by the command-consumer task, so theSetVariableshandler returns itsSetVariablesResponsewithout re-entering the receive loop.actualValuesurfaces as an outbound-schemaErr, never a panic or silent truncation.RemoteCommandenqueued, noseqNoburned (silent no-op).Test matrix
AlertingNotifyEvent; write back below → one event (leaving band); repeated writes on the same side → no re-fire (hysteresis).Deltamonitor: write moving by ≥ delta fires; sub-delta write does not.monitors_for_variable).SetVariableswrite → no trip.SetVariablesthat does not touch any monitored variable → no trip.seqNo/eventIdcontinue the per-station monotonic streams established in port(m7): CP simulator 2.0.1 — emit NotifyEvent when a CSMS-installed variable monitor trips #545;eventNotificationType = CustomMonitor.NotifyEventis emitted after theSetVariablesResponse, driven off theRemoteCommandqueue (assert via a capturing mock CSMS).Acceptance criteria
SetVariableswrite that crosses an installed threshold/delta monitor auto-originates one schema-validNotifyEventper matched monitor, via theRemoteCommandqueue (not inline).EventDataTypecorrelation, monotonicseqNo+eventId,CustomMonitornotification type) — no duplicated trip logic.Periodic/PeriodicClockAlignedmonitors are not affected; rejected and non-monitored writes never trip; hysteresis prevents same-band re-fire.Err, no panic.cargo fmt --check,cargo clippy --all-targets -- -D warnings,cargo test --workspaceall green.Reference
The mobilityhouse/ocpp Python library is protocol-only (message dataclasses +
@onrouting), so the behavior being ported here is the Charging Station side, not a Python module. The wire semantics are pinned by the ported types:ocpp/v201/enums.py—MonitorEnumType,EventTriggerEnumType,EventNotificationEnumType.ocpp/v201/call.pyclass NotifyEvent;ocpp/v201/datatypes.pyEventDataType.ocpp/v201/call.pyclass SetVariables/call_result.pySetVariables.Depends on
#545 / #570 (the
trip_variable_monitoremitter +monitors_for_variablestore read this builds on). Best implemented after #570 merges.🌙 Filed by the nightly dev during issue grooming (2026-09-09) — the milestone's earliest-incomplete queue had fewer than 3 unclaimed well-scoped port issues, and this is a genuine, code-verified follow-up gap explicitly deferred by #570 (not queue padding). Direction for the broader M7→M8 track remains tracked in #256.