Skip to content

Fix POINTTAPI services and beta-2 thermostat regressions - #25

Merged
CaseyRo merged 16 commits into
CaseyRo:masterfrom
jfhautenauven:features/CleanupAndImprovements
Aug 27, 2026
Merged

Fix POINTTAPI services and beta-2 thermostat regressions#25
CaseyRo merged 16 commits into
CaseyRo:masterfrom
jfhautenauven:features/CleanupAndImprovements

Conversation

@jfhautenauven

@jfhautenauven jfhautenauven commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Summary
This PR addresses the original bug from #24 and the regressions introduced in the beta-2 cleanup for POINTTAPI devices.

  1. Fix the generic Bosch service bug
    send_custom_get and send_custom_put_* were still routing through the legacy XMPP/raw gateway methods even when the entry was using the POINTTAPI cloud protocol. That made cloud-based calls fail at runtime.

This change:

routes POINTTAPI custom get/put calls through the cloud client
keeps the local XMPP/HTTP path unchanged
adds bosch.refresh_gateway as the clearer service name
preserves bosch.update_thermostat as a deprecated compatibility alias
2) Fix the beta-2 regressions
The beta-2 valve/device cleanup introduced a second issue: thermostat child-lock and related paths could be assigned to a fake thermostat_valve device even when the underlying device type was just thermostat.

This caused orphan devices such as a dedicated Thermostat valve entity group with only the child-lock switch, and it also increased the chance of duplicate or mismatched registrations.

This change:

only creates dedicated thermostat-valve devices for actual thermostat_valve rows
falls back to the generic device routing for thermostat-only nodes
keeps the child-lock/offset entities attached to the correct gateway/device tree
Testing
Added/updated regression tests for POINTTAPI service routing and thermostat valve discovery
Verified the targeted suite passes
Verified the repo lint/test checks remain green for the relevant scope
Fixes #24

- Bind dynamic thermostat-valve child lock switches to per-device info instead of gateway fallback
- Support thermostat-style device paths (deviceN/thermostat/*) alongside etrv paths for valve fields
- Enforce strict child-lock switch creation on concrete /childLock/enabled leaves only
- Add/update regression tests for enabled-leaf discovery and thermostat path variants
…val (63b80d9): unittests/test_pointtapi_setup.py:62 and :95 still define async_load_persistent_cache on the fake coordinators — the real method is gone, so the stubs are dead. Delete both when next in that file."

On issue CaseyRo#19
- Update childLock/enabled test payloads to stringValue with string true/false and metadata fields\n- Add explicit childLock false->off mapping assertion\n- Keep strict enabled-leaf discovery behavior for child lock switches
@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

@CaseyRo ... i'm waaaay too enthusiastic ... sorry :D 🙈

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

This PR fixes the original POINTTAPI service bug from issue #24: generic Bosch calls were still hitting the XMPP/raw gateway path, which breaks cloud devices. The fix routes send_custom_get and send_custom_put_* through the POINTTAPI client when needed, and adds refresh_gateway while keeping update_thermostat as a deprecated alias for compatibility.

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

A second regression was introduced in the beta-2 cleanup: thermostat child-lock and offset paths could be attached to a fake thermostat_valve device even when the underlying device type was actually thermostat. This patch prevents orphan Thermostat valve devices and keeps those entities on the correct generic device path instead.

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

This PR fixes the original POINTTAPI service bug from issue #24 and the beta-2 regressions introduced during the cleanup pass.

  • The service bug was that generic Bosch calls still used the legacy XMPP/raw path even for POINTTAPI, so cloud devices could not use send_custom_get and send_custom_put_* correctly.
  • The beta-2 regression was that thermostat child-lock and offset paths could be attached to a fake thermostat_valve device even when the underlying device type was thermostat, creating orphan devices like the extra valve child-lock entity.

The patch keeps the old service names working as deprecated aliases, adds the clearer refresh_gateway name, and ensures real thermostat-valve devices are only created for actual thermostat_valve rows.

@jfhautenauven jfhautenauven changed the title Fix POINTTAPI generic services and add refresh_gateway service (#24) Fix POINTTAPI services and beta-2 thermostat regressions Aug 26, 2026
@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

@CaseyRo I found and fixed one more issue related to the beta-2 child-lock regression, and pushed the fix in commit eafca2d.

The previous fix correctly identified the /childLock/enabled path, prevented the creation of the incorrect dedicated Thermostat valve #1 device, and kept the child-lock entity on the correct device. However, the POINTTAPI coordinator was only walking references far enough to fetch the childLock parent node. It was not fetching the nested /childLock/enabled leaf, so the switch was correctly discovered but remained unavailable in Home Assistant because its value was missing from coordinator.data.

This update makes the coordinator follow the additional reference level required by the real device structure:

device -> etrv -> childLock -> enabled

I also added a regression test based on that structure. The focused coordinator and entity tests now pass (152 tests).

Sorry for multiplying the fixes, but I keep finding these beta-2 regressions while testing with a real multi-zone setup. I wanted to report and correct them rather than leave the PR with partially working child-lock entities.

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

@CaseyRo I found one more cleanup issue related to the beta-2 thermostat-valve regression and pushed the fix in commit 3b61e21.

Home Assistant keeps devices in its device registry even after the integration stops creating them. As a result, the old erroneous Thermostat valve #1 device could remain visible even though the entity-routing fix prevented it from being recreated.

The integration now performs a targeted cleanup after the first successful POINTTAPI refresh. It checks the currently reported /devices/deviceN/type values, identifies old *_trv_<id> devices whose current type is no longer thermostat_valve, removes their stale entities, and then removes the obsolete device from the Home Assistant registry. Real thermostat-valve devices are left untouched, and cleanup is skipped when no device type information is available to avoid deleting valid devices during an incomplete refresh.

I also added a regression test for this exact case. The focused setup and cleanup tests pass, and Ruff reports no issues for the changed files.

@CaseyRo CaseyRo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@jfhautenauven Verified on my side: on current master, ruff clean, 557 green, CI green. The #24 fix is exactly right — the protocol branch in custom_get / custom_put, and thermostat_refresh becoming a coordinator refresh on cloud entries closes the question I'd left open. The dedupe and the thermostat-vs-thermostat_valve routing fix are the two things beta.2 actually needs, and the extra reference level explains the unavailable switch cleanly. Nice detective work for one evening. 🙏

One change request, one nudge, then I merge and tag beta.3.

The change: _remove_stale_pointtapi_valve_devicesasync_remove_config_entry_device

The 45 lines in __init__.py exist because HA shows no delete button on our devices — we never implemented async_remove_config_entry_device. That hook is the idiomatic answer and it's a few lines:

async def async_remove_config_entry_device(hass, entry, device_entry) -> bool:
    """Allow deleting devices the gateway no longer reports."""
    ...  # True when none of device_entry.identifiers is still backed by coordinator.data

With it, the fake "Thermostat valve #1" gets a Delete button in the device page, and so does every other stale device we've ever left behind (the orphaned firmware-update-state sensor from 1.2.0 included). Without it, we run registry deletions on every boot, forever, for a device that existed for about three hours in a pre-release — and any future mismatch between /devices/deviceN/type and our identifiers deletes real entities. Registry writes on the startup path are the one place I'd rather be boring.

So: drop _remove_stale_pointtapi_valve_devices and its test, add the hook (a test that it returns True for a _trv_ identifier absent from data and False for a present one is plenty), and the release note tells beta.2 users to delete the ghost device once.

The nudge: you don't need my tag to test

"Change → wait for beta → test → repeat" is costing you more than me. Your HA can run your branch directly: copy custom_components/bosch/ from your checkout over the installed folder and restart, or add jfhautenauven/ha_bosch as a second HACS custom repository and pick the branch. Then the child-lock write — which 403'd through HA on beta.1 and is still only proven via your Jeedom tooling — gets a real end-to-end check before it ships a third time, and I tag when you say "confirmed" instead of the other way round.

Everything else stays as is, refresh_gateway alias included. 🚀

@jfhautenauven

jfhautenauven commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks @CaseyRo, I’ve addressed the requested change in commit 72592b9.

I removed _remove_stale_pointtapi_valve_devices and the startup registry deletions, and added Home Assistant’s async_remove_config_entry_device hook instead. It now allows a POINTTAPI thermostat-valve device (<uuid>_trv_<id>) to be deleted from the device page when that valve is no longer present in the coordinator data, while keeping it when the gateway still reports it. The old cleanup tests were replaced with focused tests for the absent/present cases, and the beta.3 changelog tells beta.2 users to delete the existing ghost device once.

Regarding the nudge: I’ll experiment with that next month, running my branch directly in my Home Assistant setup and do a real end-to-end check. I’m still discovering how to develop properly for Home Assistant, so this is a useful next step for me. I’ll report back once I’ve tested it. For the time being, given the time I got left in my hands, well, i'll try it later :)

Child lock is not a feature that is specially demanded anyways, so it's not vital it works immediately. In this beta 3 I will get back the custom get and put, and those will help me a lot to explore the missings paths in my dumps :)

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Quick follow-up: I was able to test the child-lock write on my local Home Assistant setup, and it works perfectly. The child lock is confirmed working end to end.

I’m still having difficulties with the stale devices, though: they are not being removed as expected. I’m continuing to investigate that part and will report back once I understand what is blocking it.

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

One more small addition is now pushed in commit 06f41e7.

POINTTAPI devices created by the integration now expose Bosch as their manufacturer in Home Assistant. This applies to the thermostat-valve devices and the other routed POINTTAPI child devices as well, keeping the device registry presentation consistent. I also added regression assertions for the manufacturer metadata.

The focused tests pass (131 passed) and Ruff is clean. I also synchronized master and features/CleanupAndImprovements; both branches now point to the same commit.

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Another small metadata update is now pushed in commit c45368b.

The POINTTAPI device information now shows:

  • Bosch as the manufacturer for the routed devices;
  • EasyControl as the model for the zone/thermostat devices;
  • ETRV RT10-RF as the model for the thermostat valve devices.

I also added regression assertions for the model values. The focused tests pass and Ruff is clean. master and features/CleanupAndImprovements are synchronized again.

@CaseyRo CaseyRo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@jfhautenauven 72592b9 is exactly the shape I was hoping for — the hook is thirty lines, answers one question, and never touches the registry on its own. And the child-lock write confirmed end-to-end from your own HA within the hour: that's the whole argument for running the branch locally, made in one afternoon. 🎉 Head verified here: on current master, ruff clean, 561 green, CI green.

Two small things before I tag beta.3, both quick:

1. model="ETRV RT10-RF" on every valve

That's your hardware's name applied to everyone's. Your own fixtures carry protocol: homematicip, and @LukyHurdy1's 12-zone install confirmed homematicip on real valves on #16 — those aren't RT10-RF. If your dump has a real product/model field under /devices/deviceN (the way /gateway/productId gave you the CT200/TC100 map), read it from there. If it doesn't, drop the model= line: HA showing nothing beats HA showing a wrong part number on someone else's device. One line either way.

Same thought, lower priority: the seven manufacturer="Bosch" literals could reuse gateway_manufacturer — a Buderus TC100 owner otherwise gets a Buderus gateway with Bosch zones.

2. The stale device that "won't remove"

It isn't broken — it's just not automatic anymore. async_remove_config_entry_device doesn't delete anything by itself; it makes the Delete action appear in the device page's ⋮ menu, and HA only picks up the new hook after a restart with this __init__.py loaded. Restart, open the ghost "Thermostat valve #1", ⋮ → Delete. If the button is there and refuses, then the ghost's id is still in /devices/list as thermostat_valve and the hook is correctly saying "still present" — post the row and we'll look.

Push the model fix (or "no such field, dropped it") and I merge and tag 1.5.0-beta.3 — beta.2 still has the duplicate-unique_id errors, so this one doesn't wait for the weekly slot. 🚀

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

New feature added: the gateway productID is now resolved to the actual manufacturer and model in Home Assistant.

For example, my thermostat is not a Bosch CT200. It is a Buderus TC100.2. They are identical or nearly identical functionally, but the hardware design differs slightly. The integration now reports the correct values based on the gateway product ID instead of always showing the generic Bosch/EasyControl fallback.

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

I confirm that I tested all the new features with my own installation, and everything is working as expected and according to what I wanted.

I’m still looking into adding a few extra goodies, but after that we should be ready to move on to beta.3.

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

@CaseyRo

After reviewing the data available in my installation dump and querying the BOSCH servers directly, I don't see any remaining information from my setup that would require adding new data sources to the integration. ✅

From my side, the current coverage looks complete for this installation. 🚀

GO for Beta 3! 🎉

I’m going to close this work for the 1.5.0 release. 📦

@CaseyRo
CaseyRo merged commit 80a1a6a into CaseyRo:master Aug 27, 2026
3 checks passed
CaseyRo added a commit that referenced this pull request Aug 27, 2026
…ssions, device removal hook

PR #25 (jfhautenauven). Also drops the hardcoded thermostat-valve model
"ETRV RT10-RF": the API exposes no product id for radiator thermostats and
real installs run homematicip valves, so no model beats a wrong one.
@CaseyRo

CaseyRo commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Merged and out as v1.5.0-beta.3 — 563 green on master. 🚀

One thing I did on master rather than bounce it back to you, since you'd already called GO: I dropped model="ETRV RT10-RF" from the valve device (08f4758). Your own fixtures and @LukyHurdy1's install both run homematicip valves, which aren't RT10-RFs, and the API gives us no product id for radiator thermostats to read a real name from. No model beats a wrong one; if you ever find a field for it in a dump, it's a one-line add. Everything else, including the flue-gas codes and the Buderus/Bosch product map, shipped as you wrote it.

And on #22: thanks for digging through the old dumps — "no multi-circuit setup ever seen, live and let live" is the answer I needed. The installation device stays singular. 🙏

@jfhautenauven

Copy link
Copy Markdown
Collaborator Author

Thanks for the clarification and for the change. 👍

Thermostatic valve compatibility

As far as I know, the only thermostatic valves that were ever officially compatible with EasyControl were the Bosch-branded RT10-RF valves. Their development was outsourced to Homematic IP, which likely explains the related naming and protocol details.

Bosch internal release notes announced direct support for non-Bosch-branded Homematic EQ3 valves with firmware 5.4.0. However, I have never found public documentation that explicitly mentioned this support.

The Bosch ETRV v2 valves were never compatible with EasyControl; they are supported only by the newer HomeSmart controller.

That said, I agree with the modification: I may have assumed too quickly that everyone uses RT10-RF valves, while POINTTAPI does not actually expose enough data to identify the hardware reliably. Keeping the routing conservative is therefore the right call. ✅

Beta feedback

I am currently testing version 1.5.0 beta 3. So far, I have not observed any bugs or defects in normal use. 🎉

Release reminder

When we publish the stable release, please remember to update the release notes and the repository README as well. 📝

CaseyRo added a commit that referenced this pull request Aug 29, 2026
…fast/slow polling

Promotes the 1.5.0 beta line to stable. @jfhautenauven ran beta.3 on his
12-valve production install and reported no defects in normal use (#25).

README refreshed for the stable cut, as he asked on #25:
- entity matrix relabelled v1.5.0; adds the zone average temperature sensor
  that shipped in #20 but never reached the table
- section headings translated to English (Chaudiere, Vanne thermostatique,
  Efficacite energetique, Eau chaude sanitaire, Solaire)
- "Under the hood" polling bullet now describes the real fast/slow cadence
  instead of claiming a flat 60 seconds
- new Services section documenting bosch.refresh_gateway, the deprecated
  update_thermostat alias, and debug_scan being XMPP-only

563 tests, ruff clean.
@CaseyRo

CaseyRo commented Aug 29, 2026

Copy link
Copy Markdown
Owner

v1.5.0 is out as stable. 🎉 https://github.com/CaseyRo/ha_bosch/releases/tag/v1.5.0

Your beta.3 report was the last thing it was waiting on — thank you for running it in production the whole way through, and for confirming the child-lock write on real hardware rather than taking my word for the code path.

Thanks also for the valve-compatibility history. That's the clearest account of it I've seen anywhere, so I put it in the release notes under "A note on valve models" — RT10-RF as the only officially compatible one, the Homematic IP outsourcing behind the naming, EQ3 from firmware 5.4.0 per internal notes, ETRV v2 never on EasyControl. If someone later asks why their valve device has no model, the answer is now findable instead of living in a merged PR thread.

Release notes and README both updated, as you asked. The README needed more than a version bump:

  • entity matrix relabelled v1.5.0, plus the zone average temperature sensor from 🚀 POINTTAPI cleanup: clearer boost UX, translation harmonization, and smarter entity discovery #20 that never made it into the table
  • section headings were still French (Chaudiere, Vanne thermostatique, Efficacite energetique, Eau chaude sanitaire, Solaire) — translated
  • the "Under the hood" bullet still claimed a flat 60-second poll; it now describes your actual fast/slow cadence
  • new Services section, since update_thermostatrefresh_gateway is a user-visible rename and debug_scan being XMPP-only deserved writing down

One thing I found and did not fix, so it doesn't get lost: move_old_statistic_data is declared in services.yaml and const.py but never registered anywhere, so it's a documented service that doesn't exist. Not a 1.5.0 problem — filing it separately.

Next up is #22 for 1.6.0 whenever you have time. No rush, and enjoy having your month back. 🙂

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.

POINTTAPI: generic Bosch services are not compatible with POINTTAPI

2 participants