3.2 KiB
OCPP Forwarder Architecture
The OCPP forwarder (charger/ocpp/forwarder.go) is a hybrid proxy that lets a charger talk to evcc and an upstream OCPP server at the same time. Chargers connect directly to evcc's central system on the normal port. For each charger with a matching ForwarderRule, a "sidecar" WebSocket connection to the upstream server is opened and kept in parallel for the lifetime of the charger connection.
The forwarder is opt-in: hooks (chargerConnectHook, chargerDisconnectHook, chargerMessageHook in instance.go) are nil unless a rule matches, so a charger without a rule behaves exactly as before.
Forwarding modes
Two modes apply at the same time, selected per message by its action.
Transparent relay (billing-critical)
For the actions in actionsRelayedToUpstream (Authorize, StartTransaction, StopTransaction, DataTransfer), upstream is the authoritative Central System:
- Charger sends the Call to evcc.
- The message hook forwards it to the upstream sidecar and bypasses evcc's OCPP handler.
- Upstream's
CallResult/CallErroris relayed back to the charger.
evcc's handler is never invoked for these. This lets the pay backend control authorization, issue its own transaction IDs, and see consistent Start/Stop pairs.
Sidecar observation (informational)
For all other messages (BootNotification, StatusNotification, MeterValues, Heartbeat, etc.):
- Charger sends the Call to evcc, which processes it normally.
- The same frame is also mirrored to the upstream sidecar.
Upstream observes the session while evcc manages the charger as usual.
Upstream to charger (commands)
Calls (type 2) initiated by upstream are injected into the charger via CS.Write. The charger's CallResult/CallError is routed back to upstream. Examples: RemoteStartTransaction, RemoteStopTransaction, GetConfiguration, ChangeConfiguration, TriggerMessage, SetChargingProfile.
ChangeConfiguration for MeterValueSampleInterval is intercepted: the forwarder absorbs it as a local throttle on MeterValues forwarded to upstream and replies Accepted without touching the charger's own config. evcc still processes every MeterValues frame for energy management.
Read-only mode
When a rule sets ReadOnly, upstream may observe but cannot control the charger. Any incoming Call from upstream is answered with a SecurityError and not forwarded. ReadOnly is applied live per message, so toggling it does not require reconnecting the sidecar.
Connection lifecycle
Frames that arrive from a charger before its sidecar finishes dialling are buffered (pendingMsgs) and flushed in order once the sidecar connects, so early messages such as BootNotification still reach upstream. If the dial fails or upstream drops mid-session, any buffered or in-flight relay Calls are answered to the charger with a CallError so it is not left hanging, and the failure is surfaced to the UI via forwarderErrors.
Rules can be changed at runtime through ApplyForwarderRules. Sidecars for removed rules are closed, rules with changed connection parameters are re-dialled, and rules for chargers that are not connected are test-dialled to surface unreachable hosts immediately.