Skip to content

[eebus][WIP] Add EEBus IO add-on (LPC/LPP power limits via item metadata) - #21313

Draft
stamateviorel wants to merge 9 commits into
openhab:mainfrom
stamateviorel:eebus-io
Draft

[eebus][WIP] Add EEBus IO add-on (LPC/LPP power limits via item metadata)#21313
stamateviorel wants to merge 9 commits into
openhab:mainfrom
stamateviorel:eebus-io

Conversation

@stamateviorel

Copy link
Copy Markdown
Contributor

Description

lets openhab present itself as an eebus controllable system (ship/spine, via openmuc's jeebus sdk) so a cem/ems or smart-meter cls gateway (§14a enwg in germany) can pair with it and issue lpc/lpp power limits. full backstory in #21211.

shaped as an io add-on instead of a binding per @kaikreuzer's feedback there - lpc/lpp are household-wide limits, not per-device, so there's no real thing to model. instead it's service level config (bind address/port, device id, trusted skis) same as io.homekit, plus eebus="lpc" / eebus="lpp" item metadata to bind a use case to whatever item you want, with nominalmax/failsafelimit/failsafeduration in the metadata config. when a paired cem sends a limit it gets pushed straight onto that item as a command so it plugs into whatever automation you already have.

this only lets openhab accept limits from an external cem/gateway, doesn't let it control other eebus devices like a real wallbox or heat pump - openmuc hasn't published a java sdk for that side yet.

supersedes an earlier thing-based binding prototype (mirror at stamateviorel/openhab-eebus-binding, kept for history, points back here now).

went through the add-ons review checklist (#14694) before pushing this.

Testing

13/13 unit tests pass, checkstyle/spotbugs/spotless/i18n all clean.

still no real eebus hardware or cls gateway involved, tested against meisel2000/eebus-cbsim (built on enbility/eebus-go, same stack evcc uses). pairing, mdns discovery, and spine picking up the lpc use case all work end to end including the dynamic entity.addusecase() registration this add-on relies on (added after the device is built, not at build time), correctly reporting back the nominalmax from the item's metadata. found one real bug doing this - building the device also adds an implicit device_information entity alongside the one i asked for, so the entity has to be picked by type not just taken as the first one.

still haven't gotten an accepted active limit write through to the item - same cbsim heartbeat bug as before, not this add-on's fault as far as i can tell.

opening this as a draft since @kaikreuzer suggested it, agree it shouldn't be called done until someone tests it against a real gateway. happy to leave it open for that.

stamateviorel and others added 3 commits August 2, 2026 00:13
Exposes openHAB as an EEBus Controllable System so a CEM/EMS or
smart-meter CLS gateway (§14a EnWG) can pair over SHIP and issue
LPC/LPP power limits, delivered as commands onto items tagged
eebus="lpc"/"lpp". Backed by OpenMUC's jEEBus SHIP/SPINE (EPL-2.0).

Shaped as an IO add-on rather than a binding since LPC/LPP are
household-wide singleton limits, not per-device Things - mirrors
org.openhab.io.homekit's service-config + item-metadata pattern.

Signed-off-by: Stamate Viorel <stamate.viorel@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…ATION too

Live-tested against eebus-cbsim: entity.addUseCase(LpcCs) threw
immediately because getEntities().iterator().next() picked up the
implicit DEVICE_INFORMATION entity Device.build() also adds, not the
requested CEM one. Select by type instead.

Signed-off-by: Stamate Viorel <stamate.viorel@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Documents what's actually been verified live (SHIP pairing, dynamic
entity.addUseCase() negotiation, metadata-driven nominalMax) vs. what
hasn't (an accepted active-limit write - blocked on a cbsim simulator
bug, not this add-on), and points at the superseded CS-binding mirror.

Signed-off-by: Stamate Viorel <stamate.viorel@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@jlaur jlaur added the new io label Aug 2, 2026

@wborn wborn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this. The overall approach of implementing the Controllable System side as an IO add-on with item metadata looks like a good fit, and it matches the direction discussed in #21211.

I found a few lifecycle/configuration issues that I think should be addressed before this is ready:

  • Pairing configuration changes currently do not affect the running SHIP node.
  • A full item-registry reload can add LPC/LPP use cases again to an entity that already has them.
  • Changes to nominalMax, failsafeLimit, or failsafeDuration on an already-bound item are silently ignored.
  • The LPP nominalMax default differs from the documented 4200 W default.

CI also currently fails because the feature should be named openhab-misc-eebus rather than openhab-io-eebus. There are a couple of smaller cleanup items such as the stale README status/build text and the compiler warnings, but I don't think those need separate review threads.

stamateviorel and others added 4 commits August 8, 2026 08:31
Matches the io.homekit/io.mcp/io.metrics convention - the feature
name follows the addon.xml <type> (misc), not the Maven artifactId's
io.* package. Was breaking CI.

Signed-off-by: Stamate Viorel <stamate.viorel@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
connectPolicy/trustedSkis/autoAcceptPairing were read once in
startNode() and never revisited - requiresRestart() excludes them
(by design, they shouldn't need a full SHIP node teardown), but
nothing then pushed a later change onto the running ShipCommunication
either, so e.g. adding a trusted SKI after boot silently had no
effect on the live node.

ShipCommunication's with*() methods mutate the instance in place and,
per its own bytecode, push straight into the underlying Ship object
when one is already connected - confirmed before relying on this,
since the earlier SimpleLimitationConfig bug came from trusting a
constructor's argument order without checking. Extracted
configurePairing() so both startNode() and modified() go through the
same path instead of duplicating it.

Also: unregister the ReadyTracker in deactivate() (was leaked), and
suppress the compiler warning on ShipNodeConfiguration - it's flagged
deprecated-for-removal since jEEBus.ship 2.3.0 but no replacement is
published in this version yet.

Signed-off-by: Stamate Viorel <stamate.viorel@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Two related bugs, both stemming from jeebus.spine having no API to
remove a use case once entity.addUseCase() has been called:

- A full item-registry reload reset the lpcItemName/lppItemName
  bookkeeping to null before rescanning, so an item that was still
  tagged got bound again - entity.addUseCase() a second time for a
  use case it already had attached. Fix: don't reset the bookkeeping;
  bind() already no-ops correctly when the same item is still bound,
  it just needed that state to survive the reload.

- Editing nominalMax/failsafeLimit/failsafeDuration on an
  already-bound item's metadata reached bind(), which also silently
  no-op'd (same guard) - so the change looked like it should apply
  but didn't, with no explanation. jeebus.spine has no way to
  reconfigure an already-registered use case, so this can't actually
  be applied live; now it logs a warning telling the installer to
  restart instead of just doing nothing.

Added EEBusChangeListenerTest (mocking Entity) asserting addUseCase()
is called exactly once per use case across both scenarios - the kind
of call-count assertion that would have caught this immediately.

Signed-off-by: Stamate Viorel <stamate.viorel@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
The README documented 4200W as the default nominalMax for both LPC
and LPP, but LPP's real (intentional) default is 0 - claiming export
capacity that was never configured would misreport this
installation's real capability to the CEM, same reasoning the
original Thing-based binding already used for this default. The code
was right, the docs weren't; no test locked in the LPP empty-config
case either, so added one alongside the existing LPC one.

Also updates the status banner: PR number, test count, and the
pairing-settings/restart wording now that they're accurate again.

Signed-off-by: Stamate Viorel <stamate.viorel@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@stamateviorel

Copy link
Copy Markdown
Contributor Author

thanks for the thorough review, all four fixed:

  • pairing settings now actually apply live. turns out ShipCommunication's with*() methods mutate in place and push straight into the running Ship object if one's already connected, so startNode() and modified() both go through the same configurePairing() now instead of only doing it once at startup.
  • the reload double-registration was literally just two lines resetting the item-name bookkeeping before rescanning - removed them, the existing "already bound" guard in bind() then does the right thing on its own since jeebus has no addUseCase removal anyway.
  • same root cause for the silent config-update no-op - now it logs a warning telling you to restart instead of pretending nothing happened. didn't try to actually reconfigure the running use case live, jeebus doesn't expose a way to do that on an already-attached LpcCs/LppCs as far as I could find, so warning + restart felt like the honest option over guessing at some lower-level workaround.
  • the zero for lpp is intentional actually, carried over from the original binding - claiming export capacity you haven't configured would misreport what the installation can actually feed back. the code was right, the readme table was wrong, fixed that and added the missing empty-config test for lpp to match the lpc one.

feature is renamed to openhab-misc-eebus, added a test that mocks Entity and asserts addUseCase() call counts across both bug scenarios, and cleaned up the two compiler warnings you flagged (unused readyService field - now actually used to unregister the tracker on deactivate - and suppressed the ShipNodeConfiguration deprecation since there's no replacement published yet).

18/18 tests pass now.

stamateviorel and others added 2 commits August 8, 2026 10:39
…nfiguration

ShipNodeConfiguration is @deprecated(forRemoval = true), which ECJ
categorizes as "removal", a distinct warning category from plain
"deprecation" since Java 9 - the wrong key both left the actual
warning unsuppressed AND got itself flagged as an unnecessary
annotation. Only showed up building through the full reactor (as CI
does via -pl -am), not building the submodule standalone, which is
how the CI-only failure got past me building it as I went - confirmed
by reproducing the exact CI build command locally before trusting the
fix.

Signed-off-by: Stamate Viorel <stamate.viorel@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…a -am

Both invisible in a standalone 'mvn package' of this module - only
showed up reproducing CI's actual reactor-scoped build command
(./mvnw verify -pl :org.openhab.io.eebus -am -amd), which is what
finally caught them:

- jackson-annotations was pinned to ${jackson.version} (2.21.4) in
  feature.xml, but that Jackson module doesn't publish patch releases
  the way jackson-core/databind do - 2.21.4 doesn't exist, only plain
  2.21. The reactor's root pom already has a dedicated
  ${jackson.annotations.version} property for exactly this (used by
  several other bundles) - was just using the wrong one.

- bcprov/bcpkix are direct pom.xml deps (embedded into this bundle's
  jar via bnd), but bcutil-jdk18on was left as a separate external
  feature.xml bundle. It needs org.bouncycastle.asn1 packages that
  bcprov/bcpkix used to export as real OSGi bundles - once those two
  became embedded/private instead, nothing was left to satisfy that
  import. org.openhab.io.homekit embeds all three BouncyCastle jars
  together for the same reason; matched that instead of the
  inconsistent partial-embed.

Reproduced CI's exact build command locally before pushing this time
(not just a standalone module build) - confirmed BUILD SUCCESS
including the karaf-feature-verification goal, 18/18 tests.

Signed-off-by: Stamate Viorel <stamate.viorel@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@lsiepel lsiepel linked an issue Aug 10, 2026 that may be closed by this pull request
@wborn wborn added the work in progress A PR that is not yet ready to be merged label Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new io work in progress A PR that is not yet ready to be merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[eebus] Add support for the eebus

3 participants