Skip to content

feat(ocpp-cp): emit v201 TransactionEvent(CablePluggedIn) for the EVConnected plug-in phase (M7) - #581

Merged
duyhuynh-vn merged 2 commits into
mainfrom
claude/inspiring-ramanujan-yr3qsk
Sep 28, 2026
Merged

duyhuynh-vn merged 2 commits into
mainfrom
claude/inspiring-ramanujan-yr3qsk

Conversation

@duyhuynh-vn

Copy link
Copy Markdown
Collaborator

Summary

Ports the OCPP 2.0.1 cable-plugged, pre-authorization transaction phase into the CP simulator (M7): a new V201-only driver hook drives a connector into the EVConnected state and originates one TransactionEvent(triggerReason = CablePluggedIn, chargingState = EVConnected). This is the exact companion #577 (landed as #580) deferred.

Closes #579

Real use case

A driver pulls up and plugs the cable in before authorizing (delayed authorization / plug-and-charge, where the station's TxStartPoint includes EVConnected). A real charging station opens the transaction and reports a TransactionEvent with triggerReason = CablePluggedIn / chargingState = EVConnected — the "cable in, not yet authorized/charging" phase — so the back office sees the car is physically connected and waiting, not idle. Today the simulator jumps straight to Charging, so a downstream CSMS never observes this phase that plug-and-charge and delayed-authorization conformance exercises. This adds the hook that emits it.

What changed

crates/ocpp-cp, extending #577's plumbing rather than duplicating it:

  • v201_transaction::transaction_event_state_transition — a new pub(crate) core builder that both the ChargingStateChanged builder (port(m7): CP simulator 2.0.1 — emit TransactionEvent(Updated, ChargingStateChanged) on SuspendedEV / SuspendedEVSE / Charging transitions #577) and the new CablePluggedIn builder delegate to. Same interim-Updated shape: the new transactionInfo.chargingState, no meterValue (the reading rides the MeterValuePeriodic path), no idToken (not an authorization event).
  • transaction_event_cable_plugged_in — the new pure builder (CablePluggedIn / EVConnected).
  • ChargePoint::plug_in_cable(evse_id) — the new driver hook. It shares the resolve → compare-and-set → emit core (emit_transaction_state_change) with transition_charging_state, so behavior is identical bar the trigger reason / target state.

Failure modes / trust boundary: driver-originated (not inbound-CALL-triggered), so it emits inline via call(). Redundant transition (already EVConnected) → Redundant, no event, no seqNo burned (hysteresis). No active transaction (incl. out-of-range evse_id) → NoTransaction, inert, no panic. A transport/timeout/CALLERROR leaves the modeled state unchanged. The builder is pure and its output is schema-validated by call().

What was ported

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

Test plan

cargo fmt --all --check, cargo clippy --all-targets -- -D warnings, cargo test --workspace all green locally (816 passing in ocpp-cp, +6).

New tests:

  • Builder (v201_transaction.rs): cable_plugged_in_event_carries_ev_connected_shape, cable_plugged_in_round_trips_the_exact_wire_tokens, and the schema-validity sweep extended to the plug-in event.
  • Hook (lib.rs): plug_in_cable_emits_one_ev_connected_event, plug_in_cable_redundant_transition_is_a_noop (no event, no seqNo burned), plug_in_cable_with_no_active_transaction_is_inert, plug_in_cable_is_v201_only.

Acceptance criteria

  • A V201-only driver hook drives a connector into the pre-authorization EVConnected phase and originates one schema-valid TransactionEvent(triggerReason = CablePluggedIn, chargingState = EVConnected).
  • Reuses port(m7): CP simulator 2.0.1 — emit TransactionEvent(Updated, ChargingStateChanged) on SuspendedEV / SuspendedEVSE / Charging transitions #577's TransactionEvent state-change plumbing (shared pub(crate) core — no duplicated builder).
  • Redundant / no-context cases are inert (no event, no seqNo burned, no panic); Ended still closes with Idle.
  • seqNo continues the transaction's monotonic stream.
  • A 1.6J charge point refuses the hook with OcppError::NotSupported.
  • cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test --workspace all green.

Known gaps / notes

🤖 Generated with Claude Code

https://claude.ai/code/session_01RR4rxKX9fJungJiibgJZ5S


Generated by Claude Code

…onnected plug-in phase (M7)

Port the OCPP 2.0.1 cable-plugged, pre-authorization transaction phase into
the CP simulator: a new V201-only driver hook drives a connector into the
EVConnected state and originates one
TransactionEvent(triggerReason = CablePluggedIn, chargingState = EVConnected).

This is the companion #577 (landed as #580) deferred, and extends #577's
state-transition plumbing rather than duplicating it:

- v201_transaction::transaction_event_state_transition: a new pub(crate) core
  that both the ChargingStateChanged builder (#577) and the new CablePluggedIn
  builder delegate to (same interim-Updated shape: new chargingState, no
  meterValue, no idToken).
- transaction_event_cable_plugged_in: the new pure builder.
- ChargePoint::plug_in_cable(evse_id): the new driver hook, sharing the
  resolve -> compare-and-set -> emit core (emit_transaction_state_change) with
  transition_charging_state. Redundant / no-transaction cases are inert; a 1.6J
  station refuses with OcppError::NotSupported.

Reconciling the full TxStartPoint variants (having the plug-in genuinely open
the transaction before authorization, Started vs Updated per start point) is a
deliberate follow-up, out of this slice's scope.

Closes #579.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RR4rxKX9fJungJiibgJZ5S
…spans

The `Documentation` CI check (`cargo doc --document-private-items` with
`RUSTDOCFLAGS=--deny warnings`) failed on `rustdoc::private_intra_doc_links`:
the public docs for `transaction_event_cable_plugged_in` and
`ChargePoint::plug_in_cable` linked the private `transaction_event_state_transition`
and `emit_transaction_state_change`. Such links resolve only under
`--document-private-items` and break in the public docs, so rustdoc denies them.
Reference both as plain code spans instead — no public link to a private item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RR4rxKX9fJungJiibgJZ5S
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants