Adaptive Cover Pro ⛅ v2.28.0-beta.5
Pre-releaseℹ Using release notes from: release_notes/v2.28.0-beta.5.md
This beta builds on beta.4 (Configuration Summary translation, FOV-mode persistence, same-position gate, true-0% covers, entity_id normalization, low-sun fix, write-gating) by adding Jinja2 template support to the 9 numeric threshold config fields (issue #577) and an optional occupancy-condition template on Motion Override.
🎯 Highlights
Beta — please test and report back.
- Configure a temperature threshold (e.g.
CONF_TEMP_HIGH) as a Jinja2 template —{{ states('sensor.outdoor_temp') | float }}— save, and confirm the cover responds to the rendered value rather than a hard-coded number. - Configure a lux or wind-speed threshold as a template and confirm it resolves each coordinator cycle.
- Enter a malformed template (e.g.
{{ states('sensor.missing') | bad_filter }}) and confirm the cover falls back to the field's default rather than breaking or logging an error every cycle. - Open Diagnostics for a cover with templated thresholds and confirm the diagnostic payload shows the raw template string alongside its last-resolved value.
- Add an occupancy condition template to Motion Override and confirm a truthy render counts as presence — the cover should behave as if a motion sensor is active.
- Confirm a falsy occupancy template correctly starts the motion timeout rather than holding the cover open.
✨ Features
Jinja2 templates in numeric threshold fields (#577)
Nine numeric threshold config fields now accept either a plain number or a Home Assistant Jinja2 template: CONF_LUX_THRESHOLD, CONF_IRRADIANCE_THRESHOLD, CONF_CLOUD_COVERAGE_THRESHOLD, CONF_TEMP_LOW, CONF_TEMP_HIGH, CONF_OUTSIDE_THRESHOLD, CONF_WEATHER_WIND_SPEED_THRESHOLD, CONF_WEATHER_RAIN_THRESHOLD, and CONF_WEATHER_WIND_DIRECTION_TOLERANCE. All nine are collected in a single frozenset TEMPLATABLE_KEYS, which is consumed by the config-flow selector builder, the service validators, and the runtime resolver — one source, no divergence.
In the config UI these fields render as a Jinja code editor (selector.TemplateSelector) with entity autocomplete and syntax highlighting. Because the template selector only handles string values, legacy numeric values are stringified before being handed to the schema via _stringify_templatable. The field unit (°C, lux, W/m², %, etc.) moved out of the selector into the field's translation description, since the template selector carries no unit metadata.
At runtime, TemplateResolver.resolve renders templated thresholds to floats once per coordinator cycle, so the pure calculation engine and RuntimeConfig never see a raw template string. Fast path: if no templatable key holds a string, resolve returns the options dict unchanged with no copy overhead. Render failures are non-fatal — a threshold whose template fails to render is dropped so the field falls back to its default. The failure is logged once per failure transition (not every cycle) via the resolver's internal _failed set. is_template_string returns True only for strings containing {{ or {%, so a plain numeric string like "1000" is never treated as a template.
_num_or in config_types.py keeps the typed RuntimeConfig snapshot numeric: RuntimeConfig.from_options runs at setup/attach time on raw options before the first resolved cycle, so an unrendered template string falls back to the field default until TemplateResolver substitutes a real number.
Diagnostics surface the template state: DiagnosticsBuilder._templated_thresholds maps each key whose raw value is an actual template to an object containing the raw template string and its last-resolved value, backed by the new DiagnosticContext.resolved_options field. Plain-number configs produce an empty map.
All three threshold-template flavours ship English/German/French translations.
Optional occupancy condition template on Motion Override (#577 follow-up)
A new optional CONF_MOTION_TEMPLATE field ("motion_template") on the Motion Override config accepts a Jinja2 condition template. When it renders truthy, it counts as occupancy and is OR'd with the configured motion sensors and media players. render_condition is the reusable primitive — it uses HA's result_as_boolean and returns the default on an empty string, a non-template value, or a render failure.
MotionManager.is_configured is now the single source for "is motion control active?" — it returns True when any sensor is set OR a template is configured. MotionManager.template_active exposes the rendered result. MotionManager.is_motion_detected OR's the template result with the sensor states. The coordinator wires the template as a state listener via async_check_motion_template_change so a truthy render immediately refreshes the cover and a falsy render starts the motion timeout, matching the existing sensor behavior. MotionSlice.template carries the template string through RuntimeConfig. Diagnostics expose motion_template_active.
🔧 Internal
- Single-source
TEMPLATABLE_KEYS: the frozenset inconfig_fields.pyis the only place the set of templatable keys is defined; config-flow, service validators, andTemplateResolverall consume it. _num_orsnapshot guard:config_types.py's_num_orhelper coerces any non-numeric raw value (including an unrendered template string) to the field's numeric default atRuntimeConfig.from_optionstime, so the calculation engine is always handed a float.- Once-per-transition failure logging:
TemplateResolver._rendertracks the set of previously failing keys and logs only on the transition from success to failure (and recovery back), not every cycle.
🧪 Testing
4,391 tests passing (up from 4,327 at beta.4). New coverage: tests/test_templates.py (367 lines — TestTemplateResolver, TestIsTemplateString, TestRenderCondition, TestMotionTemplate, end-to-end suppression tests), plus config-flow selector tests, motion-control template tests, and diagnostics surfacing tests.
Previously in this beta line
Configuration Summary translated to user's language (beta.4, #258, #575, #576): The narrative Configuration Summary in the config/options flow now renders in the user's HA language. A config_summary translation namespace carries translated leaves for every summary fragment; _load_summary_labels loads them via HA's translation system with an English fallback. Cover-type labels and dimension lines also translate. German and French ship alongside English.
Set-position covers can reach true 0% in solar tracking (beta.3/beta.4, #569, #572): A geometry oversight prevented some set-position covers from closing fully during solar tracking. Covers that support arbitrary position commands now close to true 0%.
Same-position gate used tolerance band, swallowing 1–3% tracking moves (beta.3, #567, #574): A tolerance band introduced in #567 silently dropped small solar tracking adjustments. The gate reverts to exact equality; movement hysteresis is preserved in the reconcile path.
String entity_id in service targets not normalizing correctly (beta.3, #570, #571): Services that accept a string entity_id target now normalize the value before resolution, fixing silent failures from case or spacing variations.
FOV mode not persisted on re-render causes save loop (beta.2/beta.3, #565): The selected FOV mode was not written back into the stored config/options dict before re-render, causing an infinite re-render cycle. The fix writes CONF_FOV_MODE before re-rendering.
Measurements mode config-flow save and imperial shaded-area compounding (beta.2, #565): FOV sliders were emitted as vol.Required, blocking the frontend from switching to Measurements mode. Imperial shaded-area values also compounded on each re-render.
Low-sun edge case returns closed position (beta.1, #559, #562): At very low sun elevations, a geometry edge case returned the fully-open position instead of 0. The cover now closes when the sun is near the horizon.
Write-gating, geometry caching, and sun availability guard (beta.1, #543): _acp_render_signature() skips HA state writes when the calculated position matches the last written value. Pure geometry helpers use @lru_cache to eliminate repeated identical calculations. A guard on sun.sun suppresses solar tracking when the sun entity is unavailable.
Compatibility
Requires Home Assistant 2026.3.0+. No new coupled-component requirements.