EEBus: add OHPCF heat pump compressor flexibility#30636
Conversation
The dev branch reworks remote service trust around ship-go's ServiceIdentity and splits the load control write approval event into separate limit and device configuration events. NewConfiguration now takes the device categories as well as an optional SHIP Pairing config and ring buffer persistence; evcc trusts remote services by their configured SKI via RegisterRemoteService and does not use SHIP Pairing, so both are passed as nil. RegisterRemoteSKI/UnregisterRemoteSKI/RemoteServiceForSKI/CancelPairingWithSKI are replaced by their ServiceIdentity-based equivalents, and the service reader callbacks switch from raw SKI strings to ServiceIdentity, gaining the new SHIP Pairing auto-trust events as no-ops. Device configuration writes were applied automatically up to v0.7.0 but now require explicit approval, so the LPC/LPP handlers approve all pending configuration writes to preserve the previous behaviour.
Bumps eebus-go to the branch adding the cem/ohpcf use case and registers it on the CEM entity alongside the other use cases. OHPCF lets evcc, acting as CEM, observe the optional power consumption a remote heat pump compressor announces. The use case is wired read-only: its data update events are read and logged (availability, requested and maximum power, process state, pausable/stoppable flags and the minimal run and pause durations) and the use case is reachable for on-demand reads via CustomerEnergyManagement. Scheduling, aborting, pausing and resuming a consumption process are intentionally left out for now.
Adds a charger that exposes a remote heat pump compressor announcing optional power consumption (OHPCF) as a switchable load. Status, enabled state and control are derived from the compressor's power consumption process state: running maps to charging, available/scheduled/ paused to connected, everything else to disconnected. OHPCF has no power setpoint, so MaxCurrent instead converts the loadpoint's allotted current to power (using the active phases) and schedules the optional consumption to start now once the available surplus covers the compressor's requested power, pausing or aborting it otherwise.
CurrentPower now reports the heat pump's actual measured power consumption through the MPC (Monitoring of Power Consumption) use case instead of the announced OHPCF estimate, capturing the MPC entity from its support update and returning api.ErrNotAvailable when no measurement is available, matching the eebus meter implementation.
meter/eebus.go inlined the eebusapi.ErrDataNotAvailable to api.ErrNotAvailable mapping in eebusReadValue and readPhases. Both now use the existing eebus.WrapError helper so the mapping lives in a single place.
The OHPCF charger now reads and controls the heat pump compressor directly, so the server-side diagnostic logging is no longer needed. OHPCF use case events are forwarded straight to registered devices via ucCallback, like the other use cases.
|
@copilot resolve the merge conflicts in this pull request |
Done. I merged |
|
Closed due to missing feedback |
|
Why close this? This could be useful for Vaillant heat pumps, if there is enough surplus we could heat the hot water to a higher than standard temperature |
In case you've missed it: #30636 (comment) |
|
Did not realize that this is already in a state worth testing. In fact I am already failing to configure it... It looks like there is a new Does not seem to work either... Probably can't add EEBUS twice.. |
I think this might be related to this bug |
|
The following config works: So how do I test the feature now? DHW has just been loaded. So there is nothing to do for the heat pump right now. One thing that will sorely be missed opposed to the sensoNET charger is the DHW temperature display. There is however a HVAC use-case (MDT - Monitoring of DHW Temperature) which covers that. |
|
You can add integrateddevice capability, let me check on MDA. We announce flexibility for „now“ (really like FLOA) but its probably bot a good fit. |
|
Why charging never starts (from the trace) The heat-pump loadpoint (
Root cause: the compressor's So the gap is upstream in Two evcc-side notes:
🤖 Generated with Claude Code |
|
as a workaround for test we can do this |
Point eebus-go at andig/eebus-go fix branch (enbility/eebus-go#228) which registers the OHPCF use case event handler, so the compressor's SmartEnergyManagementPs feature is actually subscribed and process-state updates arrive. Revert to an upstream pseudo-version once #228 lands.
|
@CiNcH83 upstream bug enbility/eebus-go#228 fixed here, please give it another try. |
|
@TheEragon are you on Slack? |
Status returned StatusA when no compressor entity was present, which made a missing/disconnected compressor indistinguishable from an idle device. Return a "not connected" error instead so the loadpoint surfaces the condition.
Enabled, Enable and MaxCurrent silently returned a benign value when no compressor entity was present, hiding a disconnected device. Return the shared errNotConnected from every method that accesses the compressor.
The device template renders features: [integrateddevice], but the config struct rejected unknown keys, failing TestTemplates. Embed the shared embed struct so icon/features decode and the charger reports integrateddevice.
|
Still no DHW production even though there should be demand (T=43,5 °C). I had a boost running over an hour ago but stopped it to keep the demand for testing. I configured a blocking time for DHW production of 1h which is already over by now though. I am attaching the log but keep it running. Maybe something is still going to happen... |
|
@CiNcH83 can you share your full config please? I connected my Vaillant to evcc but I don't see the energy management menu And I can see that connection is working in logs, no idea what could be the problem. |
I know, but I don't see it in myVaillant app after I connect to evcc. I tried different EMS and with some it showed and with other didn't. I always thought it's because of the OHPCF but I guess it's something else :( |
|
Those settings are indeed weird. I feel like the cloud is sometimes out of sync with the real configuration. So if you just paired evcc it may very well be that Energymanagement only appears tomorrow. I also had it missing at times. Probably reappeared after enabling EnergyPLUS? Dunno. Vaillant hasn't been a super smooth experience for me so far... |
It appeared as a wallbox in the UI. Move the template to the heating group and add the heating feature so the loadpoint renders it as a heat pump.
Pick up enbility/eebus-go#228 follow-up that reads the OHPCF data on connect so the compressor's process state is populated instead of staying empty.
|
Both upstream fixes are working — the OHPCF data now flows (all Why it still doesn't start / loops: The heat pump offers a fixed, all-or-nothing 3850 W optional consumption ( On enable, the loadpoint offers its minimum current first:
That's the Standby + 1 m countdown loop. Root cause: OHPCF is a fixed-power on/off device, but it's modelled as a ramping charger:
Fix (charger-side): make it a true switch — Workaround to test right now, @CiNcH83: set the loadpoint min power ≥ 3850 W (the announced 🤖 Generated with Claude Code |
The compressor offers a fixed, non-modulatable consumption, but it was driven like a ramping charger: enabling deferred to MaxCurrent, which only scheduled once the loadpoint's offered power exceeded the request. With the loadpoint starting at minimum current that never held, so nothing was scheduled and Enabled stayed false, looping the loadpoint via repeated out-of-sync. Drive it on/off instead: Enable schedules/resumes or pauses/stops, Enabled reports the commanded intent, MaxCurrent ignores the offered current, and the surplus decision is left to the loadpoint. Drop the now-unused phase handling and loadpoint controller.
|
Good news: the control path is now correct end-to-end. In the latest trace evcc does send the schedule, there's no out-of-sync loop, and status holds at B. The remaining blocker is that the heat pump rejects the write: for what evcc writes: alternatives:[[{ powerSequence:[[
{ description:[{sequenceId:0}] },
{ schedule:[{startTime:"2026-06-25T12:27:58Z"}] }
]] }]]So the compressor stays
@CiNcH83 — could you check what Vaillant expects here? Specifically, from the Cross-manufacturer communication via EEBUS doc (or via Vaillant support):
That would let us fix the write encoding upstream. 🤖 Generated with Claude Code |
|
Forwarded. The startTime does not seem right though? More like 14:30. Another DST/time zone issue? |
|
Z is UTC |
Control writes passed a nil result callback, so a rejected schedule/resume/ pause/abort was silently dropped while the loadpoint assumed success. Wait for the write result with a timeout: log and return the error on rejection, and return a timeout error if no result arrives.
|
A log with the most recent change... |
|
Thank you, those logs are immensely helpful . |







Stacked on #30633 (eebus-go @dev bump) and based on the upstream OHPCF use case from enbility/eebus-go#223.
OHPCF (Optimization of Self-Consumption by Heat Pump Compressor Flexibility) lets evcc, acting as CEM, observe and control the optional power consumption a remote heat pump compressor announces.
Read (server/eebus):
usecases/cem/ohpcfand registers the use case on the CEM entity (reachable viaCustomerEnergyManagement).Charger (charger/eebus-ohpcf.go):
api.Charger). Status/Enabled are derived from the power consumption process state: running → charging, available/scheduled/paused → connected, else → disconnected.MaxCurrentconverts the loadpoint's allotted current to power (using the active phases fromloadpoint.Controller) and schedules the optional consumption to start now once the available surplus covers the compressor's requested power, pausing or aborting it otherwise. The schedule/resume/pause/stop decision is centralised in one guarded helper so an unchanged state issues no repeated commands.CurrentPowerreports the announced power while running.The control decision logic (state × surplus → action) and the status/enabled mapping are unit tested. The use-case wiring is exercised by the existing eebus integration handshake test.
go build,go vet, charger + eebus tests, and golangci-lint all pass.