2.28.0 is a large rollup of the beta.1–beta.14 series. The headline changes are Force Override merging into Custom Positions with multi-sensor and template triggers, Jinja2 templates in threshold fields across every climate and light sensor, opt-in position matching, and a redesigned Cover Geometry step with a generate-from-measurements button that fixes an FOV calculation that was producing roughly half the intended angle. Several pipeline correctness fixes ship alongside these: a low-sun edge case that returned full-open instead of closed, a same-position gate that was swallowing 1–3% solar-tracking moves, and a climate status sensor crash on string thresholds.
⚠️ Upgrade Notes
Position matching is now opt-in — but upgrading changes nothing. The enable_position_matching default is OFF only for covers created on v2.28.0. Existing covers are migrated to ON, so they keep retrying after settling off-target exactly as before — you don't need to do anything. If you'd rather have the new command-once behavior (where an off-target delta engages manual override instead of retrying), turn the toggle off in Position Settings.
Force Override is removed as a separate config step. Existing configs auto-migrate: your Force Override sensor and position move to Custom Positions slot 5 at priority 100, which preserves the same behavior — commands outside the time window, bypasses delta gates. The set_force_override service remains as a deprecation shim for one release. Slot 5 now also accepts multiple sensors (OR logic) and Jinja2 template triggers.
FOV-from-measurements was producing ~half the intended angle. The formula used atan(half_width / depth) instead of atan(width / depth). A 2 m window with 0.5 m reveal returned ~63° instead of ~76°. Re-open Cover Geometry for any cover configured in Measurements mode and verify the displayed FOV is correct.
🎯 Highlights
- Force Override absorbed into Custom Positions (#563, #584): slot 5 at priority 100 replaces the standalone step, adds multi-sensor OR logic and Jinja2 template triggers, and existing configs auto-migrate.
- Jinja2 templates in threshold fields (#577, #578): temperature, lux, irradiance, cloud, wind, and rain thresholds accept templates resolved each coordinator cycle, not just static numbers.
- Generate FOV from measurements (#565, #588, #595): a single button computes
fov_left/fov_rightfrom window width and reveal depth. FOV sliders stay visible and editable. A formula bug that halved the angle is fixed (#583). - Opt-in position matching (#591, #592):
enable_position_matchingtoggle (default OFF) controls retry-on-settle. Off means command once; an off-target delta engages manual override. - Climate status sensor gains typed inactive reasons (#589, #590):
inactive_reasonslug (active,mode_off,outside_time_window,thresholds_not_met,other_mode_active,readings_unavailable) plus one-decimal setpoint attributes. - Low-sun and solar edge cases corrected (#559, #562, #598, #599): full-open on low sun is fixed; vertical extreme-gamma is now elevation-aware; redundant backwards edge cases dropped.
✨ Features
Force Override merged into Custom Positions (#563, #584)
The standalone Force Override config step is gone. Its safety semantics now live on Custom Positions slot 5, which runs at priority 100 — above all other handlers, outside the time window, bypassing delta gates. Slot 5 accepts a list of trigger sensors (OR logic) or a Jinja2 template expression, so a single slot can cover the cases that previously required workarounds. Existing force_override_sensors and force_override_position config keys are read during the v3.2 minor migration and written to slot 5; the migration is rollback-safe. The set_force_override service continues to work for one release as a shim pointing at slot 5.
Jinja2 templates in threshold fields (#577, #578)
Every numeric threshold — temperature high/low, summer outside temp, lux, irradiance, cloud cover, wind speed, rain — now accepts a Jinja2 template string in addition to a plain number. Templates are resolved once per coordinator cycle. Failures log once per transition so the log does not fill on a broken template. An optional occupancy template on Motion Override evaluates as presence when truthy. Config summary displays [template] as the placeholder for any threshold that holds a template string (#587).
Generate FOV from measurements (#565, #588, #595)
The FOV mode selector (FovMode enum, CONF_FOV_MODE option key) is removed. In its place, a "Generate field of view from measurements" button in Cover Geometry computes fov_left and fov_right from window width and reveal depth, writes those values into the FOV sliders, then clears itself so you can fine-tune either angle independently. The sliders are always visible. Old saved CONF_FOV_MODE values are inert and harmless.
Opt-in position matching (#591, #592)
enable_position_matching (default OFF for new installs) controls whether the integration retries after a cover settles off-target. When off, the integration sends the command once; if the cover reports a different position afterward, it treats that as a manual override. The position tolerance slider moved to the Position Settings step. A v3.3 → v3.4 migration sets the toggle ON for every pre-existing cover, so upgrades keep the old reconcile/chase behavior rather than silently flipping to command-once (#606, #610).
Climate status sensor (#589, #590)
sensor.climate_status now returns temp_low, temp_high, and temp_summer_outside rounded to one decimal, and an inactive_reason attribute with a typed slug. The slugs are: active, mode_off, outside_time_window, thresholds_not_met, other_mode_active, readings_unavailable. These replace the previous freeform string, making the attribute usable in automations without string-matching.
Held position in manual override trace (#608, #609)
The manual override sensor's decision_trace attribute now includes the held position, making it straightforward to read what position the integration is locked to without navigating diagnostics.
Vertical-drop shade model for oscillating awning (#586, #618)
Oscillating awnings now compute shading using a vertical-drop model. arm_length upper limit raised from 3.0 m to 6.0 m to match real-world hardware. Thanks to @muhamedfazalps for the contribution (#637).
Anticipatory solar positioning (#616, #617)
Solar targeting anticipates the throttle window during fast sun movement, computing the target position for the end of the window rather than the current moment. Covers track the sun more smoothly when the sun is moving quickly across the azimuth.
Solar-tracking forecast in diagnostics
The diagnostics dump now includes a pure solar-tracking forecast — sun position only, with no pipeline overrides — alongside the full pipeline forecast. Useful for comparing what the sun geometry alone produces against what the pipeline actually commands.
Set-position covers reach true 0% (#569, #572)
The lower clamp on solar-tracking positions is removed for set-position covers, so a 0% computed position closes the cover fully instead of stopping at 1%.
Configuration Summary in your HA language (#258)
The narrative summary shown during config and options flow renders in English, German, or French depending on the HA UI language setting.
🐛 Fixes
Climate status crash on string thresholds (#619, #620)
_round_threshold assumed its argument was always a float. When a Jinja2 template string was stored as a threshold, the function crashed. It now accepts float | str | None, normalizes comma-decimal formats, and returns None for template strings rather than raising.
FOV-from-measurements was ~half the intended angle (#565, #583)
fov_from_reveal() called atan(half_width / depth) instead of atan(width / depth). For a 2 m window with 0.5 m reveal, the result was ~63° instead of the intended ~76°. Re-open Cover Geometry after upgrading to verify your FOV for any cover that used Measurements mode.
Same-position gate uses exact equality (#567, #574)
The gate that skips redundant commands was checking whether the new position fell within a tolerance band of the current position. That band was wide enough to swallow 1–3% solar-tracking corrections. The gate now uses exact equality: if the integer position is unchanged, skip the command; otherwise send it.
Low-sun returned full-open (#559, #562)
An edge case in the low-sun path returned the full-open position instead of the closed position when the sun was below the valid elevation range. The correct return is now the configured default.
Persist all create-flow fields on new cover save (#565, #580)
The allowlist controlling which fields are written on first save was incomplete. Fields the user entered during the create flow — window width, azimuth, reveal depth — were silently dropped. All user-entered fields now persist.
Legacy custom-position sensor key migrated to list (#563, #597)
Older configs stored the custom-position trigger as a bare entity ID string. The migration now converts that to a list so multi-sensor logic works correctly on upgraded installs.
Normalize string entity_id in service target resolution (#570, #571)
Service calls that passed entity_id as a plain string rather than a list were silently dropped. The resolver now coerces the string to a list before matching.
Always show computed FOV preview at reveal depth 0 (#596)
The FOV preview was suppressed when reveal depth was set to 0. It now shows the computed value regardless.
Vertical extreme-gamma edge case is elevation-aware (#598, #599)
The extreme-gamma path for vertical blinds now checks sun elevation before applying the edge-case correction. Backwards edge cases that fired incorrectly at low elevation are removed (#600, #601).
MINOR_VERSION bumped to 3 for v3.3 sensor migration (#563, #607)
The coordinator's MINOR_VERSION was not incremented, so the migration that sets up the new custom-position sensor list format never ran on existing installs. Version bumped to 3; the migration now runs correctly on upgrade.
Simplified Cover Geometry step description (#564, #581)
The Cover Geometry config step had a long description that duplicated information shown in the UI itself. Trimmed to the information that is not otherwise visible.
🔧 Internal / Performance
Write-gating, geometry caching, sun-availability guard (#543)
Three performance changes ship together: HA state writes are skipped when the position is unchanged (write-gating); pure geometry helpers are cached with @lru_cache; solar tracking is suppressed entirely when the sun entity is unavailable rather than computing a position that will be discarded.
Coordinator on ConfigEntry.runtime_data
The coordinator is now stored on the typed ConfigEntry.runtime_data field instead of hass.data[DOMAIN][entry_id]. This matches current HA best practice and makes the coordinator accessible with proper type information from the config entry.
config_summary i18n moved out of translations/ (#579)
The config_summary translation category previously lived under translations/ alongside component strings, which caused hassfest validation failures. It now lives at the correct path for custom categories.
CI updates
Black bumped to ~=26.5. Dependabot now targets develop instead of main. Workflow run times reduced.
Compatibility
- Home Assistant 2026.3.0+