Fix hot/warm href tiers dropped for no-entity coverage capabilities

discover() only emitted a BoundEntity per capability *entity*, so a
coverage-only Capability (entities=(), used to mark a href as handled
elsewhere -- e.g. the AC climate card's wind/strength, wind/direction,
temperature/control hrefs) produced zero rows. The coordinator computed
its hot/warm href lists by walking `bound`, so every such href's
poll_tier was silently discarded and it fell back to the ~30s summary
poll only -- no sub-poll cadence and never attempted for OCF OBSERVE.

This is the root cause of issue #166's "up to a minute" lag for
remote-driven fan-speed changes: /wind/strength/vs/0 carries poll_tier
'warm' via airconditioner.COVERAGE but never reached
_hot_hrefs/_warm_hrefs, so it wasn't in the OBSERVE-attempt href list
and only refreshed on the summary poll.

discover() now takes an optional tier_log(href, poll_tier) callback
fired for every href a capability matches, entities or not. The
coordinator uses it directly instead of deriving tiers from `bound`.
This commit is contained in:
Marc Billow committed 2026-07-28 15:08:12 +00:00
1 parent 65b0326d62
commit 71e026b804
3 files changed
+38 -9

No files matched your search

+16
View File
@@ -209,3 +209,19 @@ def test_discover_rt_filter_gates_binding():
resources = {'/mode/vs/0': {'rt': ['x.com.samsung.da.mode'], 'x.com.samsung.da.mode': 'Bake'}}
bound = discover(resources, {'/mode/vs/0': [oven_mode_cap]})
assert bound == []
def test_discover_tier_log_fires_for_no_entity_coverage_cap():
"""A coverage-only Capability (entities=(), used to mark a href as
handled by e.g. a composite climate entity rather than its own HA
entity) produces zero BoundEntity rows -- `bound` alone can't tell a
caller its poll_tier. tier_log must still report it, or a consumer
(the coordinator's hot/warm href lists) silently treats it as 'cold'
regardless of what poll_tier the capability actually declares."""
coverage_cap = Capability(href='/wind/strength/vs/0', poll_tier='warm')
resources = {'/wind/strength/vs/0': {'x.com.samsung.da.modes': '0'}}
seen = []
bound = discover(resources, {coverage_cap.href: [coverage_cap]},
tier_log=lambda href, tier: seen.append((href, tier)))
assert bound == []
assert seen == [('/wind/strength/vs/0', 'warm')]