climate: refresh HVAC capabilities on every coordinator poll#223
Conversation
Fixes the silent capability-strip bug reported in dlarrick#105: when a unit's WiFi adapter is offline at HA startup, pykumo returns empty profiles and `__init__` caches `_hvac_modes = [OFF, COOL]` permanently. Heat-cool commands are then silently rejected until the next HA restart, even after the adapter reconnects. Changes: - Extract the capability derivation block into `_refresh_capabilities()`. - Call it from `__init__` (same behavior as before for online units) and from `update()` after every successful coordinator poll. - Use an **upgrade-only** merge for `_hvac_modes`: modes are only ever added, never removed. A transient poll failure cannot strip a capability that was already confirmed. `fan_modes` and `swing_modes` are refreshed from the live profile unconditionally (pykumo preserves the last-good profile across failures, so this is always safe). - Add a DEBUG log line after each refresh so capability changes are visible in HA logs without enabling extra verbosity. Co-Authored-By: Claude <noreply@anthropic.com>
pykumo initialises _profile to {} and only populates it after a
successful network poll. The previous code guarded fan_modes with
`if fan_speeds:`, but get_fan_speeds() falls back to a hard-coded
5-item list when numberOfFanSpeeds is absent (KeyError → speeds = 5),
so the guard always passed and the stale default list was cached as
real hardware data.
Fix: skip all capability derivation when _profile is empty. The
entity keeps its safe init defaults ([OFF, COOL], empty fan/swing
lists) until the first successful poll populates the profile. Once
populated, the existing upgrade-only logic for hvac_modes and the
overwrite logic for fan/swing modes work correctly.
Co-Authored-By: Claude <noreply@anthropic.com>
The profile-populated guard currently accesses pykumo's private _profile attribute directly. dlarrick/pykumo#72 adds a public has_profile() method for exactly this purpose. Add a TODO comment so the migration path is clear once pykumo cuts a release with that method and the requirement can be bumped. Co-Authored-By: Claude <noreply@anthropic.com>
|
I've opened a companion PR in pykumo — dlarrick/pykumo#72 — that adds The implementation is one line: For now, the TODO comment in the latest commit on this branch makes the migration path explicit. The private access is safe and correct as written — this just tidies it up for the future. |
|
Thanks, I should have a chance to merge these this weekend. I'll merge the pykumo one first (bumping minor version) so if you want to update this PR with that in mind it'll go quicker. |
There was a problem hiding this comment.
Pull request overview
This PR addresses a Home Assistant capability-caching issue in the Kumo climate entity where HVAC/fan/swing capabilities derived during __init__ could remain permanently incorrect if the adapter profile was empty at startup and only became populated after later successful polls.
Changes:
- Initializes capability-related fields to safe defaults and centralizes capability derivation in a new
_refresh_capabilities()helper. - Gates capability derivation on the pykumo profile being populated and refreshes capabilities after every coordinator update.
- Uses an “upgrade-only” strategy for
hvac_modes(and related feature flags) to avoid transient poll failures removing confirmed capabilities.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # --- fan / swing: overwrite from current (real) profile --- | ||
| fan_speeds = self._pykumo.get_fan_speeds() | ||
| if fan_speeds: | ||
| self._fan_modes = fan_speeds | ||
| vane_dirs = self._pykumo.get_vane_directions() | ||
| if vane_dirs: | ||
| self._swing_modes = vane_dirs |
There was a problem hiding this comment.
I disagree with Copilot here, this is fine, in fact is the point of the feature. Maybe update the docstring.
The previous docstring said fan_modes/swing_modes are overwritten on every populated-profile call. The code actually uses a truthy guard (if fan_speeds: / if vane_dirs:) so a transient empty read never clobbers a previously confirmed list. Update the docstring to match. No logic changes. Co-Authored-By: Claude <noreply@anthropic.com>
|
Updated the docstring to match the truthy-guard behavior as you suggested — left the logic as-is. Will switch to has_profile() and bump the pykumo pin once #72 is released. |
|
You'll need to update the pykumo required version (to v.0.5.2) in manifest.json in your branch. GitHub doesn't let me make that edit on your PR without just making it my own. |
pykumo 0.5.2 ships PyKumoBase.has_profile() (merged from dlarrick/pykumo#72), replacing the private _profile attribute access that was guarded with a noqa. - manifest.json: pykumo>=0.5.0 → pykumo>=0.5.2 - climate.py _refresh_capabilities(): replace `if not self._pykumo._profile:` (with noqa: SLF001 and TODO comment) with `if not self._pykumo.has_profile()` Co-Authored-By: Claude <noreply@anthropic.com>
|
Bumped the pykumo requirement to >=0.5.2 and migrated the profile guard from the private |
| if not self._pykumo.has_profile(): | ||
| _LOGGER.debug( | ||
| "Kumo %s: profile not yet populated, skipping capability refresh", | ||
| self._name, | ||
| ) | ||
| return |
There was a problem hiding this comment.
No, Copilot, that's a terrible idea.
|
Merging this now. I will cut a prerelease later. |
Summary
When a unit's WiFi adapter is offline at HA startup,
KumoThermostat.__init__calls the pykumohas_*methods against an empty profile and permanently caches_hvac_modes = [OFF, COOL]. After the adapter reconnects, pykumo's internal profile is updated on the next successful poll — but the capability lists in HA are never recalculated. The result is silent rejection ofheat_cool(andheat,dry,fan_only) commands until the next HA restart.Root cause
Capability derivation ran once at init time and was never repeated:
The fix
Extract capability derivation into
_refresh_capabilities()and call it from both__init__andupdate().Profile-populated guard (key correctness fix)
All capability derivation is now gated on
self._pykumo._profilebeing non-empty. pykumo initialises_profile = {}at construction and only populates it after a successful network poll. Without this guard,get_fan_speeds()returns a hard-coded 5-item fallback list (["superQuiet","quiet","low","powerful","superPowerful"]) whenevernumberOfFanSpeedsis absent from the profile — i.e., on every call before the first successful poll — so a truthiness check on the return value does not distinguish real hardware data from the default. The same applies tohas_dry_mode(),has_heat_mode(), etc., which all returnFalse(not an error) against an empty profile.By skipping
_refresh_capabilities()entirely when_profileis empty, the entity keeps its safe init defaults ([OFF, COOL], empty fan/swing lists) until the first real poll. There is no public API to test profile population, so_profileis accessed directly; other existing code in hass-kumo already uses this pattern.Upgrade-only merge for
hvac_modesOnce the profile is populated, modes are only ever added, never removed. A transient poll failure (which leaves pykumo's
_profileunchanged) cannot strip a capability that was already confirmed. Onceheat_coolappears it stays in_hvac_modesfor the lifetime of the entity.fan_modes/swing_modesAlways overwritten from the live (populated) profile. pykumo preserves the last-good profile across poll failures, so this is safe and keeps the lists current.
ClimateEntityFeature.SWING_MODEAlso upgrade-only — added as soon as
has_vane_direction()returns True, never cleared.DEBUG log
Emitted after each refresh (or skip) so capability changes are visible in HA logs without extra verbosity.
Why upgrade-only for hvac_modes?
If a poll times out mid-stream, pykumo may return partial or stale data. Allowing
_hvac_modesto shrink on a bad poll would cause the same user-visible regression (mode commands silently rejected) that this PR is fixing. Upgrade-only ensures advertised capabilities only ever grow toward true hardware capability, never regress on transient failures.pykumo version note
Correct
heat_cooldetection for units withautoModePrevention(the_compute_has_mode_autologic introduced in #204) requires pykumo >= 0.4.2. This dependency is already satisfied:manifest.jsonpinspykumo>=0.5.0, so no additional constraint is needed. The note here is for reviewer awareness only.Related work
This is complementary to #204 (auto/
heat_coolmode suppressed byautoModePrevention, implemented in pykumo v0.4.2). That change addresses the case where the adapter reports a capability flag that wrongly suppressesheat_cool, and already recomputes that one mode on each update. This PR addresses a different trigger — the adapter being unreachable at entity init, which strips all capabilities (heat,dry,fan_only, swing, fan speeds, not justheat_cool) — and generalises the "recompute on update" approach to the full capability set with upgrade-only safety. The two fixes stack cleanly.Testing
Tested on a multi-zone SVZ-KP18NA install. A zone with an intermittent WiFi adapter previously came up cool-only after every HA restart and required a manual restart to recover. With this fix, the entity self-heals after the next successful coordinator poll (~30 s) without any user intervention or HA restart.
python3 -m py_compile custom_components/kumo/climate.pypasses cleanly. Full integration tests require a live HA environment (the upstream test suite has the same dependency onhomeassistantbeing installed).Addresses #105.