Skip to content

Support for Wallbox chargers over OCPP #131

Description

@stamateviorel

Hi @splatch and @PRosenb,

Following up on https://community.openhab.org/t/charging-openhab-with-ocpp/135114, I've been running an eu.chargetime.ocpp:ocpp16j:2.0-based OCPP central system against two Wallbox chargers in production and would like to contribute back. This is a single tracking issue for what I found; I'd open the individual PRs against master after we agree on scope and cadence.

Environment

  • Two Wallbox chargers, both firmware 6.7.38:
    • Wallbox Copper SB (CPB1-S-2-4, socketed Type 2)
    • Wallbox Pulsar Plus (PLP1-0-2-4)
  • OCPP 1.6-J central system bound to 0.0.0.0:8887
  • Both chargers also reachable over wallbox-mqtt-bridge for ground-truth comparison
  • openHAB 5.x on Debian, Java 21

All findings below are empirically reproduced on those units. Happy to share full session captures (frames + logs) if useful.

Findings

1. WebSocket 1006 idle reconnect cycle — PING_INTERVAL mismatch

Out of the box the chargers cycled the WebSocket with close code 1006 (remote=true) every 30–45 s. Root cause is in OcppServer.java:

this.server = new JSONServer(new ServerCoreProfile(handler));

The single-arg JSONServer constructor uses JSONConfiguration.get() defaults, which set PING_INTERVAL_PARAMETER = 60. That's passed straight through to Java-WebSocket's setConnectionLostTimeout(60). Wallbox's internal idle timeout fires before our PING does. Setting PING_INTERVAL_PARAMETER = 20 and passing a JSONConfiguration to the 2-arg constructor makes the connection stay up for hours / indefinitely.

2. Wallbox drops the socket if it receives an outbound CALL before its BootNotification is acknowledged

Pete's #122 ("register in newSession() since BootNotification isn't always sent") fixes the inbound side — registration happens early and outbound send() resolves to a UUID. But the binding still happily sends outbound CALLs immediately on newSession. Wallbox firmware specifically closes the socket if it receives any CALL before its boot handshake has completed for that session. See lbbrhzn/ocpp#1510 — confirmed on FW 6.7.38.

What works: gate outbound CALLs until either (a) handleBootNotificationRequest has fired this session, or (b) the session has been stable for N seconds with ≥1 StatusNotification (trust fallback for the case Wallbox reuses an existing boot state on reconnect).

3. OcppMeasurementMapping.get() returns null for Wallbox's measurand names

Wallbox firmware emits the phase inside the measurand string, with an empty phase field on SampledValue. Live GetConfiguration output from FW 6.7.38 (excerpt):

MeterValuesAlignedData = Energy.Active.Import.Register,Power.Active.Import,
  Current.Import.L1,Current.Import.L2,Current.Import.L3,
  Voltage.L1-N,Voltage.L2-N,Voltage.L3-N,
  Current.Offered,Power.Offered,Temperature

OcppMeasurementMapping.MAPPING.get("Current.Import.L1") returns null → silently dropped in ConnectorThingHandler.handleMeterValues(). Fix: strip a trailing .L1/.L2/.L3/.L1-N from the measurand before lookup, then route to per-phase channels.

4. Persisted hardware max current on Wallbox is the vendor key chargingALimitConn1, not OCPP-standard MaxCurrent

ConnectorThingHandler.handleCommand() sends ChangeConfiguration[MaxCurrent=N] for the currentLimit channel. Wallbox firmware silently ignores MaxCurrent. ChangeConfiguration[chargingALimitConn1=N] is Accepted and the value mirrors instantly on the Wallbox MQTT bridge's max_charging_current topic — same underlying register. Suggested: a hardwareMaxCurrentKey thing parameter (default MaxCurrent to keep current behavior; chargingALimitConn1 for the Wallbox vendor preset).

5. SmartCharging and RemoteTrigger feature profiles aren't registered on JSONServer

new JSONServer(new ServerCoreProfile(handler)) only enables Core. SetChargingProfile, ClearChargingProfile, TriggerMessage then throw UnsupportedFeatureException on server.send(...). Adding server.addFeatureProfile(new ServerSmartChargingProfile()) and ... new ServerRemoteTriggerProfile() unblocks:

  • dynamic current limit via TxDefaultProfile (the only chargingProfilePurpose Wallbox firmware accepts)
  • pause via SetChargingProfile(limit=0) and resume via SetChargingProfile(limit=N)
  • TriggerMessage(MeterValues) as a watchdog for stale telemetry

6. ClockAlignedDataInterval = 0 by default → no MeterValues outside a transaction

Without auto-config, telemetry only flows during an active charge session. Sending ChangeConfiguration[ClockAlignedDataInterval=30] plus setting MeterValuesAlignedData to the same measurand list makes the charger emit per-phase voltage/temperature even when idle. Worth doing once per session, post-boot.

7. Outbound CompletableFuture from Server.send() is never completed when the socket dies

ChargeTimeEU/Java-OCA-OCPP#121. The PromiseRepository retains the future indefinitely; on socket close it is silently orphaned. Wrapping every server.send(sid, req).toCompletableFuture().orTimeout(15, SECONDS) surfaces the failure as a TimeoutException in the log instead of silence.

Proposed PRs

Small, focused, easy to review independently:

  1. Configurable pingInterval on the server bridge (default 60 to match current behavior, can be lowered for Wallbox)
  2. Register ServerSmartChargingProfile + ServerRemoteTriggerProfile on the JSONServer
  3. MeterValues parser: strip phase suffix from measurand; route to per-phase channels
  4. .orTimeout() wrapper on outbound CALLs (and surface timeouts cleanly in the log)
  5. Boot-gate / trust-fallback for outbound CALLs (the big one — happy to discuss design first)
  6. New channels: pause, cableConnected (derived from StatusNotification), reset (Soft/Hard)
  7. hardwareMaxCurrentKey thing parameter + hardwareMaxCurrent channel (defaults to MaxCurrent for backwards compatibility)
  8. Auto-config ClockAlignedDataInterval + MeterValuesAlignedData post-boot
  9. One-shot GetConfiguration diagnostic dump on session-ready (log every key=value)
  10. Optional: chargerVendor dropdown (generic/wallbox/...) that presets sensible defaults for several of the above

PRs 1, 2, 4, 9 are tiny and low-risk. PR 5 is the meatiest and worth a design discussion before code.

Questions

  1. Do you have a preferred PR cadence — one tracking discussion thread + 10 small PRs, or batched into 2-3 larger ones?
  2. Are you open to adding a chargerVendor preset dropdown (PR 10), or would you prefer to keep the binding strictly OCPP-spec-level with vendor presets as a separate downstream binding?
  3. Anything in the above already covered by work-in-progress on your side that I should know about before opening PRs?

Thanks for the binding — it's the only OCPP option in the openHAB ecosystem and a solid foundation.

— Stamate Viorel (@stamateviorel)


Disclosure: this analysis and the proposed fixes were developed and tested over a production deployment with assistance from Anthropic's Claude (Claude Code). The findings are reproduced empirically; the Claude-assisted parts are the writeup and code drafts that follow.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions