Skip to content

Commit 88e88f6

Browse files
committed
Merge branch 'master' into pr18
2 parents 14b326d + e126632 commit 88e88f6

4 files changed

Lines changed: 67 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,24 @@ All notable changes to this Bosch Home Assistant custom component will be docume
44

55
## [Unreleased]
66

7-
Collected for 1.3.0 (pre-released as 1.3.0-beta.1). Held out of 1.2.0 because
8-
none of it has been confirmed on real multi-zone hardware yet.
7+
## [1.3.1] — 2026-08-17 — Quiet teardown on cloud entries
8+
9+
### Fixed
10+
- **No more hourly "Unable to remove unknown service bosch/debug_scan"**
11+
`debug_scan` only registers on the XMPP/HTTP path, but every unload removed
12+
it unconditionally, so a POINTTAPI (cloud) entry asked Home Assistant to
13+
remove a service it never had. Harmless, but it shouted about it in
14+
@altugyurtbasi's log every reload since April. Both services are now guarded
15+
on `has_service` rather than special-casing the debug one, since
16+
`bosch.update` has the same exposure when setup fails before registration
17+
(#7).
18+
19+
## [1.3.0] — 2026-08-12 — Per-valve telemetry, assigned program, schedule/manual climate
20+
21+
Pre-released as 1.3.0-beta.1 and held back from 1.2.0 until someone could run
22+
it on real multi-zone hardware. @LukyHurdy1 did, on a 12-zone Czech install,
23+
and reported battery `ok`, signal 69%, protocol `homematicip` and a working
24+
warning sensor — the confirmation this release ships on (#16).
925

1026
### Added
1127
- **Per-thermostat-valve telemetry** — each ETRV from `/devices/list` becomes

custom_components/bosch/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
"issue_tracker": "https://github.com/CaseyRo/ha_bosch/issues",
1212
"loggers": ["custom_components.bosch", "bosch_thermostat_client"],
1313
"requirements": ["bosch-thermostat-client==0.28.2"],
14-
"version": "1.3.0-beta.1",
14+
"version": "1.3.1",
1515
"zeroconf": ["_buderus._tcp.local."]
1616
}

custom_components/bosch/services.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,13 @@ async def async_handle_put(service_call: ServiceCall) -> ServiceResponse:
192192

193193

194194
def async_remove_services(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
195-
"""Remove services."""
196-
hass.services.async_remove(DOMAIN, SERVICE_DEBUG)
197-
hass.services.async_remove(DOMAIN, SERVICE_UPDATE)
195+
"""Remove services that this integration actually registered.
196+
197+
debug_scan only registers on the XMPP/HTTP path — a POINTTAPI entry returns
198+
from async_init() long before that call — so removing it unconditionally
199+
asks HA to drop a service that was never there and logs "Unable to remove
200+
unknown service bosch/debug_scan" on every unload and reload (#7).
201+
"""
202+
for service in (SERVICE_DEBUG, SERVICE_UPDATE):
203+
if hass.services.has_service(DOMAIN, service):
204+
hass.services.async_remove(DOMAIN, service)

unittests/test_services_removal.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Service teardown must not remove services it never registered (#7)."""
2+
from __future__ import annotations
3+
4+
from unittest.mock import MagicMock
5+
6+
from custom_components.bosch.const import DOMAIN, SERVICE_DEBUG, SERVICE_UPDATE
7+
from custom_components.bosch.services import async_remove_services
8+
9+
10+
def _hass(registered: set[str]) -> MagicMock:
11+
hass = MagicMock()
12+
hass.services.has_service.side_effect = (
13+
lambda domain, service: domain == DOMAIN and service in registered
14+
)
15+
return hass
16+
17+
18+
def test_unregistered_debug_scan_is_not_removed():
19+
"""POINTTAPI entries never register debug_scan.
20+
21+
Removing it anyway makes HA log "Unable to remove unknown service
22+
bosch/debug_scan" on every unload and reload.
23+
"""
24+
hass = _hass({SERVICE_UPDATE})
25+
26+
async_remove_services(hass, MagicMock())
27+
28+
removed = {call.args[1] for call in hass.services.async_remove.call_args_list}
29+
assert removed == {SERVICE_UPDATE}
30+
31+
32+
def test_registered_services_are_still_removed():
33+
hass = _hass({SERVICE_DEBUG, SERVICE_UPDATE})
34+
35+
async_remove_services(hass, MagicMock())
36+
37+
removed = {call.args[1] for call in hass.services.async_remove.call_args_list}
38+
assert removed == {SERVICE_DEBUG, SERVICE_UPDATE}

0 commit comments

Comments
 (0)