Follow-up from #540 and the per-entity write-gating work. Layer 1 (gating redundant async_write_ha_state at AdaptiveCoverBaseEntity) and Layer 2 (lru_cache on the pure geometry functions) have landed and remove the user-visible cost: the ~30 redundant entity writes per update cycle and the repeated safety-margin math. This issue tracks the remaining CPU optimization that #540 was reaching for but did not land correctly: skipping _calculate_cover_state (coordinator.py) when no pipeline-relevant input has changed.
Why #540's version never fired
#540 gated the short-circuit on not self.state_change. In practice almost every _async_update_data runs with state_change=True, because the dominant triggers (sun.sun azimuth/elevation updates, chatty temperature/lux/irradiance sensors) all set that flag before the refresh. The fingerprint compare was never reached.
The correct design
Gate on a fingerprint regardless of state_change, with rounding so micro-updates that do not change the output are absorbed:
- Round sun azimuth/elevation to ~1 dp and raw temps to whole degrees.
- Include the derived climate threshold booleans (
lux_below_threshold, irradiance_below_threshold, cloud_coverage_above_threshold, is_sunny, is_presence) so a threshold crossing always changes the fingerprint even when the rounded raw value looks stable.
- Include every override/gate flag: manual, weather, motion, force, grace, in_time_window.
- Include cover positions and custom-position sensor on/off states.
- Always recompute on
first_refresh, cover_state_change, auto_expired, and option changes (key off an options-version guard).
Use a structured tuple fingerprint rather than #540's MD5-of-JSON, so it stays debuggable and collision-free.
Why this is a separate, deliberate change
Any pipeline input left out of the fingerprint means covers silently stop moving. That is a correctness bug, not a perf regression, so it should not ride along with the low-risk write-gating change. Mitigations:
- Keep an explicit whitelist of every input the pipeline reads.
- Add a guard test that introspects the
CoverStateSnapshot and ClimateReadings fields and fails if a new field is not represented in the fingerprint.
Layer 1 already removed the visible cost, so this is a pure coordinator-CPU win. Worth doing only once profiling on a busy instance (many entries, chatty sensors) shows pipeline CPU is material.
Follow-up from #540 and the per-entity write-gating work. Layer 1 (gating redundant
async_write_ha_stateatAdaptiveCoverBaseEntity) and Layer 2 (lru_cacheon the pure geometry functions) have landed and remove the user-visible cost: the ~30 redundant entity writes per update cycle and the repeated safety-margin math. This issue tracks the remaining CPU optimization that #540 was reaching for but did not land correctly: skipping_calculate_cover_state(coordinator.py) when no pipeline-relevant input has changed.Why #540's version never fired
#540 gated the short-circuit on
not self.state_change. In practice almost every_async_update_dataruns withstate_change=True, because the dominant triggers (sun.sun azimuth/elevation updates, chatty temperature/lux/irradiance sensors) all set that flag before the refresh. The fingerprint compare was never reached.The correct design
Gate on a fingerprint regardless of
state_change, with rounding so micro-updates that do not change the output are absorbed:lux_below_threshold,irradiance_below_threshold,cloud_coverage_above_threshold,is_sunny,is_presence) so a threshold crossing always changes the fingerprint even when the rounded raw value looks stable.first_refresh,cover_state_change,auto_expired, and option changes (key off an options-version guard).Use a structured tuple fingerprint rather than #540's MD5-of-JSON, so it stays debuggable and collision-free.
Why this is a separate, deliberate change
Any pipeline input left out of the fingerprint means covers silently stop moving. That is a correctness bug, not a perf regression, so it should not ride along with the low-risk write-gating change. Mitigations:
CoverStateSnapshotandClimateReadingsfields and fails if a new field is not represented in the fingerprint.Layer 1 already removed the visible cost, so this is a pure coordinator-CPU win. Worth doing only once profiling on a busy instance (many entries, chatty sensors) shows pipeline CPU is material.