Skip to content

POINTTAPI: optional entity discovery, switch value fixes, and translation updates - #18

Merged
CaseyRo merged 18 commits into
CaseyRo:masterfrom
jfhautenauven:features/CleanupAndImprovements
Aug 17, 2026
Merged

POINTTAPI: optional entity discovery, switch value fixes, and translation updates#18
CaseyRo merged 18 commits into
CaseyRo:masterfrom
jfhautenauven:features/CleanupAndImprovements

Conversation

@jfhautenauven

Copy link
Copy Markdown
Collaborator

Summary

This PR improves the POINTTAPI experience by exposing only relevant entities, fixing switch state mappings, and completing translation consistency updates.

What changed

  • Added optional POINTTAPI entities when advertised/available:
    • sensor.energy_efficiency (/gateway/ui/eco) when referenced by /gateway/ui.
    • sensor.return_temperature.
    • sensor.zigbee_firmware_version when /gateway/zigbee/versionFirmware exists.
    • number.annual_electricity_goal when /energy/electricity/annualGoal exists.
    • number.annual_gas_goal is now also conditional on /energy/gas/annualGoal presence.
  • Improved thermostat valve presentation:
    • Battery ok value is normalized to OK.
    • Zone label is resolved from /zones/zn*/name when available.
    • Signal icon set to mdi:signal.
  • Aligned electricity average sensors metadata with energy semantics:
    • Proper energy unit and statistics metadata (today/month reset behavior) consistent with gas daily metrics.
  • Fixed POINTTAPI thermal disinfect switch state mapping:
    • /dhwCircuits/dhw1/thermalDisinfect/state now uses on/off (instead of default true/false) to correctly reflect UI state.
  • Translation updates across supported locales (strings.json + locale files):
    • Added missing keys for new entities.
    • French wording fix for thermal disinfect last result: Effectuée.

Tests

  • Updated and extended targeted tests in unittests/test_pointtapi_new_entities.py for:
    • conditional entity discovery,
    • switch value mappings (on/off),
    • metadata and translation behavior.
  • Local targeted suite passes.

jfhautenauven and others added 7 commits August 10, 2026 16:02
Use live solar resource availability instead of root references when deciding whether to create POINTTAPI solar entities. Add regression coverage for unavailable resources and valid zero values.
…bility

Fix conditional Solar entity creation
# Conflicts:
#	custom_components/bosch/sensor/__init__.py
@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Update: we have added boiler appliance-code interpretation in this PR.

What was added:

  • New interpreted status sensor based on /system/appliance/causeCode + /system/appliance/displayCode.
  • Extended mapping for many additional Buderus code pairs (including display variants like A/-A, 2E/2P, 3Y, and EL/EC/EA families).
  • Fallback behavior by causeCode when displayCode is missing or variant.
  • Specific handling for the internal-error range while preserving exact mapped codes when known.
  • Full translation coverage for the new status keys across all supported locales.
  • Regression tests updated to validate key mappings and fallback behavior.

Example now covered explicitly: 203 + 0H => standby / no heat demand (normal operating state, not a fault).

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Follow-up update in this PR:

  • sensor.energy_efficiency (/gateway/ui/eco) is now explicitly routed to the Energy performance device instead of the Gateway.
  • Implementation detail: added explicit device_info_fn for /gateway/ui/eco with kind="energy_efficiency" and included this kind in _ENERGY_KINDS routing.
  • Added regression test to verify device attachment (uuid*_energy) and localized device name behavior.
  • Cleaned a test-only unused import flagged by Ruff.

Result: energy efficiency now appears under the correct device in Home Assistant.

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Follow-up UX tweak included in this PR:

  • Added custom icon for current modulation power sensor:
    • /heatSources/actualModulation (actual_modulation) -> mdi:signal-cellular-2
  • Added custom icon for ignition starts sensor:
    • /heatSources/numberOfStarts (boiler_ignition_starts) -> mdi:reload

Why: improve visual discoverability/readability in the entity list and device pages without changing entity IDs or behavior.

Validation: Ruff check passes on custom_components/bosch/pointtapi_entities.py.

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Follow-up UX cleanup for POINTTAPI zone climate entities:

  • Removed the exposed climate preset control (program / manual) from HA UI.
  • Kept only climate mode control (Auto / Heat / Off) to avoid duplicate controls on zones.

Behavior is preserved (no functional regression):

  • Auto still maps to userMode = clock
  • Heat still maps to userMode = manual
  • Off still maps to userMode = manual + minimum setpoint

So this is a UI simplification only, with the same underlying POINTTAPI semantics.

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Follow-up fix for burner flame state on POINTTAPI:

  • burner_flame is now derived from /heatSources/actualModulation instead of strict parsing of /heatSources/flameIndication.
  • Behavior is now:
    • modulation > 0 => flame ON
    • modulation == 0 => flame OFF
    • missing/invalid modulation => unknown
  • Added regression tests for dhw/ch flame-indication contexts and modulation-based ON/OFF mapping.

Why: real dumps show /heatSources/flameIndication can return values like off, ch, or dhw; strict on/off parsing caused unexpected unknown states.

Note/caveat:

  • This implementation is validated with gas-boiler dumps.
  • It may need refinement for end users with a heat pump (no literal "flame" concept).
  • I currently don’t have a heat-pump dump to validate whether modulation semantics should be adapted for that device type.

@jfhautenauven

jfhautenauven commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Update implemented and pushed (commit c655ac1): added the new boiler sensor Heat Demand Type based on /heatSources/flameIndication.

Details:

New translated sensor: heat_demand_type
User-friendly mapping: off → no demand, ch → heating, dhw → DHW
Returns None for an unknown value (so no incorrect state is displayed)
Completed i18n coverage (strings + locales)
Added tests covering the description, mapping, and unknown-value fallback

Dump verification:

Observed raw values: off (most common), ch, dhw
The mapping is therefore aligned with the values actually received

Local validation:

pytest unittests/test_pointtapi_new_entities.py: 70 passed
ruff on modified files: OK

Heat pump (PAC) caveat:

The binary flame sensor intentionally remains based on actualModulation > 0 to avoid false unknown states depending on heat pump variants; this new Heat Demand Type sensor complements the business information without changing this robustness logic.

- Add requested Bosch/Buderus display+code combinations to appliance status mapping
- Add new state keys for low fan speed, gas-shutdown flame detection, safety-time faults, and KIM recognition
- Extend base strings and all locale translations (en/de/fr/it/nl/pl/sk)
- Expand pair-coverage unit test with newly requested combinations
@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Implemented and pushed in commit 02c8f7b.

This update extends the POINTTAPI appliance-status mapping with the additional display/code combinations you requested, including:

  • 3L/214, 3P/216, 3C/217
  • 4U/222, 4Y/223
  • 6C/306
  • 7L/261, 7L/280
  • 9L/234, 9L/238, 9P/239
  • EL/259
    (plus the previously added related pairs already discussed)

I also added the new state keys needed for these mappings:

  • fan_speed_too_low
  • flame_detected_after_gas_shutdown
  • first_safety_time_fault
  • restart_attempt_time_fault
  • kim_not_recognized

Localization is now present in all supported locales (en, de, fr, it, nl, pl, sk), and the pair-coverage test was expanded to lock these combinations in.

Validation:

  • Targeted suite passed: unittests/test_pointtapi_new_entities.py (80 passed, 0 failed)
  • No diagnostics/syntax errors in touched files.

Note on 8Y/232: the same pair is used for multiple real-world textual interpretations. With the current payload fields, there is no reliable discriminator, so we keep one stable mapped status for that pair.

- Add dynamic select entities for /zones/{id}/clockProgram
- Build select options from decoded /programs names
- Map selected display label back to numeric clockProgram id for writes
- Add localization key for assigned program select in all supported locales
- Extend tests for discovery, decoded display value, and write path
@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Implemented and pushed in commit ceca83c.

This update adds writable, per-zone program selects for POINTTAPI using each zone’s clockProgram resource.

What is included:

  • Dynamic select discovery for all configured zones (/zones/{id}/clockProgram)
  • Human-readable select options from decoded program names (/programs/.../name)
  • Safe write path: selected label is mapped back to the correct numeric clockProgram value before PUT
  • Full localization coverage for the new select label (assigned_program_select) across all supported locales
  • Unit test coverage for:
    • dynamic select discovery by zone,
    • decoded current option rendering,
    • write behavior to the expected API path/value

Validation:

  • Targeted test suite passed: unittests/test_pointtapi_new_entities.py (83 passed, 0 failed)
  • No diagnostics/syntax errors in modified files

Safety note:

  • The select only writes known options derived from coordinator data; unsupported options are rejected with a clear error instead of issuing an ambiguous API write.

- Add dynamic per-zone sensor for /zones/{id}/optimumStartState
- Keep raw state value (no interpretation mapping yet)
- Discover sensors from zone references to avoid ghost entities
- Add translation key for all supported locales (en/de/fr/it/nl/pl/sk)
- Add tests for discovery and raw-value passthrough
@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Implemented and pushed in commit 1cdf109.

This follow-up adds a new POINTTAPI per-zone diagnostic sensor for optimum-start state:

  • Path: /zones/{id}/optimumStartState
  • Behavior: raw value is exposed as-is for now (no interpretation/mapping layer yet)
  • Discovery: reference-driven from each /zones/znX resource to avoid creating entities for non-advertised paths

Localization:

  • Added optimum_start_state sensor label in all supported locales:
    • en, de, fr, it, nl, pl, sk

Validation:

  • Targeted tests passed: unittests/test_pointtapi_new_entities.py (85 passed, 0 failed)
  • Added tests for:
    • dynamic discovery from zone references
    • raw value passthrough (idle, etc.)
  • No diagnostics/syntax errors in modified files.

@jfhautenauven

jfhautenauven commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Hey @CaseyRo ! I had a little bit of motivation left, so I went ahead and pushed a few more improvements to PR #18 😄

It now also includes some additional appliance status mappings, the heat demand type sensor, a couple of UX/icon tweaks, per-zone program selection, and the optimum start state sensor.

As a bonus : found internal documentation about error codes for Bosch / Buderus error codes so I added an interpreter of the faults based on the descriptions found in the manufacturers documentation.

Nothing dramatic, just kept going while I was in the code 😅

PR: [#18](https://github.com/CaseyRo/ha_bosch/pull/18?utm_source=chatgpt.com)

Move assigned_program_select out of switch.open_window_detection and into entity.select for en/de/fr/it/nl/pl/sk to satisfy hassfest translation schema.
@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Quick update: I fixed the hassfest translation schema failure.

What was wrong:

  • assigned_program_select had been inserted under entity.switch.open_window_detection in locale files, which violates Home Assistant translation schema.

What I changed:

  • Moved assigned_program_select under entity.select in all affected locales:
    • en, de, fr, it, nl, pl, sk
  • Removed the misplaced nested key from entity.switch.open_window_detection.

Validation:

  • Targeted tests still pass locally: unittests/test_pointtapi_new_entities.py (85 passed, 0 failed).
  • Updated locale files report no diagnostics.

This should unblock hassfest for PR #18.

Two review findings on the new appliance-status and electricity metadata.

The cause-only fallback kept the first-seen mapping when several display
codes share a cause. Nine causes have display variants that disagree, and
two of them turn a normal state into a fault: cause 273 is a 24h safety
shutdown under display 3F but flame monitoring under 0U, and 280 is a
restart-time fault under 7L but a fan start under 0U. 0U codes 270-274,
280 and 281 are the normal ignition sequence, so a boiler that is merely
starting up with no display code would report a safety shutdown. Build
the fallback only from causes whose variants agree; ambiguous ones read
unknown and keep their raw codes on the entity attributes.

Also revert the electricity dayAverage/monthAverage promotion to
device_class=ENERGY + state_class=TOTAL + last_reset. The paths are named
average, and an average that falls as well as rises is not a TOTAL: HA
reads each decrease as a meter reset and the sensor becomes selectable as
an Energy Dashboard source. Wrong long-term statistics are painful to
unwind, so these stay plain sensors until someone watches the value
across a full day on real hardware.
@CaseyRo

CaseyRo commented Aug 12, 2026

Copy link
Copy Markdown
Owner

@jfhautenauven "a little bit of motivation left" produced 2,100 lines, an error-code interpreter sourced from manufacturer docs, and a hassfest fix you caught yourself. That's a strange definition of holiday, and I'm not complaining 😅

Reviewed the whole thing. CI green, ruff clean, 365 tests passing. The valve work lands especially well — see below. I pushed one commit (c23f4fb) with two changes, both defensive rather than corrections of your judgement.

1. The cause-only fallback could raise a fault for a healthy boiler.

_build_appliance_status_by_cause keeps the first-seen mapping when several display codes share a cause. Nine causes have variants that disagree, and two flip a normal state into a fault:

cause picked but also
273 safety_shutdown_after_24h_continuous_operation (3F) flame_monitoring (0U)
280 restart_attempt_time_fault (7L) fan_starting (0U)

0U 270–274/280/281 is your normal ignition sequence. So a boiler that's simply lighting up, reporting a cause with no display code, would announce a 24-hour safety shutdown. People wire notifications to that.

I changed it to only build the fallback from causes whose variants agree — ambiguous ones read unknown and keep display_code/cause_code in the attributes, so nothing is lost for debugging. If your documentation says a bare 273 really does mean the 3F reading, put it back — you have the source and I don't. I optimised for "don't cry wolf" in the absence of that.

2. Reverted the electricity average metadata, and this one is a genuine question for you.

You promoted /energy/electricity/dayAverage and monthAverage to device_class=ENERGY + state_class=TOTAL + last_reset. If those paths accumulate, that's exactly right. If they're what the name says — averages — then TOTAL is a problem: HA reads every dip as a meter reset, and the sensor becomes selectable as an Energy Dashboard source. Bad long-term statistics are miserable to unwind afterwards, which makes this asymmetric enough that I'd rather wait.

The tests assert the metadata we set, so they can't settle it. Does that number climb through the day and drop at midnight, or does it wander up and down? One glance at your history graph decides it, and I'll put the metadata straight back.

What ships when

Stable 1.3.0 goes out today with the #17 content — @LukyHurdy1 confirmed it on his 12-zone Czech setup this morning: battery ok, signal 69%, protocol homematicip, zone 2. Your valve telemetry works on hardware neither of us owns 🎉

This PR then opens 1.4.0-beta.1 on its own line. Same rule as last time: verified work ships, unverified work carries a version number that admits it.

And your zone-name resolution is validated by that very screenshot — his valve currently reads zone 2 where it should read the room name, and battery ok next to a warning sensor saying OK. You fixed both before anyone reported them. Nice instinct.

Go and enjoy the beach properly. 🏖️

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Thanks Casey, I fully accept the safeguard on ambiguous cause-only fallback. Preventing false fault alarms is the right default until we have definitive mapping evidence.

I also agree on publishing this as a beta so I can validate behavior end-to-end on my setup before we promote further.

For the electricity values: I do want those day/month averages visible to users, but strictly as informational sensors. We should keep them implemented in a way that does not participate in long-term statistics or Energy Dashboard calculations unless we can confirm they are truly cumulative/monotonic in real hardware observations.

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Follow-up pushed.

We accept Casey’s remarks; they are fully justified and highlighted something I had completely overlooked.

I kept Casey’s defensive approach for the electricity averages (informational only, no statistics metadata) because he is right on the HA long-term statistics risk.

I only reintroduced a small UX improvement: the kWh unit of measurement, so the values remain clear to users while still staying out of Energy Dashboard/statistics behavior.

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

@CaseyRo : see the above :) ready for a new beta :)

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

@CaseyRo : hey :) don't forget about this pull request :)

@CaseyRo

CaseyRo commented Aug 17, 2026 via email

Copy link
Copy Markdown
Owner

CaseyRo added a commit that referenced this pull request Aug 17, 2026
… zone program selects

Contributed by @jfhautenauven. Conditional entity creation (energy efficiency,
return temp, zigbee firmware, electricity annual goal), appliance status sensor
backed by a manufacturer display/cause code table, per-zone assigned-program
selects and optimum-start sensors, thermal-disinfect switch on/off mapping, and
translation updates across all 7 locales.
CaseyRo added a commit that referenced this pull request Aug 17, 2026
PR #18 (appliance status, zone program selects, conditional entity discovery)
ships on its own line so it can be confirmed on real hardware first. The
display+cause status table is transcribed from manufacturer documentation and
has not been observed against a live boiler.
@CaseyRo
CaseyRo merged commit 88e88f6 into CaseyRo:master Aug 17, 2026
3 checks passed
@CaseyRo

CaseyRo commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Merged, and sorry for the four-day silence — you were waiting on me, not the other way round.

Shipped as v1.4.0-beta.1. Same
rule as last time: the display+cause table is transcribed from documentation rather than observed
against a live boiler, so it carries a version number that admits it until someone runs it.

Your follow-up on the electricity averages was exactly right — kWh for readability, no
device_class/state_class, so the values are visible without becoming an Energy Dashboard
source. That is the version I would have asked for.

I ran one more pass over the diff before merging, hunting complexity rather than bugs, and applied
two things in bd6ee64:

_program_names_by_index read /programs/list, which never exists. Reasonable assumption —
/devices really does advertise a list reference, so the symmetry is tempting. But /programs
advertises its programs as pgN references instead, and _program_roots consumes that listing to
pick walk roots without ever storing the response. So the branch was unreachable, which is also
why no test covered it. The /programs/pgN/name loop below it was doing all the work already.

A dead _attr_options = [] in the select entity, overwritten two lines later by
_refresh_supported_options().

The rest I wrote up as #19 rather than touching your code further. The one I would actually like
your eye on is the first item: _pointtapi_number_descriptions re-indents the entire static tuple
to append two conditional entries, while _pointtapi_select_descriptions — same problem, same PR —
does it in six lines by returning CONSTANT + dynamic. Your select version is the better pattern;
the number one just needs to copy it.

Nothing there is urgent and nothing there is a bug. 369 tests green, ruff clean, hassfest and HACS
happy. Thanks again — this was a big one. 🙏

@CaseyRo

CaseyRo commented Aug 17, 2026

Copy link
Copy Markdown
Owner

@jfhautenauven follow-up on the electricity averages — I tried to answer my own question and hit a wall that I think you can clear in about thirty seconds.

I went to check my own history for dayAverage, expecting to finally see whether it ramps or wanders. There is no history, because there is no entity. Here is what my CT200 actually reports:

/energy/electricity/annualGoal    available: "true"   value 0.0
/energy/electricity/dayAverage    available: "false"  value 0.0   unitOfMeasure "kWh"
/energy/electricity/monthAverage  available: "false"  value 0.0   unitOfMeasure "kWh"
/energy/electricity/price         used: "false"       value 0.0

The paths resolve in the reference walk, so they show up in diagnostics, but available: "false" means your own gating in _pointtapi_electricity_average_sensor_descriptions correctly suppresses them. The code is doing exactly the right thing — my gas-only boiler has no electricity metering, so there is nothing to report. It just means my hardware cannot settle this question, and I had been assuming it could.

So: what does your gateway say for those two paths? Settings → Devices & Services → Bosch → ⋮ → Download diagnostics, then search for dayAverage. The available flag is the whole answer:

  • "true" → you have the entity, and a few days of History settles it. What I need to see is whether the line is a monotonic ramp that sawtooths to zero at midnight (a genuine cumulative total → state_class=TOTAL + last_reset is right, and I will put the metadata back immediately), or flat-ish and drifting within the day (a running average → current metadata stays).
  • "false" → then neither of us has ever seen these entities render, and we have been carefully debating the statistics metadata of a sensor that has never appeared on anybody's dashboard. Worth knowing before either of us spends another minute on it.

One thing this did confirm, and it is yours: the gateway declares unitOfMeasure: "kWh" itself. Your follow-up commit adding the kWh unit is backed by the primary source, not just a sensible guess. 👍

I also fixed the doc line that sent me down this path — docs/pointtapi-api.md listed /energy/electricity/* under "verified present on CT200", which is true and useless. Present and available are different questions, and that section now says so, with the live values recorded (c0296d4).

Unrelated bonus from the same dump: /energy/electricity/annualGoal is available on my CT200, so the number.annual_electricity_goal you added in #18 will appear on my install as soon as I move to 1.4.0-beta.1 — something from this PR I can verify on my own hardware. /energy/oil/annualGoal is available too, if you ever get bored. 🛢️

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

@CaseyRo : I implemented that for people having hybrid setups. This is for those scenarii that the values become relevant. However, I only have a gas boiler setup at home, so I cannot for sure verify if the values are ramping up correctly for the people that have a hybrid setup (Gas + Electric heat pump assistance)

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

@CaseyRo : so far so good with the testing of 1.4.0 beta 1 and I don't see any issues, everythings works as intended. I've seen improvements points and taken into account the things you listed in the issue #19. Pull request will follow soon enough.

There is something puzzling me still : this is what I see on my main thermostat device (CT200) :

image

I don't understand what that switch does ... looking at the code, I guess it is something related with boost ... but the labeling, IMHO isn't user friendly at all. Mind taking a look ? :)

CaseyRo added a commit that referenced this pull request Aug 26, 2026
PR #20 (thermostat-valve entities, fast/slow polling, translation parity,
#19 cleanup) ships on the 1.5.0 line, superseding 1.4.0-beta.1 which never
went stable. The #18 appliance-status table still awaits hardware
confirmation; #20 ran in a 12-valve production install for a week.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants