feat: add EMON energy sensors via /heatSources/emon endpoints - #14
Open
engels0n wants to merge 2 commits into
Open
feat: add EMON energy sensors via /heatSources/emon endpoints#14engels0n wants to merge 2 commits into
engels0n wants to merge 2 commits into
Conversation
engels0n
force-pushed
the
feat/emon-energy-sensors
branch
from
August 5, 2026 09:50
9233458 to
d88ae1c
Compare
engels0n
force-pushed
the
feat/emon-energy-sensors
branch
from
August 5, 2026 09:55
d88ae1c to
1221db4
Compare
- api: EMON endpoints (/heatSources/emon/...), recording aggregation - api: distinguish 401 (expired token) from 403 (domain unavailable) - init: separate EMON coordinator, lock-guarded token refresh - sensor: 10 energy/COP sensors - config_flow: reauth via PKCE with fresh verifier and state - translations: reauth strings (en, de)
engels0n
force-pushed
the
feat/emon-energy-sensors
branch
from
August 5, 2026 12:53
505c969 to
23394db
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #8
Summary
Adds energy consumption and heat production sensors (kWh) to the integration,
exposed through the undocumented
heatSources/emonendpoints. All new energysensors are compatible with the Home Assistant Energy Dashboard.
Completely made with Claude Opus 5.
Background
Energy values are visible in the MyBuderus app but were never surfaced by this
integration. The obvious candidate path
/energyMonitoringconsistently returns403 Forbidden, which is what made this hard to find.
Things that did not work, documented here so nobody repeats them:
unsupported_grant_type. No additional token is needed; the regular accesstoken works fine against
pointt-api.The endpoints were finally recovered by decompiling the Android APK with jadx,
which exposed both the real paths and the enums used to build them.
API structure
Base host:
pointt-api(same host and auth as the existing calls). All EMONreads go through the existing
/resource/endpoint, so no new transport layerwas needed.
Lifetime counters — one path per domain, no separate domain segment:
Historical time series:
intervalisYYYY-MM-DD,YYYY-MMorYYYY.The API answers 403, not 404, for a domain the appliance does not have, so
403 cannot be treated as an authentication failure here. See the token section
below.
Enums (extracted from the decompiled client)
EmonTypech,dhw,cooling,pool,total, each also as a*Consumptionvariant.The
*Consumptionvalues are what the client puts into the path.SystemComponentDomainTypeCH,COOLING,DHW,POOL,TOTALSubDomainSourceTypeELECTRICITY,COMPRESSOR,EHEATER,BURNER,SOLAR,OUTPUT_PRODUCED— these appear as keys inside the response payload, and as the
{sub}segmentfor recordings.
This integration implements the four consumption paths above.
poolis definedin the app but not covered here, as no test system was available.
Response format
EmonValuereturns avaluesarray of single-key objects:{ "values": [ { "compressor": 27.35 }, { "eheater": 3.85 } ] }Keys are camelCase in the payload (
outputProduced) and are converted tosnake_case before being mapped onto entities.
yRecordingis used for the time-series endpoint and is aggregated by the newsum_recording()helper.Changes
api.pyget_emon()andget_emon_recording()sum_recording()helper for aggregatingyRecordingpayloads{domain}_electricis derived as compressor + eheater, andscopastotal_output_produced / total_electric. Neither value is returned by theAPI directly.
an empty sensor set
coordinator.py60-second poll. Lifetime counters change slowly, so there is no reason to poll
them at the normal rate. This keeps the additional API load minimal.
sensor.pydevice_class: energyandstate_class: total_increasing, so they can be selected in the Energy DashboardavailablereturnsFalsefor domains the system does not have (e.g. coolingon a heating-only unit), instead of surfacing permanent
unknownentitiesconst.pyTranslations
strings.json,de.jsonanden.jsonupdated with names for all new sensorsget_emon_recording()andsum_recording()are not yet called by any entity.They are included because the recording endpoint is the only way to obtain
historical data, which is a prerequisite for backfilling long-term statistics —
happy to drop them from this PR if you would rather keep the diff minimal.
New entities
total_electrictotal_compressortotal_eheaterch_compressordhw_compressordhw_eheatercooling_compressortotal_output_produceddhw_output_producedscopscopis the only non-energy entity: it is a dimensionless ratio, so it carriesno device class and uses
measurementinstead oftotal_increasing. It istherefore not selectable in the Energy Dashboard — the nine kWh sensors are.
Testing
Verified against a live heat pump. Example readings:
The numbers are internally consistent
(27.35 + 3.85 = 31.2 kWh; 55.55 / 31.2 ≈ 1.78 COP), which confirms the values
are being read and mapped correctly.
Token handling and re-authentication
While testing the new sensors, a pre-existing bug surfaced that also affects
installations without EMON:
Both coordinators refreshed the access token independently. When the token
expired (~1 hour), they raced — SingleKey rotates the refresh token on use, so
the second request received
invalid_grantand that coordinator failedsilently. Sensors went unavailable with no reauth prompt.
__init__.pyasyncio.Lock, with a double-check sothe waiting caller does not refresh again unnecessarily
api.pyBuderusAuthErrornow carries the HTTP status. 401 (expired token) ispropagated so the coordinator can trigger reauth; 403 is only skipped when
probing optional EMON domains
config_flow.pyasync_step_reauth/async_step_reauth_confirm—ConfigEntryAuthFailednow surfaces a repair card instead of failing silently
in with a different SingleKey account aborts with
wrong_accountrather thansilently rebinding the entry
Translations
strings.json,de.json,en.jsonCompatibility
rather than broken.
Screenshots (German UI)