Created
December 4, 2025 19:01
-
-
Save FrankTub/30d8625d3b110f2abc9b700beea859b5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| blueprint: | |
| name: "Cover Control Automation (CCA) - Beta" | |
| description: | | |
| # ⭐ Cover Control Automation (CCA) ⭐ | |
| Cover Control Automation is a comprehensive Home Assistant blueprint which automatically manages your window coverings (such as roller shutters and blinds) based on time, the position of the sun, and weather conditions. It adapts intelligently to your preferences, largely eliminating the need for manual adjustments. | |
| **Version**: 2025.12.03 | |
| ### 🔗 Resources & Support | |
| - **💬 Community Thread**: [CCA Community Thread](https://community.home-assistant.io/t/cover-control-automation-cca-a-comprehensive-and-highly-configurable-roller-blind-blueprint/680539) | |
| - **🐛 Report Issues**: [GitHub Issues](https://github.com/hvorragend/ha-blueprints/issues) | **📄 Source Code**: [github.com/hvorragend](https://github.com/hvorragend/ha-blueprints/blob/main/blueprints/automation/cover_control_automation.yaml) | |
| - **❤️ Support Development**: [PayPal Donation](https://www.paypal.com/donate/?hosted_button_id=NQE5MFJXAA8BQ) | [Buy me a Coffee](https://buymeacoffee.com/herr.vorragend) 🙏 | |
| <details> | |
| <summary><strong>✨ Key Features</strong></summary> | |
| ### 🔑 CCA handles | |
| - ☀️ Opening blinds in the morning automatically | |
| - 🌞 Closing blinds during sunny periods for effective sun protection | |
| - 🌙 Closing blinds in the evening | |
| - 🪟 Respecting open windows and preventing closure during ventilation | |
| - 🛡️ Recognizing manual adjustments and not overriding your decisions | |
| - ⚡ Supporting a wide range of cover types and integrations | |
| ### 🕐 Time-Based Control | |
| - Automatic opening in the morning, closing in the evening | |
| - **Flexible scheduling options:** Choose between traditional time settings or Home Assistant calendars | |
| - **Calendar Mode:** Create events like "Open Cover" or "Close Cover" for visual, family-friendly scheduling | |
| ### ☀️ Advanced Sun Shading | |
| - Flexible AND/OR logic for shading conditions | |
| - Independent START and END condition configuration | |
| - Fine-grained control over individual shading triggers | |
| - Responds to **sun position** (azimuth and elevation) | |
| - Monitors **temperature** (indoor, outdoor, and forecast data) | |
| - Evaluates **light brightness** for direct sun detection | |
| - Considers **weather forecasts** to avoid unnecessary closures | |
| - Independent START and END logic for precise control | |
| - Built-in **hysteresis** to prevent wear from rapid cycling | |
| ### 🎯 Cover Type Support / Awning Support | |
| - Blinds & Roller Shutters: Traditional covers where 0% = closed (down) | |
| - Awnings & Sunshades: Inverted logic where 0% = retracted, 100% = extended | |
| - Automatic position logic adaptation based on cover type | |
| ### 🪟 Ventilation Management | |
| - Automatically adjusts to ventilation position when window is tilted | |
| - Fully opens when window is completely opened | |
| - Lockout protection prevents accidental closure | |
| - Restores position when ventilation ends | |
| ### 🎯 Manual Override Intelligence | |
| - Detects manual cover adjustments | |
| - Respects your manual decisions for up to one hour | |
| - Automatically resumes automation after timeout | |
| - Tracks status persistently across restarts | |
| ### 🛠️ Extensive Customization | |
| - Fine-tune every timeout and threshold | |
| - Multi-stage tilt position control | |
| - Flexible AND/OR logic for complex conditions | |
| - Per-feature enable/disable | |
| - Custom delay options to prevent radio interference | |
| </details> | |
| <details> | |
| <summary><strong>🔧 Advanced Capabilities</strong></summary> | |
| - **Tilt position control** with elevation-based multi-stage support | |
| - **Forecast integration** (daily, hourly, or current conditions) | |
| - **Multi-sensor support** with configurable AND/OR logic | |
| - **Hybrid scheduling** (Combines time-based triggers with calendar integration for flexible scheduling. ) | |
| - **Custom delays** to prevent RF interference | |
| - **Force triggers** for emergency scenarios (anti-freeze, rain protection, wind protection) | |
| - **Background state tracking** with automatic return to target position after force disable | |
| - **Resident detection** for respecting sleep schedules | |
| - **Smart state tracking** via helper integration | |
| </details> | |
| <details> | |
| <summary><strong>🚀 Quick Start (5 Steps)</strong></summary> | |
| 1. **Create a Helper**: Set up a text input helper with **minimum 254 characters** (Settings → Devices & Services → Helpers) | |
| 2. **Select Your Cover**: Choose the blind or shutter to automate | |
| 3. **Enable Features**: Activate the features you need (morning opening? Sun protection? Ventilation mode?) | |
| 4. **Configure Basics**: Set opening/closing times and connect your sensors | |
| 5. **Test & Refine**: Run the automation and adjust thresholds as needed | |
| </details> | |
| <details> | |
| <summary><strong>⚠️ Essential Prerequisites</strong></summary> | |
| **Required:** | |
| - A Home Assistant `cover` entity with `current_position` attribute | |
| - Home Assistant **2024.10.0 or newer** | |
| **Strongly Recommended:** | |
| - Text Helper (254 characters) for cover status tracking | |
| - Sun integration enabled (`sun.sun`) for sun-based features | |
| - Accurate latitude/longitude in Home Assistant configuration | |
| **Critical Configuration:** | |
| - Times must be correctly ordered (`time_up_early` < `time_up_late`, etc.) | |
| - Position values must not overlap (consider tolerance settings) | |
| - Only binary sensors for resident and contact detection | |
| - <strong>It is <ins>not</ins> possible to execute this automation manually! Unless you use it exclusively for the configuration check!!</strong> | |
| - If you want to use sun elevation and/or azimuth it's strongly advised to use sun.sun. And please make sure your sun.sun entity is enabled! | |
| - `time_up_early` should be earlier than `time_up_late` | |
| - `time_up_early_non_workday` should be earlier than `time_up_late_non_workday` | |
| - `time_down_early` should be earlier than `time_down_late` | |
| - `time_down_early_non_workday` should be earlier than `time_down_late_non_workday` | |
| - `shading_azimuth_start` should be lower than `shading_azimuth_end` | |
| - `shading_elevation_min` should be lower than `shading_elevation_max` | |
| - `shading_sun_brightness_start` should be higher than `shading_sun_brightness_end` | |
| - `open_position` should be higher than `close_position` | |
| - `open_position` should be higher than `ventilate_position` | |
| - `close_position` should be lower than `ventilate_position` | |
| - `shading_position` should be higher than `close_position` | |
| - `shading_position` should be lower than `open_position` | |
| - `resident_sensor` is only allowed to be on/off/true/false | |
| - cover must have a `current_position` attribute | |
| </details> | |
| </details> | |
| <details> | |
| <summary><strong>❓ Frequently Asked Questions</strong></summary> | |
| **Q: Can I automate multiple covers with one automation?** | |
| **A:** Create **one automation per cover** for reliable results. While cover groups are technically possible, position detection becomes inaccurate when covers are at different positions simultaneously. | |
| --- | |
| **Q: Is the Helper mandatory for all features?** | |
| **A:** The helper is **required** for sun shading and ventilation. Time-based opening/closing works with basic position detection alone, but you lose manual override protection and advanced features. | |
| --- | |
| **Q: What does "Pending" status mean?** | |
| **A:** Conditions are actively being evaluated. The automation is waiting for stable sensor readings. Increase the "Maximum duration for retry" if conditions frequently fluctuate, or adjust thresholds for better stability. | |
| --- | |
| **Q: Can I trigger CCA manually?** | |
| **A:** ⚠️ **Not directly**—the automation runs on time triggers and sensor changes. However, force triggers allow manual override of automatic behavior when needed. | |
| </details> | |
| <details> | |
| <summary><strong>📈 How Hysteresis Works</strong></summary> | |
| Hysteresis creates two different thresholds: | |
| - **Upper Threshold (Activation)**: `shading_forecast_temp` | |
| - **Lower Threshold (Deactivation)**: `shading_forecast_temp - shading_forecast_temp_hysteresis` | |
| **Example with Forecast Temp = 25°C and Hysteresis = 2°C:** | |
| - Shading activates when temperature rises above **25°C** | |
| - Shading deactivates when temperature falls below **23°C** | |
| - Temperature fluctuations between 23°C and 25°C won't cause switching | |
| **Benefits:** | |
| - Prevents rapid on/off cycling | |
| - Reduces wear on cover motors | |
| - More stable shading behavior | |
| **Recommended Values:** | |
| - For stable weather: 1.0 - 2.0°C | |
| - For variable weather: 2.0 - 3.0°C | |
| - Default: 1.5°C | |
| **Note:** Set to 0 to disable hysteresis | |
| </details> | |
| <details> | |
| <summary><strong>📋 License & Attribution</strong></summary> | |
| This blueprint is open-source. When modifying or redistributing: | |
| - Maintain attribution to the original author | |
| - Link to the original GitHub repository | |
| - Respect all license terms | |
| ⚠️ Copies created via "Take Control" are **not officially supported**. Custom modifications may prevent technical support. | |
| </details> | |
| <details> | |
| <summary><strong>📢 Latest changes</strong></summary> | |
| **Full Changelog**: [CHANGELOG.md](https://github.com/hvorragend/ha-blueprints/blob/main/blueprints/automation/CHANGELOG.md) | |
| # 🚀 CCA 2025.12.03 - Smart State Memory, Flexible Shading Logic, Calendar Integration, Awning Support & Dynamic Sun Elevation & More | |
| ## 🧠 Background State Memory & Force Return | |
| - **Automatic return to target state after force disable** | |
| When `enable_background_state_tracking` is enabled, the cover automatically returns to the position stored in the helper (background state) after a force function is disabled. This enables seamless transitions from emergency states back to normal automation. | |
| - **Continuous helper updates during force functions** | |
| The helper now continues to update in the background even when force functions are active, ensuring the target state always reflects the current automation intent. For example, if Force-Open is active but it's evening close time, the helper stores "close" as the background state. | |
| - **Support for all position types** | |
| Return-to-background works for all cover states: open, close, shading, and ventilation positions. The automation intelligently determines which position to return to based on the helper's background state. | |
| - **Backward compatible (opt-in)** | |
| The feature is disabled by default (`enable_background_state_tracking = false`), preserving existing behavior. Users must explicitly enable it to use the new functionality. | |
| ### 💡 Practical use cases | |
| This feature is particularly useful for emergency and weather-based scenarios where you need manual control but want automation to resume afterward: | |
| - 🌧️ **Rain Protection**: Force-close all covers during heavy rain. When rain stops, covers automatically return to their scheduled state (e.g., shading position during the day, open in the evening). | |
| - 💨 **Wind Protection**: Force-open awnings/blinds during strong winds to prevent damage. Once wind subsides, covers return to sun shading or close position based on time of day. | |
| - ❄️ **Frost Protection**: Force-open covers in winter mornings to prevent ice formation on mechanisms. After sunrise, covers automatically resume normal automation (close for privacy, shade for sun protection). | |
| - 🔥 **Emergency Scenarios**: During fire alarm or security events, force all covers to specific positions. After the event, covers return to their intended automation state without manual intervention. | |
| - 🏠 **Cleaning/Maintenance**: Force covers to full open position for window cleaning. When done, covers automatically return to current schedule (closed in evening, shaded during midday). | |
| - 🌡️ **Extreme Heat Protection**: Temporarily force all covers closed during heat waves. When temperatures normalize, covers return to regular shading schedules. | |
| - 🎬 **Movie Mode**: Force living room covers closed for watching movies during daytime. After movie ends, covers automatically return to open or shading position based on sun conditions. | |
| ### 🔁 Example flow | |
| ``` | |
| 10:00 - Normal schedule: Covers open | |
| 12:00 - Shading active (sun protection) | |
| 14:00 - Heavy rain detected → Force-Close activated | |
| → Covers close immediately | |
| → Helper continues tracking: "shading should be active" | |
| 15:00 - Rain stops → Force-Close deactivated | |
| → Covers automatically return to shading position | |
| 18:00 - Evening close time | |
| → Covers close normally | |
| ``` | |
| ## ☀️ Flexible Shading Logic - AND/OR Condition Builder | |
| - **Powerful AND/OR condition builder** | |
| Decide exactly which shading conditions must all be met (AND) and which act as optional boosters (OR), so you can fine‑tune between conservative and aggressive sun protection without touching your sensor setup. | |
| - **Independent START and END logic** | |
| Shading start and shading end have fully separate configuration paths, allowing strict criteria for starting shading and more relaxed logic for ending it – or the other way around. | |
| - **Per‑condition on/off switches** | |
| Each individual shading trigger (e.g. azimuth, elevation, brightness, temperatures, weather) can be enabled or disabled independently, making it easy to experiment with different strategies or temporarily turn off single inputs. | |
| - **Unified, robust retry behavior** | |
| Both shading start and shading end use a unified retry loop that periodically re‑checks conditions, providing smooth behavior in fast‑changing weather instead of getting stuck or flip‑flopping. | |
| - **Timeouts to prevent endless waiting** | |
| New maximum duration settings for start and end ensure the automation never remains in an infinite “waiting for conditions” state; if the timeout is reached, the loop stops cleanly and waits for a fresh trigger. | |
| ## 🌡️ Forecast & Temperature Intelligence | |
| - **Dedicated forecast inputs for clarity** | |
| Forecast handling is split into two clearly separated fields: one for standard weather entities (`weather.*`) and one for direct forecast temperature sensors (`sensor.*`), so you always know which source you are using. | |
| - **Smart source priority** | |
| When both a weather entity and a forecast temperature sensor are configured, the direct sensor is preferred for faster updates and better performance, without extra API calls. | |
| - **Configurable forecast mode (daily, hourly, or live)** | |
| Choose whether shading should rely on the daily forecast, the hourly forecast, or skip forecast data entirely and use current weather attributes, depending on how “future‑driven” you want your strategy to be. | |
| - **Full hysteresis for all temperature paths** | |
| Hysteresis is applied not only to current temperature sensors 1 and 2, but also to forecast temperature, dramatically reducing unnecessary open/close cycles around threshold values. | |
| ## 📅 Calendar Integration for Time Control | |
| - **New Feature:** Use Home Assistant calendars for flexible cover scheduling! | |
| - ### What's New? | |
| - **Calendar Control Mode**: Select "Use a Home Assistant calendar" in Time Control Configuration | |
| - **Simple Event Titles**: Just create calendar events with titles: | |
| - "Open Cover" for daytime window | |
| - "Close Cover" for evening window | |
| - **Instant Response**: Automation reacts immediately when events start or end | |
| - ### Benefits Over Time Scheduler: | |
| - **More Flexible**: Different times for each day of the week | |
| - **Exception Handling**: Easy to create holiday/vacation schedules | |
| - **No Reloads Needed**: Change times in calendar, automation adapts instantly | |
| - **Visual Planning**: See your schedule in calendar view | |
| - **Family Friendly**: Anyone can adjust schedule in calendar app | |
| - ### Example Schedule: | |
| - **Monday-Friday**: "Open Cover" 06:00-20:00 | |
| - **Saturday-Sunday**: "Open Cover" 08:00-22:00 | |
| - **Vacation Week**: "Close Cover" all day (keep closed) | |
| ## 🔄 Tilt Position Control - Wait Until Idle Mode | |
| - **New optional mode for reliable tilt control on Z-Wave devices** | |
| - Added "Wait Until Idle" mode that monitors cover state before sending tilt commands, solving reliability issues with Z-Wave devices (e.g., Shelly Qubino Wave Shutter) that block tilt during motor movement. | |
| - **New Configuration Options** (Tilt Position Settings): | |
| - **Tilt Wait Mode**: "Fixed Delay" (default) or "Wait Until Idle" | |
| - **Tilt Wait Timeout**: Maximum wait time (default: 30s) | |
| - **Benefits**: | |
| - Reliable tilt without manual delay tuning | |
| - Fully backward compatible | |
| - Timeout protection with warning logs | |
| ## ✨ Seasonal Sun Elevation Adaptation / Dynamic Sun Elevation | |
| - **Problem solved:** Fixed sun elevation thresholds don't work optimally year-round. In winter the sun stays lower, in summer higher. With fixed values your covers open/close at the wrong solar times. | |
| - **Solution:** Optional template sensors automatically adapt thresholds to the season. Thanks, Zanuuu, for this idea in issue #285. | |
| - **Sun Elevation Up Sensor (Dynamic)** – Optional: Sensor for seasonal opening thresholds | |
| - **Sun Elevation Down Sensor (Dynamic)** – Optional: Sensor for seasonal closing thresholds | |
| - New guide with step-by-step instructions: [Dynamic Sun Elevation Guide](https://github.com/hvorragend/ha-blueprints/blob/main/blueprints/automation/DYNAMIC_SUN_ELEVATION.md) | |
| - **Benefits**: | |
| - No more DST adjustments – No manual changes needed when clocks shift | |
| - Year-round optimization – Covers open/close at consistent solar times | |
| - Set and forget – Configure once, works automatically forever | |
| - Smooth transitions – Gradual threshold changes throughout the year | |
| ## 🧱 Stability & Hysteresis Improvements | |
| - **Brightness hysteresis to avoid flicker** | |
| A new brightness hysteresis value prevents the cover from opening and closing repeatedly when light levels hover just above or below your thresholds, protecting both comfort and hardware. | |
| - **Consistent hysteresis on start and end** | |
| Temperature hysteresis is now applied to both start and end conditions for all configured sensors, making shading decisions much more stable at the edges of your comfort band. | |
| - **Smarter shading end detection** | |
| Shading end conditions are checked periodically until they remain stable over the configured waiting period or a timeout is reached, so shading does not end prematurely just because of a short‑lived fluctuation. | |
| ## 🐛 Contact Sensor Race Condition | |
| - When multiple contact sensors changed state simultaneously (e.g., window sensor + lock sensor within milliseconds), mode: single would block the second trigger, causing it to be lost. This led to incorrect lockout protection behavior where covers could close despite active lockout sensors, potentially locking users out. (Fixed #225) | |
| ## ⚡ Resident Mode Fix & Code Refactoring | |
| - **Resident Mode: Cover opens correctly after resident leaves** | |
| Fixed issue (#174) where cover remained closed when resident left room during daytime with all opening conditions met. | |
| Cover now evaluates time window and environmental conditions (brightness, sun) when resident leaves. | |
| Prevents unwanted opening during evening/night hours (after time_down_early). | |
| - "Open immediately" resident option now respects daytime phase (only opens before time_down_early). | |
| ## 🏖️ Awning & Sunshade Support | |
| - CCA now supports now **awnings and sunshades** with inverted position logic! | |
| ### Configuration Examples | |
| #### Roller Shutter (Standard) | |
| ```yaml | |
| Cover Type: Blind / Roller Shutter | |
| Open Position: 100% # Fully up | |
| Shading Position: 25% # Partially down | |
| Close Position: 0% # Fully down | |
| ``` | |
| #### Awning (Inverted) | |
| ```yaml | |
| Cover Type: Awning / Sunshade | |
| Open Position: 0% # Retracted | |
| Shading Position: 75% # Extended for shade | |
| Close Position: 100% # Fully extended | |
| ``` | |
| - **Removed: Shading End Behavior parameter** | |
| The parameter `shading_end_behavior` has been removed. Covers now always return to `open_position` when shading ends (fully up for blinds, retracted for awnings). | |
| - **Important for Existing Users** | |
| If you upgrade from an older CCA version: | |
| - **Blinds/Shutters**: Select "Blind / Roller Shutter" (default) | |
| - **No more changes needed** to your existing configuration | |
| ## 🛠️ Reliability, Fixes & Internal Optimizations | |
| - **Fix for `current_tilt_position` errors** | |
| Roller blind setups that support tilt no longer produce errors when reading or using the `current_tilt_position` attribute. (#284) | |
| - **Safe handling when end conditions change** | |
| If shading end conditions change during the waiting time, the retry logic is reset properly while shading itself remains active, preventing stuck or half‑finished states. | |
| - **Cleaner state handling at midnight** | |
| The nightly reset also clears the newly introduced `pending` and `end‑pending` shading states to start each day with a clean slate. | |
| - **Protection against stale pending states** | |
| An additional safety check at the OPEN anchor can clear pending shading states older than one hour (currently commented out, ready for advanced users who want this safeguard). | |
| - **Stronger “force” trigger safeguards** | |
| Force triggers are cross‑checked with internal `_force_disabled` flags to avoid conflicting commands and race conditions between different features. | |
| - **More robust JSON initialization** | |
| JSON helper usage has been hardened by adding `|default` values for shading and status fields, making the automation more resilient against missing data. | |
| - **Refined shading end behavior with prevent‑options** | |
| The internal logic for ending shading was reworked so that “prevent opening/closing” options are always respected, avoiding unwanted movements when opening is intentionally blocked. | |
| - **Reduced internal duplication** | |
| Repeated calls to `as_timestamp(now()) | round(0)` have been replaced with a shared `ts_now` variable, improving readability and slightly reducing processing overhead. | |
| - **Variables refactoring:** | |
| Consolidated 80+ flag variables into maintainable dictionaries. No functional changes. | |
| ## ⏰ Time Early and Time Late can now be identical for both Open and Close | |
| - **Change:** Both Early and Late times can now be set to the same value (e.g., Time Up Early and Time Up Late both at 07:00, or Time Down Early and Time Down Late both at 22:00) to guarantee opening/closing at that exact time, regardless of environmental conditions. | |
| - **Previous behavior:** With different Early and Late times, covers opened/closed at the early time as soon as conditions were met—even when Brightness/Sun Elevation were disabled. | |
| - **Migration:** | |
| - For fixed opening/closing times without early triggering: Set both Early and Late times to identical values | |
| - For flexible opening/closing (early when conditions met, late as fallback): Keep Early before Late and enable Brightness/Sun Elevation | |
| ## ⚠️ Breaking Changes & Migration | |
| - **New parameter for shading start retries** | |
| The old `shading_start_behavior` has been replaced by `shading_start_max_duration`, giving you fine‑grained control over how long the blueprint should keep retrying shading start conditions. | |
| - Previous presets map approximately as follows: | |
| - `"trigger_reset"` → `0` (no periodic retry, stop immediately) | |
| - `"trigger_periodic"` → `3600–7200` seconds (1–2 hours) | |
| - **Minor change with “Immediate end by sun position” option** | |
| The parameter *End Sun Shading – Immediately When Out Of Range* (`is_shading_end_immediate_by_sun_position`) has been removed; please update your configuration accordingly. Parameter update required! | |
| - **Removed: Shading End Behavior** | |
| Parameter `shading_end_behavior` removed. Covers always return to `open_position` after shading ends. | |
| - **Removed: Time Schedule Helper** | |
| Parameter 'time_schedule_helper' removed. | |
| - **Clean‑up for manual YAML users** | |
| If you maintain your automation YAML manually, remove the deprecated variables `shading_start_behavior` and `is_shading_end_immediate_by_sun_position` to keep your configuration aligned with the new logic. | |
| ## ✅ Config Check Refactoring | |
| - Organized 80+ validation checks into 19 logical sections with clear headers | |
| - Enhanced error messages with more specific language and actionable guidance | |
| - Improved code formatting for consistency (uniform indentation, better line lengths) | |
| - Simplified template expressions (cleaner negation syntax, removed redundant parentheses) | |
| --- | |
| </details> | |
| source_url: https://github.com/hvorragend/ha-blueprints/blob/beta/blueprints/automation/cover_control_automation.yaml | |
| domain: automation | |
| homeassistant: | |
| min_version: "2024.10.0" | |
| input: | |
| blind: | |
| name: "🏠 Cover" | |
| description: >- | |
| Which blind or roller shutter should be automated? | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> Information about a cover group</code></summary> | |
| In principle, you can use a group here. | |
| But please note that there are problems with position detection for a group of covers! | |
| For example, one cover may be at position 100% and the other cover at position 0%. | |
| This results in a wrong group-value of 50%. | |
| My clear recommendation is to create **one automation for each cover**. | |
| </details> | |
| selector: | |
| entity: | |
| filter: | |
| - domain: | |
| - cover | |
| feature_section: | |
| name: "Automation Options" | |
| icon: mdi:window-shutter-cog | |
| collapsed: true | |
| input: | |
| auto_options: | |
| name: "👉 Automation Options" | |
| description: >- | |
| The options on the right-hand side determine whether the cover is allowed to open or to close. | |
| <br /><br /> | |
| Typically, the opening and closing of the roller blinds is managed by a timer (see below). | |
| Activation of these initial two options (1 and 2) is necessary for the blinds to function. | |
| <br /><br /> | |
| <ins>In addition to the time control</ins>, the height of the sun and a brightness sensor can also play | |
| a role in regulating the opening and closing of the blinds. | |
| To access options 3 or 4, activation is required. Up to this point, <ins>none</ins> of these settings | |
| pertain to the protection of the sun. This is activated in the last option. | |
| <br /><br /> | |
| <strong>Important Note:</strong> Configuring the Cover Status Helper is <ins>mandatory</ins> for utilizing | |
| ventilation mode or for sun protection and sunshade control. | |
| <br /><br /> | |
| This can can be enhanced with additional conditions (see below). | |
| However, it’s crucial that options are remain activated; otherwise, the specified conditions will not function. | |
| <br /><br /> | |
| Please ensure that the relevant sensors are are also included. | |
| For instance, the brightness control will only operate if a brightness sensor is specified. | |
| </details> | |
| <br /><br /> | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> Some notes on multiple triggering</code></summary> | |
| Even if multiple opening, closing, shading, etc. is activated, this only works if a trigger is available. | |
| However, the numeric state triggers only trigger under certain circumstances. | |
| Please see notes here [Numeric state triggers](https://www.home-assistant.io/docs/automation/trigger/#numeric-state-trigger) | |
| <em>Crossing the threshold means that the trigger only fires if the state wasn't previously within the threshold. If the current state of your entity is `50` and you set the threshold to `below: 75`, the trigger would not fire if the state changed to e.g. `49` or `72` because the threshold was never crossed. The state would first have to change to e.g. `76` and then to e.g. `74` for the trigger to fire.</em> | |
| </details> | |
| default: | |
| [ | |
| auto_up_enabled, | |
| auto_down_enabled, | |
| auto_sun_enabled, | |
| ] | |
| selector: | |
| select: | |
| options: | |
| - label: "🔼 - Automatically open cover in the morning based on configured time" | |
| value: "auto_up_enabled" | |
| - label: "🔻 - Automatically close cover in the evening based on configured time" | |
| value: "auto_down_enabled" | |
| - label: "🔅 - Add brightness-based control to time-based opening and closing" | |
| value: "auto_brightness_enabled" | |
| - label: "☀️ - Add sun elevation-based control to time-based opening and closing" | |
| value: "auto_sun_enabled" | |
| - label: "💨 - Enable ventilation mode and lockout protection (requires helper, blinds only)" | |
| value: "auto_ventilate_enabled" | |
| - label: "🥵 - Enable automatic sun protection and shading control (requires helper)" | |
| value: "auto_shading_enabled" | |
| multiple: true | |
| sort: false | |
| custom_value: false | |
| mode: list | |
| individual_config: | |
| name: "⚙️ Preventing Configuration" | |
| description: >- | |
| Various different options for some fine adjustments | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> Further description</code></summary> | |
| <ins>Prevent 'set_cover_position' and 'set_cover_tilt_position'</ins><br /> | |
| There are devices that have problems when the two services 'set_cover_position' and 'set_cover_tilt_position' are executed directly one after the other. | |
| For example, there are Shelly devices that use the script [cover_position_tilt.yaml](https://gist.github.com/lukasvice/b364724d84c3ac4e160f7a7d8fa37066) here. | |
| Or Homematic blind actuators, which can better use their own service for this: [homematicip_local.set_cover_combined_position](https://github.com/SukramJ/custom_homematic?tab=readme-ov-file#homematicip_localset_cover_combined_position). | |
| This option can be used to disable the standard services and allows the control to be implemented individually via the addtional actions. | |
| </details> | |
| default: [] | |
| selector: | |
| select: | |
| options: | |
| - label: "🚫 Prevent the cover from moving to a higher position when closing in the evening" | |
| value: "prevent_higher_position_closing" | |
| - label: "🚫 Prevent the position from being lowered when closing in the evening if it is currently shaded" # If the cover is in the shading position, it should not be closed in the evening if the close position is lower than the shading position. The current shading position should therefore be kept." | |
| value: "prevent_lowering_when_closing_if_shaded" | |
| - label: "🚫 Prevent the end of sun shading when the cover is already closed" | |
| value: "prevent_shading_end_if_closed" | |
| - label: "🚫 Prevent the opening of the cover after shading ends (office use)" | |
| value: prevent_opening_after_shading_end | |
| - label: "🚫 Prevent the opening of the cover after ventilation ends (office use)" | |
| value: prevent_opening_after_ventilation_end | |
| - label: "🚫 Prevent the use of 'set_cover_position' and 'set_cover_tilt_position' and only use the additional actions" | |
| value: "prevent_default_cover_actions" | |
| - label: "🚫 Prevent the cover from being opened several times a day" | |
| value: "prevent_opening_multiple_times" | |
| - label: "🚫 Prevent the cover from being closed several times a day" | |
| value: "prevent_closing_multiple_times" | |
| - label: "🚫 Prevent the cover from being shaded several times a day" | |
| value: "prevent_shading_multiple_times" | |
| multiple: true | |
| sort: false | |
| custom_value: false | |
| mode: list | |
| helper_section: | |
| name: "Cover Status Helper" | |
| icon: mdi:form-textbox | |
| collapsed: true | |
| input: | |
| cover_status_options: | |
| name: "🦮 Status detection of the cover" | |
| description: > | |
| It is essential that the cover has the <em>current_position</em> attribute. | |
| Regardless of which option is selected here, the position detection is based on this attribute. | |
| The <em>current_tilt_position</em> attribute is <ins>not</ins> taken into account. | |
| <strong>Important note:</strong><br /> | |
| - I strongly recommend using the Cover Status Helper! The full range of functions is only available if this is configured.<br /> | |
| - The helper is required for shading, ventilation and lockout protection!<br /> | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> Further descriptions</code></summary> | |
| - <ins>Check the current position</ins><br /> | |
| The automation only moves the cover if it is positioned at one of the defined positions (ventilate position, shading position, open position or closed position). Otherwise, the cover has been moved manually. | |
| After a manual drive, it is unclear what is intended to be achieved, so the automation no longer takes action. | |
| To automate the drive of the cover again, it must be moved to one of the defined positions beforehand. | |
| The advantage of this is that you don't have to create a helper in Home Assistant. | |
| Only basic features are available with this setting. | |
| - <ins>Use an external Cover Status Helper</ins><br /> | |
| If you want to be able to do manual overrides and have the automation do the next drive as usual, | |
| then a helper is necessary. Without a helper, the manual override would always be overwritten by the automation. | |
| This has the advantage that the cover does not necessarily have to be in a defined position. | |
| With this choice, advanced functions are possible and you can always rely on the automation. | |
| </details> | |
| default: cover_helper_disabled | |
| selector: | |
| select: | |
| options: | |
| - label: "#️⃣ Check the current position" | |
| value: "cover_helper_disabled" | |
| - label: "🦮 Also use an external Cover Status Helper (better)" | |
| value: "cover_helper_enabled" | |
| cover_status_helper: | |
| name: "🦮 Cover Status Helper" | |
| description: >- | |
| Helper used to store the last cover event. A separate helper must be created for each CCA automation. | |
| *Attention:* You will need to manually create a [input_text](https://my.home-assistant.io/redirect/helpers/) entity with a <ins>length of 254 chars</ins> for this. | |
| default: [] | |
| selector: | |
| entity: | |
| domain: input_text | |
| drive_time: | |
| name: "⏲️ Cover Drive Time" | |
| description: >- | |
| Can be used to recognise manual control. Please round up a little and do not adjust too precisely. Is used to delay the trigger if too much or incorrect position data is sent back. | |
| <br /><br /> | |
| Within this time, it is assumed that CCA has carried out the last action. Otherwise, CCA would react to its own commands and recognize them as manual intervention. | |
| default: 90 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 180.0 | |
| unit_of_measurement: seconds | |
| step: 1.0 | |
| mode: slider | |
| position_section: | |
| name: "Cover Position Settings" | |
| icon: mdi:window-shutter-settings | |
| description: >- | |
| <br /><center> | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> Important notes on configuring the position values</code></summary> | |
| <code> | |
| <strong>Position Logic:</strong><br /> | |
| - For Blinds/Shutters: open_position (100%) > shading_position (25%) > close_position (0%)<br /> | |
| - For Awnings: open_position (0%) < shading_position (75%) < close_position (100%)<br /> | |
| <br /> | |
| Please ensure that all position values are unique and do not conflict with each other.<br /> | |
| collapsed: true | |
| input: | |
| cover_type: | |
| name: "🎯 Cover Type" | |
| description: >- | |
| Select the type of cover you want to control. | |
| <br /><br /> | |
| <strong>Blind/Roller Shutter:</strong> Position 0% = closed (down), 100% = open (up). Shading uses lower positions. | |
| <br /><br /> | |
| <strong>Awning/Sunshade:</strong> Position 0% = retracted (closed), 100% = extended (open). Shading uses higher positions. | |
| <br /><br /> | |
| ⚠️ <strong>Note:</strong> Ventilation and tilt features are not available for awnings. | |
| <br /><br /> | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> Position value examples</code></summary> | |
| <strong>For Blinds/Shutters:</strong><br /> | |
| - Open Position: 100% (fully up)<br /> | |
| - Shading Position: 25% (partially down for sun protection)<br /> | |
| - Ventilate Position: 30% (slightly down for air flow)<br /> | |
| - Close Position: 0% (fully down)<br /> | |
| <br /> | |
| <strong>For Awnings:</strong><br /> | |
| - Open Position: 0% (retracted/closed)<br /> | |
| - Shading Position: 75% (extended for sun protection)<br /> | |
| - Close Position: 100% (fully extended)<br /> | |
| <br /> | |
| <em>Note: Ventilate Position is not used for awnings.</em> | |
| </details> | |
| default: "blind" | |
| selector: | |
| select: | |
| options: | |
| - label: "🪟 Blind / Roller Shutter (Standard)" | |
| value: "blind" | |
| - label: "☂️ Awning / Sunshade (Inverted)" | |
| value: "awning" | |
| mode: dropdown | |
| open_position: | |
| name: "🔼 Open Position" | |
| description: "What position should the cover be moved into when opening?" | |
| default: 100 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 100.0 | |
| unit_of_measurement: "%" | |
| close_position: | |
| name: "🔻 Close Position" | |
| description: "What position should the cover be moved into when closing?" | |
| default: 0 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 100.0 | |
| unit_of_measurement: "%" | |
| ventilate_position: | |
| name: "💨 Ventilate Position" | |
| description: >- | |
| What position should the cover move to when the window is tilted? | |
| If closing is triggered and the contact sensor is 'on', the cover will move to this position instead of closing completely. | |
| <br /><br />Should not be 100. In this case please use 99. And please also note the information in the position tolerance. | |
| default: 30 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 100.0 | |
| unit_of_measurement: "%" | |
| shading_position: | |
| name: "🥵 Sun Shading Position" | |
| description: "To which position should the cover be moved for shading?" | |
| default: 25 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 100.0 | |
| unit_of_measurement: "%" | |
| position_tolerance: | |
| name: "〰️ Position Tolerance" | |
| description: >- | |
| Tolerance to be applied when comparing the current position with the to be position. | |
| These are absolute values. Not relative to the previous position values. | |
| default: 0 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 20.0 | |
| unit_of_measurement: "%" | |
| tilt_position_section: | |
| name: "Cover Tilt Position Settings" | |
| icon: mdi:image-filter-tilt-shift | |
| collapsed: true | |
| input: | |
| cover_tilt_wait_mode: | |
| name: "🔄 Tilt Wait Mode" | |
| description: >- | |
| Choose how CCA waits before sending tilt commands: | |
| **Fixed Delay (Standard)**: Uses the configured tilt delay value. | |
| Works well for most devices. | |
| **Wait Until Idle (Z-Wave)**: Waits until cover reports 'open' or 'closed' | |
| state before sending tilt. Prevents tilt commands from being ignored on | |
| devices that block tilt during motor movement (e.g., Shelly Qubino Wave Shutter). | |
| If your tilt positions are unreliable, try switching to 'Wait Until Idle' mode. | |
| default: "fixed_delay" | |
| selector: | |
| select: | |
| options: | |
| - label: "⏱️ Fixed Delay (Standard)" | |
| value: "fixed_delay" | |
| - label: "⏸️ Wait Until Idle (Z-Wave devices)" | |
| value: "wait_idle" | |
| mode: dropdown | |
| cover_tilt_wait_timeout: | |
| name: "🔄 Tilt Wait Timeout" | |
| description: >- | |
| Maximum time to wait for cover to become idle before sending tilt command. | |
| Only used when 'Wait Until Idle' mode is selected. | |
| default: 30 | |
| selector: | |
| number: | |
| min: 5 | |
| max: 60 | |
| unit_of_measurement: seconds | |
| step: 1 | |
| mode: slider | |
| tilt_delay: | |
| name: "🕛 Default Tilt Delay" | |
| description: >- | |
| Delay between <em>set_cover_position</em> and <em>set_cover_tilt_position</em>. | |
| Only necessary when using the tilt functions. | |
| This separates the two commands in terms of time. | |
| <br /><br />`Optional` | |
| default: 0 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 600.0 | |
| unit_of_measurement: seconds | |
| step: 1.0 | |
| mode: slider | |
| cover_tilt_config: | |
| name: "📐 Tilt Position Feature" | |
| description: >- | |
| ⚠️ <strong>Note:</strong> Tilt control is only available for blinds/shutters, not for awnings. | |
| <br /><br /> | |
| If the cover and the integration support it, the tilt position of the cover can be set. The standard attribute ‘current_tilt_position’ is used for this. | |
| However, the ‘current_position’ attribute is still used exclusively for the actual position detection in the blueprint. | |
| default: cover_tilt_disabled | |
| selector: | |
| select: | |
| options: | |
| - label: "✅ Enable Tilt Position Control" | |
| value: "cover_tilt_enabled" | |
| - label: "❌ Disable Tilt Position Control" | |
| value: "cover_tilt_disabled" | |
| multiple: false | |
| sort: false | |
| custom_value: false | |
| mode: list | |
| cover_tilt_reposition_config: | |
| name: "📐 Tilt Reposition Feature" | |
| description: >- | |
| If Tilt Reposition Feature is enabled you can choose if the blinds are closed before tilting to a new position. | |
| In some cases tilting the blinds in small steps can lead to false positions. This is because of the minimum time the motor needs to run. | |
| If the runtime between current tilt position and target tilt position is to small the motor will not stop at the right position. | |
| Thus the blinds will be preclosed to 0 and then run to the target position. | |
| default: cover_tilt_reposition_disabled | |
| selector: | |
| select: | |
| options: | |
| - label: "✅ Enable Tilt Reposition Control" | |
| value: "cover_tilt_reposition_enabled" | |
| - label: "❌ Disable Tilt Reposition Control" | |
| value: "cover_tilt_reposition_disabled" | |
| multiple: false | |
| sort: false | |
| custom_value: false | |
| mode: list | |
| open_tilt_position: | |
| name: "🔼 Open Tilt Position" | |
| description: "To which tilt position should the cover be moved when opening?" | |
| default: 50 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 100 | |
| unit_of_measurement: "%" | |
| close_tilt_position: | |
| name: "🔻 Close Tilt Position" | |
| description: "To which tilt position should the cover be moved when closing?" | |
| default: 50 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 100 | |
| unit_of_measurement: "%" | |
| ventilate_tilt_position: | |
| name: "💨 Ventilate Tilt Position" | |
| description: "To which tilt position should the cover be moved for ventilation?" | |
| default: 50 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 100 | |
| unit_of_measurement: "%" | |
| shading_tilt_position_0: | |
| name: "🥵 Sun Shading Tilt Position" | |
| description: "Minimum tilt position for shading. The cover will be tilted to this position if the sun elevation is below the value of elevation 1." | |
| default: 0 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 100 | |
| unit_of_measurement: "%" | |
| shading_tilt_position_1: | |
| name: "🥵 Sun Shading Tilt Position 1" | |
| description: "To which tilt position should the cover be moved for shading when the sun is above elevation 1?" | |
| default: 20 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 100 | |
| unit_of_measurement: "%" | |
| shading_tilt_elevation_1: | |
| name: "🥵 Sun Shading Tilt Elevation 1" | |
| description: "Sun elevation for tilt position 1." | |
| default: 20 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 90.0 | |
| unit_of_measurement: ° | |
| mode: slider | |
| step: 1.0 | |
| shading_tilt_position_2: | |
| name: "🥵 Sun Shading Tilt Position 2" | |
| description: "To which tilt position should the cover be moved for shading when the sun is above elevation 2?" | |
| default: 37 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 100 | |
| unit_of_measurement: "%" | |
| shading_tilt_elevation_2: | |
| name: "🥵 Sun Shading Tilt Elevation 2" | |
| description: "Sun elevation for tilt position 2." | |
| default: 30 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 90.0 | |
| unit_of_measurement: ° | |
| mode: slider | |
| step: 1.0 | |
| shading_tilt_position_3: | |
| name: "🥵 Sun Shading Tilt Position 3" | |
| description: "To which tilt position should the cover be moved for shading when the sun is above elevation 3?" | |
| default: 50 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 100 | |
| unit_of_measurement: "%" | |
| shading_tilt_elevation_3: | |
| name: "🥵 Sun Shading Tilt Elevation 3" | |
| description: "Sun elevation for tilt position 3." | |
| default: 48 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 90.0 | |
| unit_of_measurement: ° | |
| mode: slider | |
| step: 1.0 | |
| time_section: | |
| name: "Time Control Configuration" | |
| icon: mdi:clock-time-two-outline | |
| collapsed: true | |
| description: >- | |
| <br /><center> | |
| <details> | |
| <summary><code><strong>CLICK HERE: CCA Time Control - How It Works</strong></code></summary> | |
| <br /> | |
| <strong>⏰ Time Control Modes:</strong><br /> | |
| - <strong>Input Fields:</strong> Use time inputs directly in this section (recommended)<br /> | |
| - <strong>Schedule Helper:</strong> Use external schedule entity for flexible weekday configuration<br /> | |
| - <strong>Disabled:</strong> No time-based triggers, only brightness/sun/manual control<br /> | |
| <br /> | |
| <strong>📖 Detailed Documentation:</strong><br /> | |
| For comprehensive explanations, examples, and timing diagrams, see:<br /> | |
| <a href="https://github.com/hvorragend/ha-blueprints/blob/main/blueprints/automation/TIME_CONTROL_VISUALIZATION.md">Time Control Guide</a><br /> | |
| <br /> | |
| Topics covered: Early/Late times, environmental interaction, workday scheduling, schedule helper behavior, and troubleshooting.<br /> | |
| </code> | |
| </details> | |
| </center><br /> | |
| input: | |
| time_control: | |
| name: "⏲️ Selection of time control options" | |
| description: >- | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> Further descriptions</code></summary> | |
| <br /> | |
| <ins>Input fields for time control</ins><br /> | |
| The times for opening and closing the cover are configured here in the blueprint. | |
| There are various input fields here for this purpose. | |
| Even if you only want to control the covers using brightness values or the sun elevation, | |
| it is important to specify times. The times are used to divide the day into "morning" (up) and "evening" (down). | |
| <br /><br /> | |
| <ins>Calendar Control</ins><br /> | |
| You can use a Home Assistant calendar to define when covers should be open or closed. | |
| Create calendar events with titles "Open Cover" (for daytime) or "Close Cover" (for evening). | |
| The automation reacts immediately when events start or end. | |
| <br /><br /> | |
| <ins>Disable time control and all time triggers</ins><br /> | |
| Finally, you can also deactivate the time control completely. | |
| This means that the roller shutters are not opened or closed via time triggers. | |
| The sun-elevation and brightness control then also runs completely independently. | |
| </details> | |
| default: time_control_input | |
| selector: | |
| select: | |
| options: | |
| - label: "✏️ Use the time input fields in this blueprint section" | |
| value: "time_control_input" | |
| - label: "📅 Use a Home Assistant calendar" | |
| value: "time_control_calendar" | |
| - label: "🚫 Disable time control and all time triggers" | |
| value: "time_control_disabled" | |
| sort: false | |
| multiple: false | |
| custom_value: false | |
| time_up_early: | |
| name: "🔼 Time For Drive Up - Early On Workdays" | |
| description: >- | |
| The earliest time at which the cover may be opened. The cover will be opened if <ins>AFTER</ins> this time the defined brightness | |
| or sun-elevation value is high enough. (**NOTE**: A resident must also be awake if one is defined). | |
| default: "06:00:00" | |
| selector: | |
| time: {} | |
| time_up_early_non_workday: | |
| name: "🔼 Time For Drive Up - Early On Non-Workdays" | |
| description: >- | |
| As directly above, but for non-workdays. | |
| default: "07:00:00" | |
| selector: | |
| time: {} | |
| time_up_late: | |
| name: "🔼 Time For Drive Up - Late On Workdays" | |
| description: >- | |
| The latest time at which the cover should be opened. | |
| If the required brightness or sun-elevation value has <ins>NOT</ins> yet been reached by this time, the cover will still be opened. | |
| (**NOTE**: If a resident has been defined and the resident is still asleep, then the cover will NOT be opened.) | |
| default: "08:00:00" | |
| selector: | |
| time: {} | |
| time_up_late_non_workday: | |
| name: "🔼 Time For Drive Up - Late On Non-Workdays" | |
| description: >- | |
| As directly above, but for non-workdays. | |
| default: "08:00:00" | |
| selector: | |
| time: {} | |
| time_down_early: | |
| name: "🔻 Time For Drive Down - Early On Workdays" | |
| description: >- | |
| The earliest time at which the cover may be closed. | |
| The cover will be closed if <ins>AFTER</ins> this time the defined brightness or sun-elevation value is low enough. | |
| default: "16:00:00" | |
| selector: | |
| time: {} | |
| time_down_early_non_workday: | |
| name: "🔻 Time For Drive Down - Early On Non-Workdays" | |
| description: >- | |
| As directly above, but for non-workdays. | |
| default: "16:00:00" | |
| selector: | |
| time: {} | |
| time_down_late: | |
| name: "🔻 Time For Drive Down - Late On Workdays" | |
| description: >- | |
| The latest time at which the cover should be closed. | |
| If the required brightness or sun-elevation value has <ins>NOT</ins> yet been reached by this time, the cover will still be closed. | |
| <br /> | |
| Please do not enter 0:00, because that would be the next day! | |
| default: "22:00:00" | |
| selector: | |
| time: {} | |
| time_down_late_non_workday: | |
| name: "🔻 Time For Drive Down - Late On Non-Workdays" | |
| description: >- | |
| As directly above, but for non-workdays. | |
| <br /> | |
| Please do not enter 0:00, because that would be the next day! | |
| default: "22:00:00" | |
| selector: | |
| time: {} | |
| workday_sensor: | |
| name: "💼 Sensor For Workday Today" | |
| description: >- | |
| It may be desired to open a cover at a different time on work days than on non-work days. | |
| The corresponding binary sensor can be defined here. If not set, the cover will open every time at time_up_early. | |
| <br /><br /> | |
| I recommend using the [Workday integration](https://www.home-assistant.io/integrations/workday/). | |
| <br /><br /> | |
| Example: `binary_sensor.workday_today` | |
| <br /><br />`Optional` | |
| default: [] | |
| selector: | |
| entity: | |
| filter: | |
| - domain: | |
| - binary_sensor | |
| workday_sensor_tomorrow: | |
| name: "💼 Sensor For Workday Tomorrow (only for closing)" | |
| description: >- | |
| When <ins>closing</ins> the blinds, you have the option of checking the times for tomorrow rather than the current day. | |
| This has the advantage that you can <ins>close</ins> the blinds earlier if <ins>tomorrow</ins> is a working day. | |
| This makes sense if, for example, there is school tomorrow but today is actually still the weekend. | |
| But the child has to go to bed earlier.<br /> | |
| If this field is not configured here, the normal working day sensor is used. | |
| <br /><br /> | |
| I recommend using the [Workday integration](https://www.home-assistant.io/integrations/workday/). | |
| <br /><br /> | |
| Example: `binary_sensor.workday_tomorrow` | |
| <br /><br />`Optional` | |
| default: [] | |
| selector: | |
| entity: | |
| filter: | |
| - domain: | |
| - binary_sensor | |
| calendar_entity: | |
| name: "📅 Calendar Entity" | |
| description: >- | |
| Select a Home Assistant calendar for time control. | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> How to use calendar control</code></summary> | |
| **Event Titles (case-insensitive):** | |
| - **"Open Cover"** - Marks the daytime window (cover should be open) | |
| - **"Close Cover"** - Marks the evening window (cover should be closed) | |
| **How it works:** | |
| - When an "Open Cover" event starts, the automation treats it like morning time | |
| - When a "Close Cover" event starts, the automation treats it like evening time | |
| - Events can span multiple days | |
| - Multiple events per day are supported | |
| **Example schedule:** | |
| - Monday to Friday: "Open Cover" from 06:00-20:00 | |
| - Saturday/Sunday: "Open Cover" from 08:00-22:00 | |
| - Automatically handles weekday/weekend differences | |
| **Advantages over time input:** | |
| - No need for separate workday/non-workday times | |
| - Can define different times for each day | |
| - Can create exceptions (holidays, vacations) | |
| - Changes take effect immediately without automation reload | |
| </details> | |
| default: [] | |
| selector: | |
| entity: | |
| filter: | |
| - domain: calendar | |
| multiple: false | |
| calendar_open_title: | |
| name: "📂 Open Event Title" | |
| description: >- | |
| Title of calendar events that define the daytime window | |
| (case-insensitive match). | |
| Default: "Open Cover". | |
| default: "Open Cover" | |
| selector: | |
| text: {} | |
| calendar_close_title: | |
| name: "📁 Close Event Title" | |
| description: >- | |
| Title of calendar events that define the evening/close window | |
| (case-insensitive match). | |
| Default: "Close Cover". | |
| default: "Close Cover" | |
| selector: | |
| text: {} | |
| brightness_section: | |
| name: "Brightness Configuration" | |
| description: >- | |
| <br /> | |
| <center><code>Settings if the feature ‘🔅 - Enable brightness control’ has been activated above.</code></center><br /> | |
| icon: mdi:brightness-5 | |
| collapsed: true | |
| input: | |
| default_brightness_sensor: | |
| name: "🔅 Default Brightness Sensor" | |
| description: "This default brightness sensor can be defined here, which is used for daily up and down." | |
| default: [] | |
| selector: | |
| entity: | |
| filter: | |
| - domain: | |
| - sensor | |
| brightness_time_duration: | |
| name: "🔅 Brightness Time Duration" | |
| description: "Defines the time to given brightness sensor must be stay above/below the thresholds." | |
| default: 30 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 600.0 | |
| mode: slider | |
| step: 1.0 | |
| unit_of_measurement: seconds | |
| brightness_up: | |
| name: "🔅 Brightness Value For Opening The Cover" | |
| description: "At what brightness value should the cover be opened?" | |
| default: 0 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 100000.0 | |
| unit_of_measurement: lx | |
| step: 1.0 | |
| brightness_down: | |
| name: "🔅 Brightness Value For Closing The Cover" | |
| description: "At what brightness value should the cover be closed? Must be lower then the brightness up value." | |
| default: 0 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 100000.0 | |
| unit_of_measurement: lx | |
| step: 1.0 | |
| brightness_hysteresis: | |
| name: "🔅 Brightness Hysteresis Value" | |
| description: "Cover will open only when brightness exceeds (brightness_up + hysteresis), and close only when it drops below (brightness_down - hysteresis). Prevents frequent open/close cycles." | |
| default: 0 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 10000 | |
| step: 100 | |
| mode: slider | |
| unit_of_measurement: "lx" | |
| sun_section: | |
| name: "Sun Elevation Settings" | |
| description: >- | |
| <br /> | |
| <center><code>Settings if the feature ‘☀️ - Enable sun elevation control’ has been activated above.</code></center><br /> | |
| icon: mdi:weather-sunny | |
| collapsed: true | |
| input: | |
| default_sun_sensor: | |
| name: "☀️ Sun Sensor" | |
| description: >- | |
| Which sensors provides attributes with current azimuth and elevation of sun. | |
| I strongly suggest to use sun.sun ([Sun integration](https://www.home-assistant.io/integrations/sun/)). | |
| Please make sure that the integration is activated and provides the attributes. | |
| This sensor is also used for sun protection / sunshade control. | |
| <br /><br /> | |
| <ins>A few examples of threshold values:</ins> | |
| <ul> | |
| <li>+18° Astronomical Dusk</li> | |
| <li>+12° Nautical Dusk</li> | |
| <li>+6° Dusk</li> | |
| <li>0° Sunrise/Sunset (Default)</li> | |
| <li>-6° Civil Dawn</li> | |
| <li>-12° Nautical Dawn</li> | |
| <li>-18° Astronomical Dawn/Night</li> | |
| </ul> | |
| <br />`Optional` / `Shading` | |
| default: "sun.sun" | |
| selector: | |
| entity: | |
| filter: | |
| - domain: | |
| - sun | |
| sun_time_duration: | |
| name: "☀️ Sun Time Duration" | |
| description: "Defines the time to given sun sensor must be stay above/below the thresholds." | |
| default: 30 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 600.0 | |
| mode: slider | |
| step: 1.0 | |
| unit_of_measurement: seconds | |
| sun_elevation_up: | |
| name: "☀️ Sun Elevation Value For Opening The Cover" | |
| description: "The cover will be <ins>opened</ins> if the sun elevation is over this value" | |
| default: 0 | |
| selector: | |
| number: | |
| min: -90.0 | |
| max: 90.0 | |
| unit_of_measurement: ° | |
| step: 0.1 | |
| mode: slider | |
| sun_elevation_down: | |
| name: "☀️ Sun Elevation Value For Closing The Cover" | |
| description: "The cover will be <ins>closed</ins> if the sun elevation is under this value" | |
| default: 0 | |
| selector: | |
| number: | |
| min: -90.0 | |
| max: 90.0 | |
| unit_of_measurement: ° | |
| step: 0.1 | |
| mode: slider | |
| sun_elevation_up_sensor: | |
| name: "☀️ Sun Elevation Up Sensor (Dynamic - Optional)" | |
| description: >- | |
| Optional template sensor that dynamically calculates elevation threshold for opening based on season. | |
| If configured, this overrides the fixed 'Sun Elevation Up' value above. | |
| <br /><br /> | |
| **Recommended**: Use a template sensor with seasonal interpolation to handle DST and seasonal variations. | |
| See [Dynamic Sun Elevation Guide](https://github.com/hvorragend/ha-blueprints/blob/main/blueprints/automation/DYNAMIC_SUN_ELEVATION.md) for setup instructions. | |
| <br /><br /> | |
| `Optional` | |
| default: [] | |
| selector: | |
| entity: | |
| filter: | |
| - domain: [sensor, input_number] | |
| sun_elevation_down_sensor: | |
| name: "☀️ Sun Elevation Down Sensor (Dynamic - Optional)" | |
| description: >- | |
| Optional template sensor that dynamically calculates elevation threshold for closing based on season. | |
| If configured, this overrides the fixed 'Sun Elevation Down' value above. | |
| <br /><br /> | |
| `Optional` | |
| default: [] | |
| selector: | |
| entity: | |
| filter: | |
| - domain: [sensor, input_number] | |
| contacts_section: | |
| name: "Contact Sensors for Ventilation" | |
| description: >- | |
| <br /> | |
| <center><code>Settings if the feature ‘💨 - Enable ventilation mode’ has been activated above.</code></center><br /> | |
| <center><code>All these settings are optional / A Cover Status Helper is required!</code></center><br /> | |
| icon: mdi:door-closed-lock | |
| collapsed: true | |
| input: | |
| contact_window_opened: | |
| name: "🚪 Contact Sensor For Open Window (Full Ventilation)" | |
| description: >- | |
| Contact sensor of a door or window handle for detecting <ins>total opening</ins>. | |
| If this sensor switches to on/true, the cover is <ins>fully opened</ins>. | |
| At the same time, a lockout protection is <ins>always</ins> activated. | |
| The cover is not closed and the sun shading is not activated when the contact is open. | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> Further descriptions</code></summary> | |
| It must be a binary two-way contact sensor. | |
| If a three-way sensor is available, it must be converted to a binary two-way sensor using a [template sensor](https://www.home-assistant.io/integrations/template/). | |
| See also the [following posts](https://community.home-assistant.io/t/cover-control-automation-cca-a-comprehensive-and-highly-configurable-roller-blind-blueprint/680539/593) in the forum. | |
| <strong>Important note:</strong> Please do not enter the same sensor in both fields for the contact sensors. This does not work and leads to strange situations. | |
| </details> | |
| default: [] | |
| selector: | |
| entity: | |
| filter: | |
| - domain: | |
| - binary_sensor | |
| - input_boolean | |
| contact_window_tilted: | |
| name: "💨 Contact Sensor For Tilted Window (Partial Ventilation)" | |
| description: >- | |
| The contact sensor is required for the <ins>partial</ins> ventilation mode. | |
| If the contact changes to on/true, the cover is moved to the <ins>ventilation</ins> position. | |
| The prerequisite is that the cover is already closed. | |
| After the status changes to off/false, the close position is activated again. | |
| The same applies in the shading-out situation. | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> Further descriptions</code></summary> | |
| It must be a binary two-way contact sensor. | |
| If a three-way sensor is available, it must be converted to a binary two-way sensor using a [template sensor](https://www.home-assistant.io/integrations/template/). | |
| See also the [following posts](https://community.home-assistant.io/t/cover-control-automation-cca-a-comprehensive-and-highly-configurable-roller-blind-blueprint/680539/593) in the forum. | |
| <strong>Important note:</strong> Please do not enter the same sensor in both fields for the contact sensors. This does not work and leads to strange situations. | |
| </details> | |
| default: [] | |
| selector: | |
| entity: | |
| filter: | |
| - domain: | |
| - binary_sensor | |
| - input_boolean | |
| lockout_tilted_options: | |
| name: "💨 Lockout protection for window tilted" | |
| description: >- | |
| For the tilted window (or door, of course), you can individually specify where a lockout protection should be used. | |
| default: [] | |
| selector: | |
| select: | |
| options: | |
| - label: "🛡️ Lockout protection when closing the cover" | |
| value: "lockout_tilted_closing" | |
| - label: "🛡️ Lockout protection when starting the sun shading" | |
| value: "lockout_tilted_shading_start" | |
| - label: "🛡️ Lockout protection when the sun shading is ended" | |
| value: "lockout_tilted_shading_end" | |
| sort: false | |
| multiple: true | |
| custom_value: false | |
| auto_ventilate_options: | |
| name: "💨 Ventilation Configuration" | |
| description: >- | |
| Various different ventilation options. | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> Further descriptions</code></summary> | |
| - <ins>Enables a calculated delay after the window is closed:</ins> | |
| <br /> | |
| Normally, when the window contact is closed, there is no delay in the upcoming drives. If you do want this, you can activate it here. | |
| <br /><br /> | |
| The "Fixed Drive Delay" and "Random Drive Delay" settings which are already used everywhere are then used. | |
| <br /><br /> | |
| - <ins>Allow ventilation even if cover is already in a higher position:</ins> | |
| <br /> | |
| Activate ventilation mode even if the current position of the cover is already higher than the ventilation position. | |
| <br /><br /> | |
| - <ins>Using the ventilation position when the sun shade is ended:</ins> | |
| <br /> | |
| The cover can also be moved to the ventilation position when the sun protection/sun shading is ended. | |
| Normally, the cover would be fully opened when the shading is ended. | |
| <br /> | |
| To be honest, it makes no sense to switch to the ventilation position during the day if more air can flow in when the cover is open. | |
| </details> | |
| default: [] | |
| selector: | |
| select: | |
| options: | |
| - label: "💨 Enables a calculated delay after the window is closed" | |
| value: "ventilation_delay_enabled" | |
| - label: "💨 Allow ventilation even if cover is already in a higher position" | |
| value: "ventilation_if_lower_enabled" | |
| - label: "💨 Using the ventilation position when the sun shading is ended (instead of opening it completely)" | |
| value: "ventilation_after_shading_end" | |
| multiple: true | |
| sort: false | |
| custom_value: false | |
| mode: list | |
| contact_delay_trigger: | |
| name: "🕛 Contact Trigger Delay" | |
| description: >- | |
| How many seconds must the status of the contact sensors be valid for the automation to trigger? | |
| ⚠️ **Race Condition Protection:** | |
| If you have multiple contact sensors (e.g., window sensor + lock sensor) that can change | |
| simultaneously, increase this value to prevent race conditions. | |
| **Symptoms of race conditions:** | |
| - Cover closes despite lockout sensor being active | |
| - Check Home Assistant logs for "max_exceeded: warning" messages | |
| - If you see these warnings frequently, increase this delay | |
| default: 2 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 10.0 | |
| unit_of_measurement: seconds | |
| step: 1.0 | |
| mode: slider | |
| contact_delay_status: | |
| name: "🕛 Contact Sensor Status Delay" | |
| description: >- | |
| How long should the automation wait until a trigger of a contact sensor becomes valid? | |
| This may be necessary if the status of a three-state sensor has an intermediate value for a short time. | |
| default: 3 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 10.0 | |
| unit_of_measurement: seconds | |
| step: 1.0 | |
| mode: slider | |
| shading_section: | |
| name: "Sun Shading / Sun Protection" | |
| description: >- | |
| <br /> | |
| <center><code>Settings if the feature '🥵 - Enable automatic sun protection / sunshade control' has been activated above.</code></center><br /> | |
| <center><code>All these settings are optional / A Cover Status Helper is required! / The attributes of default sun sensor (configured above) is used.</code></center><br /> | |
| icon: mdi:shield-sun-outline | |
| collapsed: true | |
| input: | |
| shading_conditions_start_and: | |
| name: "🌞 Shading START - Required Conditions (AND)" | |
| description: >- | |
| **Conditions that MUST ALL be met to START shading** | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> How START conditions work</code></summary> | |
| **Required (AND) conditions for START:** | |
| - ALL selected conditions must be valid | |
| - If ANY fails, shading will NOT start | |
| - Use for critical conditions | |
| **Example - Conservative approach:** | |
| - Select ALL: Azimuth, Elevation, Brightness, Temperature | |
| - Result: Shading only when everything is perfect | |
| **Example - Flexible approach:** | |
| - Select: Azimuth, Elevation (sun position) | |
| - Leave others for OR list | |
| - Result: Sun must be in range, plus other criteria | |
| </details> | |
| default: | |
| - cond_azimuth | |
| - cond_elevation | |
| - cond_brightness | |
| - cond_temp1 | |
| - cond_temp2 | |
| - cond_forecast_temp | |
| - cond_forecast_weather | |
| selector: | |
| select: | |
| multiple: true | |
| mode: dropdown | |
| options: | |
| - label: "📐 Sun Azimuth (within configured range)" | |
| value: "cond_azimuth" | |
| - label: "📈 Sun Elevation (within configured range)" | |
| value: "cond_elevation" | |
| - label: "🔆 Brightness (above start threshold)" | |
| value: "cond_brightness" | |
| - label: "1️⃣ Temperature Sensor 1 (above threshold)" | |
| value: "cond_temp1" | |
| - label: "2️⃣ Temperature Sensor 2 (above threshold)" | |
| value: "cond_temp2" | |
| - label: "📊 Forecast Temperature (above threshold)" | |
| value: "cond_forecast_temp" | |
| - label: "🌦️ Forecast Weather Conditions (matching configured conditions)" | |
| value: "cond_forecast_weather" | |
| shading_conditions_start_or: | |
| name: "🌞 Shading START - Optional Conditions (OR)" | |
| description: >- | |
| **Conditions where AT LEAST ONE must be met to START shading** | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> When to use OR conditions</code></summary> | |
| **Optional (OR) conditions for START:** | |
| - At least ONE must be valid | |
| - Useful for redundant sensors | |
| - Useful for alternative triggers | |
| **Example - Redundant temperature sensors:** | |
| - AND: Azimuth, Elevation, Brightness | |
| - OR: Temp1, Temp2 | |
| - Result: Sun + Brightness + (Temp1 OR Temp2) | |
| - If Temp1 fails, Temp2 can still trigger | |
| **Combined logic:** | |
| - Final = (ALL AND conditions) AND (ONE OR condition) | |
| </details> | |
| default: [] | |
| selector: | |
| select: | |
| multiple: true | |
| mode: dropdown | |
| options: | |
| - label: "📐 Sun Azimuth (within configured range)" | |
| value: "cond_azimuth" | |
| - label: "📈 Sun Elevation (within configured range)" | |
| value: "cond_elevation" | |
| - label: "🔆 Brightness (above start threshold)" | |
| value: "cond_brightness" | |
| - label: "1️⃣ Temperature Sensor 1 (above threshold)" | |
| value: "cond_temp1" | |
| - label: "2️⃣ Temperature Sensor 2 (above threshold)" | |
| value: "cond_temp2" | |
| - label: "📊 Forecast Temperature (above threshold)" | |
| value: "cond_forecast_temp" | |
| - label: "🌦️ Forecast Weather Conditions (matching configured conditions)" | |
| value: "cond_forecast_weather" | |
| shading_conditions_end_and: | |
| name: "🌥️ Shading END - Required Conditions (AND)" | |
| description: >- | |
| **Conditions that MUST ALL become invalid to END shading** | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> How END conditions work</code></summary> | |
| **Required (AND) conditions for END:** | |
| - ALL selected conditions must become invalid | |
| - Shading continues if ANY is still valid | |
| - Use for stable shading (avoid flickering) | |
| **Example - Quick response:** | |
| - AND: (empty) | |
| - OR: Azimuth, Elevation, Brightness | |
| - Result: End immediately when ANY condition fails | |
| **Example - Stable shading:** | |
| - AND: Brightness, Temp1 | |
| - OR: (empty) | |
| - Result: Keep shading until BOTH brightness AND temp drop | |
| **Tip:** Usually END should be MORE permissive than START | |
| </details> | |
| default: [] | |
| selector: | |
| select: | |
| multiple: true | |
| mode: dropdown | |
| options: | |
| - label: "📐 Sun Azimuth (outside configured range)" | |
| value: "cond_azimuth" | |
| - label: "📈 Sun Elevation (outside configured range)" | |
| value: "cond_elevation" | |
| - label: "🔆 Brightness (below end threshold)" | |
| value: "cond_brightness" | |
| - label: "1️⃣ Temperature Sensor 1 (below threshold)" | |
| value: "cond_temp1" | |
| - label: "2️⃣ Temperature Sensor 2 (below threshold)" | |
| value: "cond_temp2" | |
| - label: "📊 Forecast Temperature (above threshold)" | |
| value: "cond_forecast_temp" | |
| - label: "🌦️ Forecast Weather Conditions (matching configured conditions)" | |
| value: "cond_forecast_weather" | |
| shading_conditions_end_or: | |
| name: "🌥️ Shading END - Optional Conditions (OR)" | |
| description: >- | |
| **Conditions where AT LEAST ONE must become invalid to END shading** | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> Typical END configurations</code></summary> | |
| **Optional (OR) conditions for END:** | |
| - At least ONE must become invalid to end | |
| - Shading ends quickly when conditions change | |
| **Recommended: Quick sun position response** | |
| - AND: (empty) | |
| - OR: Azimuth, Elevation | |
| - Result: End immediately when sun moves out of range | |
| **Alternative: Keep shading longer** | |
| - AND: Azimuth, Elevation | |
| - OR: Brightness, Temperature | |
| - Result: End only when sun is wrong AND (dark OR cold) | |
| **Combined logic:** | |
| - Final = (ALL AND invalid) OR (ONE OR invalid) | |
| </details> | |
| default: | |
| - cond_azimuth | |
| - cond_elevation | |
| - cond_brightness | |
| - cond_temp1 | |
| - cond_temp2 | |
| - cond_forecast_temp | |
| - cond_forecast_weather | |
| selector: | |
| select: | |
| multiple: true | |
| mode: dropdown | |
| options: | |
| - label: "📐 Sun Azimuth (outside configured range)" | |
| value: "cond_azimuth" | |
| - label: "📈 Sun Elevation (outside configured range)" | |
| value: "cond_elevation" | |
| - label: "🔆 Brightness (below end threshold)" | |
| value: "cond_brightness" | |
| - label: "1️⃣ Temperature Sensor 1 (below threshold)" | |
| value: "cond_temp1" | |
| - label: "2️⃣ Temperature Sensor 2 (below threshold)" | |
| value: "cond_temp2" | |
| - label: "📊 Forecast Temperature (above threshold)" | |
| value: "cond_forecast_temp" | |
| - label: "🌦️ Forecast Weather Conditions (matching configured conditions)" | |
| value: "cond_forecast_weather" | |
| shading_azimuth_start: | |
| name: "📐 Sun Shading - Azimuth Start Value" | |
| description: "What is the minimum azimuth at which the sun hits the window? (Shading will start)" | |
| default: 95 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 365 | |
| unit_of_measurement: "°" | |
| shading_azimuth_end: | |
| name: "📐 Sun Shading - Azimuth End Value" | |
| description: "What is the maximum azimuth at which the sun hits the window? (Shading will stop)" | |
| default: 265 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 365 | |
| unit_of_measurement: "°" | |
| shading_elevation_min: | |
| name: "📈 Sun Shading - Elevation Minimum Value" | |
| description: "Starting from which elevation of the sun should the window be shaded? (Here it makes sense to consider surrounding buildings, trees, etc.)." | |
| default: 25 | |
| selector: | |
| number: | |
| min: -90.0 | |
| max: 90.0 | |
| unit_of_measurement: ° | |
| step: 0.1 | |
| mode: slider | |
| shading_elevation_max: | |
| name: "📈 Sun Shading - Elevation Maximum Value" | |
| description: "What is the maximal elevation for elevation? (In most cases, 90 degrees is probably the most reasonable value. However, this can also be different due to surrounding buildings, etc.)." | |
| default: 90 | |
| selector: | |
| number: | |
| min: -90.0 | |
| max: 90.0 | |
| unit_of_measurement: ° | |
| step: 0.1 | |
| mode: slider | |
| shading_brightness_sensor: | |
| name: "🔆 Sun Shading - Brightness Sensor" | |
| description: "This sensor is only used for shading." | |
| default: [] | |
| selector: | |
| entity: | |
| filter: | |
| - domain: | |
| - sensor | |
| shading_sun_brightness_start: | |
| name: "🔆 Sun Shading - Brightness Start Value" | |
| description: "The minimum brightness value from which shading should start. (Must be above the value of brightness end!)" | |
| default: 35000 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 100000.0 | |
| unit_of_measurement: lx | |
| step: 1.0 | |
| shading_sun_brightness_end: | |
| name: "🔆 Sun Shading - Brightness End Value" | |
| description: "The brightness value from which shading is no longer necessary. (Must be below the value of brightness start!)." | |
| default: 25000 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 100000.0 | |
| unit_of_measurement: lx | |
| step: 1.0 | |
| shading_sun_brightness_hysteresis: | |
| name: "🔆 Sun Shading - Brightness Hysteresis" | |
| description: "Hysteresis value to prevent flickering. The brightness must exceed (start + hysteresis) to activate shading and fall below (end - hysteresis) to deactivate it." | |
| default: 0 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 10000.0 | |
| unit_of_measurement: lx | |
| step: 100.0 | |
| shading_temperatur_sensor1: | |
| name: "1️⃣ Sun Shading - Temperature Sensor 1 (eg. indoor)" | |
| description: >- | |
| This is the first temperature sensor used for sun shading logic.<br /> | |
| For example, you can use the current **indoor temperature** as a condition to trigger shading. | |
| selector: | |
| entity: | |
| filter: | |
| - domain: | |
| - sensor | |
| default: [] | |
| shading_min_temperatur1: | |
| name: "1️⃣ Sun Shading - Temperature Sensor 1 Minimum Value" | |
| description: "Minimum temperature for sensor 1 above which shading should occur." | |
| default: 18 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 50.0 | |
| step: 0.1 | |
| mode: slider | |
| unit_of_measurement: "°C" | |
| shading_temperature_hysteresis1: | |
| name: "1️⃣ Sun Shading - Temperature Sensor 1 Hysteresis Value" | |
| description: "Shading will end only when temperature drops below (minimum - hysteresis value) to prevent frequent open/close cycles." | |
| default: 0.2 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 5.0 | |
| step: 0.1 | |
| mode: slider | |
| unit_of_measurement: "°C" | |
| shading_temperatur_sensor2: | |
| name: "2️⃣ Sun Shading - Temperature Sensor 2 (eg. outdoor)" | |
| description: >- | |
| This is an optional secondary temperature sensor, typically used for **outdoor temperature**. | |
| <br /> | |
| It can serve as an additional condition for sun shading logic. | |
| <br /><br /> | |
| This sensor also plays a role in the calculation of the <ins>Sun Shading - Forecast Temperature Value</ins>. Please refer to that section for more details. | |
| default: [] | |
| selector: | |
| entity: | |
| filter: | |
| - domain: | |
| - sensor | |
| shading_min_temperatur2: | |
| name: "2️⃣ Sun Shading - Temperature Sensor 2 Minimum Value" | |
| description: "Minimum temperature for sensor 2 above which shading should occur." | |
| default: 18 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 50.0 | |
| step: 0.1 | |
| mode: slider | |
| unit_of_measurement: "°C" | |
| shading_temperature_hysteresis2: | |
| name: "2️⃣ Sun Shading - Temperature Sensor 2 Hysteresis Value" | |
| description: "Shading will end only when temperature drops below (minimum - hysteresis value) to prevent frequent open/close cycles." | |
| default: 0.2 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 5.0 | |
| step: 0.1 | |
| mode: slider | |
| unit_of_measurement: "°C" | |
| shading_forecast_sensor: | |
| name: "📊 Sun Shading - Forecast Weather Entity" | |
| description: >- | |
| Weather entity for forecast data (temperature & conditions). | |
| This is the primary and recommended method for forecast-based shading. | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> How to configure</code></summary> | |
| Select a weather entity (e.g., weather.home, weather.openweathermap). | |
| The system will: | |
| - Query forecast temperature for comparison | |
| - Check weather conditions if configured | |
| - Use daily or hourly forecast based on your selection below | |
| **Most users should use this field.** | |
| The idea is that it can happen, especially in spring, that the value of the | |
| <em>Forecast Temperature Value</em> is exceeded by strong solar radiation and | |
| the shading would be started. However, in spring you may not want shading, | |
| but the solar radiation as a welcome, free heating is desired. | |
| So you can define via the forecast that shading is only started at an | |
| expected daily maximum temperature. | |
| </details> | |
| default: [] | |
| selector: | |
| entity: | |
| filter: | |
| - domain: weather | |
| shading_forecast_type: | |
| name: "📊 Sun Shading - Forecast Source" | |
| description: >- | |
| Please select whether you want to use the **daily** or **hourly** weather forecast. | |
| This only works if a weather entity has been configured above. | |
| <br /> | |
| The first entry in the forecast array will always be used — this corresponds to the current day or current hour. | |
| <br /><br /> | |
| Note: Your weather entity must support `weather.get_forecasts`, which was introduced in Home Assistant 2023.9. | |
| <br /><br /> | |
| Alternatively, you can choose **not to use** the forecast service at all. | |
| In that case, the current weather attributes from the weather entity will be used instead. | |
| <br /><br /> | |
| **Recommendation:** Using the **daily forecast** is generally preferred for sun shading purposes. | |
| <br /><br /> | |
| **Ignored when using temperature sensor below.** | |
| default: daily | |
| selector: | |
| select: | |
| options: | |
| - label: "Use the daily weather forecast service" | |
| value: "daily" | |
| - label: "Use the hourly weather forecast service" | |
| value: "hourly" | |
| - label: "Do not use a weather forecast, but the current weather attributes" | |
| value: "weather_attributes" | |
| shading_forecast_temp_sensor: | |
| name: "📊 Sun Shading - Direct Temperature Sensor (Alternative)" | |
| description: >- | |
| **Alternative method:** Use a sensor that directly provides forecasted max temperature. | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> When to use this</code></summary> | |
| Use this if: | |
| - Your weather integration provides dedicated forecast sensors | |
| - You want better performance (no forecast service calls) | |
| - You have custom template sensors for forecast temperature | |
| Examples: | |
| - sensor.pirateweather_daytime_high_apparent_temperature_0d | |
| - sensor.met_no_forecast_temperature_max | |
| - sensor.openweathermap_forecast_temperature | |
| **Priority:** If configured, this takes priority over weather entity above. | |
| **Limitation:** Weather condition checks are not available with sensors. | |
| </details> | |
| default: [] | |
| selector: | |
| entity: | |
| filter: | |
| - domain: sensor | |
| shading_forecast_temp: | |
| name: "📊 Sun Shading - Forecast Temperature Value" | |
| description: >- | |
| This setting defines the <strong>minimum temperature threshold</strong> based on the forecast at which shading should be activated. | |
| If the forecasted temperature exceeds this value, the shading system will respond accordingly. | |
| - Minimum temperature threshold for shading activation. | |
| - Works with both weather entity and temperature sensor. | |
| - Leave empty to disable temperature-based forecast shading. | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> Further description</code></summary> | |
| To enhance reliability, the system can compare this threshold against two sources: | |
| - The forecasted temperature (this comparison is always active) | |
| - Temperature Sensor 2 (e.g. outdoor) (can be enabled via the checkbox in the next configuration field) | |
| </details> | |
| default: [] | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 50.0 | |
| step: 0.1 | |
| mode: slider | |
| unit_of_measurement: "°C" | |
| shading_forecast_temp_hysteresis: | |
| name: "📊 Sun Shading - Forecast Temperature Hysteresis" | |
| description: >- | |
| Prevents frequent on/off cycles near threshold. | |
| - Shading starts: forecast > (threshold + hysteresis) | |
| - Shading ends: forecast < (threshold - hysteresis) | |
| default: 0 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 5.0 | |
| step: 0.1 | |
| mode: slider | |
| unit_of_measurement: "°C" | |
| shading_weather_conditions: | |
| name: "🌦️ Sun Shading - Weather Conditions" | |
| description: >- | |
| Check the following weather conditions when activating the shading. | |
| Be cautious when making your selection, as weather forecasts may not always be accurate and could lead to incorrect shading decisions. | |
| And as mentioned above, weather conditions can only be checked if a weather entity has been configured under ‘Forecast Weather Sensor’. | |
| <br /><br /> | |
| **Only works with weather entity, not with temperature sensor.** | |
| <br /> | |
| Be cautious: weather forecasts may not always be accurate. | |
| default: [] | |
| selector: | |
| select: | |
| multiple: true | |
| options: | |
| - "clear-night" | |
| - "clear" | |
| - "cloudy" | |
| - "fog" | |
| - "hail" | |
| - "lightning" | |
| - "lightning-rainy" | |
| - "partlycloudy" | |
| - "pouring" | |
| - "rainy" | |
| - "snowy" | |
| - "snowy-rainy" | |
| - "sunny" | |
| - "windy" | |
| - "windy-variant" | |
| - "exceptional" | |
| shading_config: | |
| name: "🥵 Sun Shading - Configuration" | |
| description: >- | |
| These options allow you to fine-tune how the system handles temperature-based shading. | |
| <p><em>Click on the titles to get further help.</em></p> | |
| <details> | |
| <summary><code><strong>Independent Shading via Temperature Comparison</strong></code></summary> | |
| Enables shading based solely on temperature — <ins>independently of other conditions</ins> like brightness, sun position, or time. | |
| - By default, only the external <em>forecasted temperature</em> is compared with the configured threshold in <em>"Forecast Temperature Value"</em>. | |
| - You can also enable another comparison by selecting the other checkbox. | |
| If any value exceeds the threshold, shading will be activated — even if all other shading conditions are false. | |
| This is especially useful in the early morning: the system can already determine whether shading will be needed later in the day. Instead of fully opening the blinds, it can move them directly into the shading position. | |
| Additionally, one hour before the earliest possible opening time, the system retrieves the latest weather forecast. | |
| This allows it to compare the updated forecasted temperature with the configured threshold and make an early shading decision based on the most current data. | |
| </details> | |
| <br /> | |
| <details> | |
| <summary><code><strong>Additionally compare forecast temperature with Sensor 2</strong></code></summary> | |
| This activates an extended comparison, checking not only whether the external forecasted temperature is above the threshold configured in <em>"Forecast Temperature Value"</em>, but also whether the current reading from <em>"Temperature Sensor 2"</em> (e.g. outdoor) is above the threshold configured for it. The shading condition will be true if <em>one</em> of the following conditions is met: | |
| - The external forecasted temperature exceeds the configured threshold in <em>"Forecast Temperature Value"</em>, or<br /> | |
| - the <em>"Temperature Sensor 2"</em> reports a higher temperature than in <em>"Forecast Temperature Value"</em>. | |
| This mechanism improves the responsiveness and reliability of the shading system by using real-time sensor input as an optional condition, especially when forecast data is uncertain. | |
| Note: This function has nothing to do with normal temperature comparison, but is used exclusively in the context of the forecast function. | |
| </details> | |
| default: [] | |
| selector: | |
| select: | |
| options: | |
| - label: "Independent Shading via Temperature Comparison" | |
| value: "shading_temp_comparison_independent" | |
| - label: "Additionally compare 'Forecast Temperature Value' with 'Temperature Sensor 2'" | |
| value: "shading_compare_forecast_with_sensor2" | |
| multiple: true | |
| sort: false | |
| custom_value: false | |
| mode: list | |
| shading_waitingtime_start: | |
| name: "🥵 Sun Shading - Start Waiting Time" | |
| description: >- | |
| To avoid overloading the motor, a waiting time can be defined here for the start of shading. | |
| The shade will only start if <ins>all</ins> mandatory conditions are fulfilled for the entire waiting time. | |
| This waiting time is also used for the periodic condition checks within the retry loop. | |
| default: 300 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 3600 | |
| unit_of_measurement: seconds | |
| shading_start_max_duration: | |
| name: "🥵 Sun Shading - Maximum duration for shading start retry loop" | |
| description: | | |
| Maximum time to keep retrying shading start conditions after initial trigger. | |
| The Start Waiting Time is used for the periodic condition checks during this retry loop. | |
| If conditions remain unstable for longer than this timeout, the retry loop is stopped. | |
| Shading will not start and waits for a new shading start trigger. | |
| **Use case:** Prevents automation from being stuck in "waiting for stable conditions" | |
| when weather is highly unstable (rapidly changing clouds). | |
| **0 = disabled** (no periodic retry, stops immediately - old "trigger_reset" behavior) | |
| **Recommended: 3600-7200 seconds (1-2 hours)** | |
| default: 7200 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 14400 | |
| step: 300 | |
| unit_of_measurement: "seconds" | |
| shading_waitingtime_end: | |
| name: "🥵 Sun Shading - End Waiting Time" | |
| description: >- | |
| To avoid excessive load on the motor, a waiting time can be defined here before the shading is ended. | |
| Shading ends if one of the conditions is not fulfilled for the entire waiting time. | |
| This waiting time is also used for the periodic condition checks within the retry loop. | |
| default: 300 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 3600 | |
| unit_of_measurement: seconds | |
| shading_end_max_duration: | |
| name: "🥵 Sun Shading - Maximum duration for shading end retry loop" | |
| description: | | |
| Maximum time to keep retrying shading end conditions after initial trigger. | |
| The End Waiting Time is used for the periodic condition checks during this retry loop. | |
| If conditions remain unstable for longer than this timeout, the retry loop is stopped. | |
| Shading remains active and waits for a new shading end trigger. | |
| **Use case:** Prevents automation from being stuck in "waiting for stable conditions" | |
| when weather is highly unstable (rapidly changing clouds). | |
| **0 = disabled** (no periodic retry, behaves like old version) | |
| **Recommended: 3600-7200 seconds (1-2 hours)** | |
| default: 7200 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 14400 | |
| step: 300 | |
| unit_of_measurement: "seconds" | |
| shading_end_immediate_by_sun_position: | |
| name: "🥵 End Sun Shading - Immediately When Out Of Range" | |
| description: >- | |
| If enabled, shading will end immediately (only a few seconds later) when sun position moves outside the defined azimuth or elevation range. | |
| If disabled, the configured waiting time will be used before ending the shading. | |
| default: false | |
| selector: | |
| boolean: {} | |
| # shading_end_behavior: | |
| # name: "🥵 Sun Shading - End Behavior" | |
| # description: >- | |
| # Configure how the cover should behave when sun shading ends. | |
| # Choose "Move to close position" for awnings to retract when shading ends. | |
| # default: "open_position" | |
| # selector: | |
| # select: | |
| # options: | |
| # - label: "🔼 Move to open position (default for blinds / roller shutters)" | |
| # value: "open_position" | |
| # - label: "🔻 Move to close position (for awnings)" | |
| # value: "close_position" | |
| # multiple: false | |
| # sort: false | |
| # custom_value: false | |
| # mode: list | |
| resident_section: | |
| name: "Resident Settings" | |
| description: >- | |
| <br /> | |
| <center><code> | |
| (1) The purpose of resident mode is to the close the cover (without checking the defined times) when the resident sensor switches to ‘on/true’. For example, when a resident goes to sleep. | |
| <br /> | |
| (2) The cover will stay closed as long as the sensor remains in this state. | |
| <br /> | |
| (3) When the resident sensor switches to ‘off/false’, the cover is automatically opened in the morning. | |
| <br /> | |
| (4) In addition, the usual automatic opening of the cover is prevented as long as the sensor is set to ‘on/true’ or the resident. | |
| <br /><br /> | |
| All these settings are optional. | |
| </code></center><br /> | |
| icon: mdi:human-male-female | |
| collapsed: true | |
| input: | |
| resident_sensor: | |
| name: "🛌 Resident Sensor" | |
| description: "You can use this to define a resident (input_boolean or binary_sensor) for the room" | |
| default: [] | |
| selector: | |
| entity: | |
| domain: | |
| - input_boolean | |
| - binary_sensor | |
| - switch | |
| resident_config: | |
| name: "🛌 Resident Configuration" | |
| description: "Additional configuration options" | |
| default: [] | |
| selector: | |
| select: | |
| options: | |
| - label: "🔼 Open the cover shortly after the sensor switches to 'off/false'?" | |
| value: "resident_opening_enabled" | |
| - label: "🔻 Close the cover shortly after the sensor switches to 'on/true'?" | |
| value: "resident_closing_enabled" | |
| - label: "🥵 Allow sun protection when resident is still present" | |
| value: "resident_allow_shading" | |
| - label: "🔼 Allow opening the cover when resident is still present" | |
| value: "resident_allow_opening" | |
| - label: "💨 Allow ventilation when resident is still present" | |
| value: "resident_allow_ventilation" | |
| multiple: true | |
| sort: false | |
| custom_value: false | |
| mode: list | |
| override_section: | |
| name: "Manual Override" | |
| description: >- | |
| <br /> | |
| <center><code>A Cover Status Helper is required!</code></center><br /> | |
| icon: mdi:debug-step-over | |
| collapsed: true | |
| input: | |
| ignore_after_manual_config: | |
| name: "🖐️ Ignoring/override after manual position changes" | |
| description: >- | |
| Ignore or override the following actions after manual position changes. | |
| <details> | |
| <summary><code><strong>CLICK HERE:</strong> Further description</code></summary> | |
| Ultimately, this means that the cover will not be opened, closed, etc. | |
| if a manual interaction has previously been made, e.g. using a wall switch. | |
| The reason behind this is that the human being wins with his decision and his | |
| conscious decision is weighted higher than the upcoming action of the automation. | |
| As soon as a cover has been moved manually, the status is recorded in the Cover Status Helper. | |
| This usually means that a person has deliberately decided against a status. | |
| - If option is not activated: The covers are moved even if a manual correction has been made. | |
| - If option is activated: The action to open, close, etc. is not performed because a conscious decision was made to do otherwise due to a manual intervention. | |
| A Cover Status Helper is required! | |
| </details> | |
| default: [] | |
| selector: | |
| select: | |
| options: | |
| - label: "🔼 Ignore/override next automatic opening after manual position changes" | |
| value: "ignore_opening_after_manual" | |
| - label: "🔻 Ignore/override next automatic closing after manual position changes" | |
| value: "ignore_closing_after_manual" | |
| - label: "💨 Ignore/override next automatic ventilation after manual position changes" | |
| value: "ignore_ventilation_after_manual" | |
| - label: "🥵 Ignore/override next automatic sun shading after manual position changes" | |
| value: "ignore_shading_after_manual" | |
| multiple: true | |
| sort: false | |
| custom_value: false | |
| mode: list | |
| reset_override_config: | |
| name: "🗑️ Reset manual override" | |
| description: >- | |
| If the detection of the manual position change was activated above, you may need a way to reset this status. | |
| Otherwise, the next cover movements will be permanently ignored or overridden. | |
| Or you have not activated an individual action, e.g. when closing the covers, which resets the status. | |
| default: reset_disabled | |
| selector: | |
| select: | |
| options: | |
| - label: "No timed reset for manual override" | |
| value: "reset_disabled" | |
| - label: "Reset at a specified time (see below)" | |
| value: "reset_fixed_time" | |
| - label: "Reset after a timeout in minutes (see below)" | |
| value: "reset_timeout" | |
| multiple: false | |
| sort: false | |
| custom_value: false | |
| mode: list | |
| reset_override_time: | |
| name: "🗑️ Time to reset manual override" | |
| description: "At what time do you want the manual detection to be reset?" | |
| default: "00:01:00" | |
| selector: | |
| time: {} | |
| reset_override_timeout: | |
| name: "🗑️ Number of minutes until reset manual override" | |
| description: "After how many minutes should it be reset?" | |
| default: 5 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 1440 | |
| unit_of_measurement: minutes | |
| delay_section: | |
| description: >- | |
| <br /> | |
| <center><code>Take into account that, for example, waiting times are added when brightness changes or shading is started. It is therefore better to use smaller values here. It is only a matter of separating the different covers in terms of time.</code></center><br /> | |
| name: "Delay Settings" | |
| icon: mdi:timer-outline | |
| collapsed: true | |
| input: | |
| drive_delay_fix: | |
| name: "🕛 Fixed Drive Delay" | |
| description: >- | |
| Fixed drive delay to avoid radio interferences. | |
| <br /><br />`Optional` | |
| default: 0 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 600.0 | |
| unit_of_measurement: seconds | |
| step: 1.0 | |
| mode: slider | |
| drive_delay_random: | |
| name: "🕛 Random Drive Delay" | |
| description: >- | |
| Additional random delay. | |
| <br /><br />`Optional` | |
| default: 5 | |
| selector: | |
| number: | |
| min: 0.0 | |
| max: 600.0 | |
| unit_of_measurement: seconds | |
| step: 1.0 | |
| mode: slider | |
| condition_section: | |
| name: "Additional Conditions" | |
| description: >- | |
| <br /> | |
| <center><code>All these settings are optional</code></center><br /> | |
| icon: mdi:help-rhombus-outline | |
| collapsed: true | |
| input: | |
| auto_global_condition: | |
| name: "❓ Additional Condition for the entire automation" | |
| description: >- | |
| This condition allows you to control the execution of the <ins>entire</ins> automation dynamically and outside of the blueprint configuration. | |
| With this option you could enable a party mode. | |
| <br /><br /> | |
| If the result of this condition is <ins>true</ins>, the automation will continue.<br /> | |
| The result of the conditions must be <ins>false</ins>, for the automation to stop in this sequence. | |
| <br /><br /> | |
| Forcing Open/Close/Shading/Ventilation is therefore only possible if this condition remains empty or becomes valid. | |
| default: [] | |
| selector: | |
| condition: {} | |
| auto_up_condition: | |
| name: "🔼 Additional Condition For Opening The Cover" | |
| description: >- | |
| This condition can be used to dynamically control the <ins>opening</ins> of the cover. | |
| You can use this, for example, if the covers normally don't open, but you really want to do it on vacation. | |
| <br /><br /> | |
| If the result of this condition is <ins>true</ins>, the automation will continue.<br /> | |
| The result of the conditions must be <ins>false</ins>, for the automation to stop in this sequence. | |
| default: [] | |
| selector: | |
| condition: {} | |
| auto_down_condition: | |
| name: "🔻 Additional Condition For Closing The Cover" | |
| description: >- | |
| This condition can be used to dynamically control the <ins>closing</ins> of the cover. | |
| You can use this, for example, at Christmas time or if you want the covers to behave differently while on vacation. | |
| <br /><br /> | |
| If the result of this condition is <ins>true</ins>, the automation will continue.<br /> | |
| The result of the conditions must be <ins>false</ins>, for the automation to stop in this sequence. | |
| default: [] | |
| selector: | |
| condition: {} | |
| auto_ventilate_condition: | |
| name: "💨 Additional Condition For Activating Ventilation" | |
| description: >- | |
| This condition can be used to dynamically control the <ins>start of the ventilation</ins> of the cover. | |
| <br /><br /> | |
| If the result of this condition is <ins>true</ins>, the automation will continue.<br /> | |
| The result of the conditions must be <ins>false</ins>, for the automation to stop in this sequence. | |
| default: [] | |
| selector: | |
| condition: {} | |
| auto_ventilate_end_condition: | |
| name: "💨 Additional Condition For Disabling Ventilation" | |
| description: >- | |
| This condition can be used to dynamically control the <ins>end of the ventilation</ins> of the cover. | |
| <br /><br /> | |
| If the result of this condition is <ins>true</ins>, the automation will continue.<br /> | |
| The result of the conditions must be <ins>false</ins>, for the automation to stop in this sequence. | |
| default: [] | |
| selector: | |
| condition: {} | |
| auto_shading_start_condition: | |
| name: "🥵 Additional Condition When Activating Sun Shading" | |
| description: >- | |
| This condition can be used to dynamically control the <ins>shading-IN-automation</ins> of the cover. | |
| This can be useful if you want to temporarily disable automation (e.g. because of control by other automations). | |
| <br /><br /> | |
| If the result of this condition is <ins>true</ins>, the automation will continue.<br /> | |
| The result of the conditions must be <ins>false</ins>, for the automation to stop in this sequence. | |
| <br /> | |
| Another example: Here you could also set that the shading is only triggered in the summer season. | |
| default: [] | |
| selector: | |
| condition: {} | |
| auto_shading_tilt_condition: | |
| name: "🥵 Additional Condition For Sun Shading Tilt" | |
| description: >- | |
| This condition can be used to dynamically control the <ins>shading_tilt-IN-automation</ins> | |
| of the cover. This can be useful if you want to temporarily disable automation | |
| (e.g. because of control by other automations). <br /> Another example: | |
| Here you could also set that the tilting is only triggered in the summer season. | |
| <br /><br /> | |
| If the result of this condition is <ins>true</ins>, the automation will continue.<br /> | |
| The result of the conditions must be <ins>false</ins>, for the automation to stop in this sequence. | |
| default: [] | |
| selector: | |
| condition: {} | |
| auto_shading_end_condition: | |
| name: "🥵 Additional Condition When Deactivating Sun Shading" | |
| description: >- | |
| This condition can be used to dynamically control the <ins>shading-OUT-automation</ins> of the cover. | |
| This can be useful if you want to temporarily disable automation (e.g. because of control by other automations). | |
| <br /> | |
| If the result of this condition is <ins>true</ins>, the automation will continue.<br /> | |
| The result of the conditions must be <ins>false</ins>, for the automation to stop in this sequence. | |
| default: [] | |
| selector: | |
| condition: {} | |
| force_section: | |
| name: "Force Features" | |
| description: >- | |
| <br /> | |
| This can be used for the following purposes under certain circumstances: Antifreeze, RainProtection or WindProtection. | |
| <br /><br /> | |
| Note: | |
| - However, after forcing a state, you must ensure that you move to the correct target position yourself. You are responsible for getting CCA back on track. | |
| - This cannot be performed in CCA and must therefore be done via a separate automation. Presumably in the same automation that sets the "Force"-boolean. It is sufficient, by the way, if a configured position is targeted. CCA then recognises the status. | |
| - Force is a final state that cannot be cancelled or resetted by CCA. | |
| - The default automations (open, close, and further) will never be able to override a force. The force feature must therefore be cancelled manually beforehand. | |
| <br /><br /> | |
| <center><code>All these settings are optional</code></center><br /> | |
| icon: mdi:arm-flex | |
| collapsed: true | |
| input: | |
| enable_background_state_tracking: | |
| name: "🔙 Return to Target State After Force Disable" | |
| description: >- | |
| If enabled, the cover will automatically return to the target position stored in the helper (background state) when a force function is disabled. | |
| The helper will also continue to update its status in the background even when force functions are active, so the cover can always return to the current target state. | |
| default: false | |
| selector: | |
| boolean: {} | |
| auto_up_force: | |
| name: "🔼 Force Immediate Opening via Entity" | |
| description: >- | |
| If the status of this entity changes to on or true, the cover is opened immediately and without further checking. | |
| default: [] | |
| selector: | |
| entity: | |
| domain: | |
| - input_boolean | |
| - binary_sensor | |
| - switch | |
| auto_down_force: | |
| name: "🔻 Force Immediate Closing via Entity" | |
| description: >- | |
| If the status of this entity changes to on or true, the cover is closed immediately and without further checking. | |
| default: [] | |
| selector: | |
| entity: | |
| domain: | |
| - input_boolean | |
| - binary_sensor | |
| - switch | |
| auto_ventilate_force: | |
| name: "💨 Force Immediate Ventilation via Entity" | |
| description: >- | |
| If the status of this entity changes to on or true, the cover is immediately set to ventilation mode and without further checking. | |
| default: [] | |
| selector: | |
| entity: | |
| domain: | |
| - input_boolean | |
| - binary_sensor | |
| - switch | |
| auto_shading_start_force: | |
| name: "🥵 Force Activation Sun Shading via Entity" | |
| description: >- | |
| If the status of this entity changes to on or true, the shading is immediately activated and without further checking. | |
| default: [] | |
| selector: | |
| entity: | |
| domain: | |
| - input_boolean | |
| - binary_sensor | |
| - switch | |
| actions_section: | |
| name: "Additional Actions" | |
| description: >- | |
| <br /> | |
| <center><code>All these settings are optional</code></center><br /> | |
| icon: mdi:run | |
| collapsed: true | |
| input: | |
| auto_up_action_before: | |
| name: "🔼 Additional Actions Before Opening The Cover" | |
| description: "Additional actions to run <ins>before</ins> opening the cover" | |
| default: [] | |
| selector: | |
| action: {} | |
| auto_up_action: | |
| name: "🔼 Additional Actions After Opening The Cover" | |
| description: "Additional actions to run <ins>after</ins> opening the cover" | |
| default: [] | |
| selector: | |
| action: {} | |
| auto_down_action_before: | |
| name: "🔻 Additional Actions Before Closing The Cover" | |
| description: "Additional actions to run <ins>before</ins> closing the cover" | |
| default: [] | |
| selector: | |
| action: {} | |
| auto_down_action: | |
| name: "🔻 Additional Actions After Closing The Cover" | |
| description: "Additional actions to run <ins>after</ins> closing the cover" | |
| default: [] | |
| selector: | |
| action: {} | |
| auto_ventilate_action_before: | |
| name: "💨 Additional Actions Before Ventilating The Cover" | |
| description: "Additional actions to run <ins>before</ins> ventilating the cover" | |
| default: [] | |
| selector: | |
| action: {} | |
| auto_ventilate_action: | |
| name: "💨 Additional Actions After Ventilating The Cover" | |
| description: "Additional actions to run <ins>after</ins> ventilating the cover" | |
| default: [] | |
| selector: | |
| action: {} | |
| auto_shading_start_action_before: | |
| name: "🥵 Additional Actions Before Activating Sun Shading" | |
| description: "Additional actions to run <ins>before</ins> activating sun shading" | |
| default: [] | |
| selector: | |
| action: {} | |
| auto_shading_start_action: | |
| name: "🥵 Additional Actions After Activating Sun Shading" | |
| description: "Additional actions to run <ins>after</ins> activating sun shading" | |
| default: [] | |
| selector: | |
| action: {} | |
| auto_shading_end_action_before: | |
| name: "🥵 Additional Actions Before Disabling Sun Shading" | |
| description: "Additional actions to run <ins>before</ins> disabling sun shading" | |
| default: [] | |
| selector: | |
| action: {} | |
| auto_shading_end_action: | |
| name: "🥵 Additional Actions After Disabling Sun Shading" | |
| description: "Additional actions to run <ins>after</ins> disabling sun shading" | |
| default: [] | |
| selector: | |
| action: {} | |
| auto_manual_action: | |
| name: "🖐️ Additional Actions After Manual Change" | |
| description: "Additional actions after a manual change to the covers" | |
| default: [] | |
| selector: | |
| action: {} | |
| auto_override_reset_action: | |
| name: "🗑️ Additional Actions After Override Reset" | |
| description: "Additional actions to be taken after resetting the manual override" | |
| default: [] | |
| selector: | |
| action: {} | |
| configcheck_section: | |
| name: "Configuration Check" | |
| icon: mdi:invoice-text-check-outline | |
| collapsed: true | |
| input: | |
| check_config: | |
| name: "✔️ Check Configuration" | |
| description: >- | |
| With this boolean, you can enable or disable the basic plausibility check for the configuration. | |
| The check only takes place if the automation is executed manually. | |
| default: false | |
| selector: | |
| boolean: {} | |
| check_config_debuglevel: | |
| name: "✔️ Check Configuration - Debug level" | |
| description: >- | |
| Choose the debug level for Syslog messages in case of configuration issues | |
| <br /> | |
| Please make sure that it suits your Home Assistant logger default level. | |
| default: "info" | |
| selector: | |
| select: | |
| multiple: false | |
| mode: dropdown | |
| options: | |
| - "critical" | |
| - "debug" | |
| - "error" | |
| - "info" | |
| - "warning" | |
| ################################################################################ | |
| # TRIGGER VARIABLES | |
| ################################################################################ | |
| trigger_variables: | |
| blind: !input blind | |
| cover_type: !input cover_type | |
| is_awning: "{{ cover_type == 'awning' }}" | |
| resident_sensor: !input resident_sensor | |
| # Positions | |
| open_position: !input open_position | |
| close_position: !input close_position | |
| ventilate_position: !input ventilate_position | |
| shading_position: !input shading_position | |
| position_tolerance: !input position_tolerance | |
| # Tilt positions | |
| open_tilt_position: !input open_tilt_position | |
| close_tilt_position: !input close_tilt_position | |
| ventilate_tilt_position: !input ventilate_tilt_position | |
| shading_tilt_elevation_1: !input shading_tilt_elevation_1 | |
| shading_tilt_elevation_2: !input shading_tilt_elevation_2 | |
| shading_tilt_elevation_3: !input shading_tilt_elevation_3 | |
| shading_tilt_position_0: !input shading_tilt_position_0 | |
| shading_tilt_position_1: !input shading_tilt_position_1 | |
| shading_tilt_position_2: !input shading_tilt_position_2 | |
| shading_tilt_position_3: !input shading_tilt_position_3 | |
| # Feature option selections | |
| auto_options: !input auto_options | |
| cover_tilt_config: !input cover_tilt_config | |
| cover_tilt_reposition_config: !input cover_tilt_reposition_config | |
| is_cover_tilt_enabled: "{{ not is_awning and 'cover_tilt_enabled' in cover_tilt_config }}" | |
| # Tilt wait mode configuration | |
| tilt_wait_mode: !input cover_tilt_wait_mode | |
| tilt_wait_timeout: !input cover_tilt_wait_timeout | |
| is_tilt_wait_idle_mode: "{{ tilt_wait_mode == 'wait_idle' }}" | |
| # Time configuration | |
| time_up_early: !input time_up_early | |
| time_up_early_non_workday: !input time_up_early_non_workday | |
| time_up_late: !input time_up_late | |
| time_up_late_non_workday: !input time_up_late_non_workday | |
| time_down_early: !input time_down_early | |
| time_down_early_non_workday: !input time_down_early_non_workday | |
| time_down_late: !input time_down_late | |
| time_down_late_non_workday: !input time_down_late_non_workday | |
| workday_sensor_today: !input workday_sensor | |
| workday_sensor_tomorrow: !input workday_sensor_tomorrow | |
| time_control: !input time_control | |
| # Calendar configuration | |
| calendar_entity: !input calendar_entity | |
| calendar_open_title: !input calendar_open_title | |
| calendar_close_title: !input calendar_close_title | |
| calendar_open_title_lc: "{{ (calendar_open_title | default('')) | lower | trim }}" | |
| calendar_close_title_lc: "{{ (calendar_close_title | default('')) | lower | trim }}" | |
| is_calendar_enabled: "{{ 'time_control_calendar' in time_control and calendar_entity != [] }}" | |
| # Brightness configuration | |
| default_brightness_sensor: !input default_brightness_sensor | |
| brightness_up: !input brightness_up | |
| brightness_down: !input brightness_down | |
| brightness_hysteresis: !input brightness_hysteresis | |
| # Sun configuration | |
| default_sun_sensor: !input default_sun_sensor | |
| sun_elevation_up: !input sun_elevation_up | |
| sun_elevation_down: !input sun_elevation_down | |
| # Dynamic sun elevation sensors | |
| sun_elevation_up_sensor: !input sun_elevation_up_sensor | |
| sun_elevation_down_sensor: !input sun_elevation_down_sensor | |
| # Ventilation/Contact sensors | |
| contact_window_opened: !input contact_window_opened | |
| contact_window_tilted: !input contact_window_tilted | |
| lockout_tilted_options: !input lockout_tilted_options | |
| # Shading configuration | |
| shading_azimuth_start: !input shading_azimuth_start | |
| shading_azimuth_end: !input shading_azimuth_end | |
| shading_elevation_min: !input shading_elevation_min | |
| shading_elevation_max: !input shading_elevation_max | |
| shading_sun_brightness_start: !input shading_sun_brightness_start | |
| shading_sun_brightness_end: !input shading_sun_brightness_end | |
| shading_sun_brightness_hysteresis: !input shading_sun_brightness_hysteresis | |
| # Shading sensors | |
| shading_brightness_sensor: !input shading_brightness_sensor | |
| shading_temperatur_sensor1: !input shading_temperatur_sensor1 | |
| shading_temperatur_sensor2: !input shading_temperatur_sensor2 | |
| shading_min_temperatur1: !input shading_min_temperatur1 | |
| shading_min_temperatur2: !input shading_min_temperatur2 | |
| shading_temperature_hysteresis1: !input shading_temperature_hysteresis1 | |
| shading_temperature_hysteresis2: !input shading_temperature_hysteresis2 | |
| # Shading forecast configuration | |
| shading_forecast_sensor: !input shading_forecast_sensor | |
| shading_forecast_temp_sensor: !input shading_forecast_temp_sensor | |
| shading_forecast_type: !input shading_forecast_type | |
| shading_forecast_temp: !input shading_forecast_temp | |
| shading_forecast_temp_hysteresis: !input shading_forecast_temp_hysteresis | |
| shading_weather_conditions: !input shading_weather_conditions | |
| # Shading condition selections | |
| shading_conditions_start_and: !input shading_conditions_start_and | |
| shading_conditions_start_or: !input shading_conditions_start_or | |
| shading_conditions_end_and: !input shading_conditions_end_and | |
| shading_conditions_end_or: !input shading_conditions_end_or | |
| # Lockout protection flag computation | |
| lockout_tilted_when_closing: "{{ 'lockout_tilted_closing' in lockout_tilted_options }}" | |
| lockout_tilted_when_shading_starts: "{{ 'lockout_tilted_shading_start' in lockout_tilted_options }}" | |
| lockout_tilted_when_shading_ends: "{{ 'lockout_tilted_shading_end' in lockout_tilted_options }}" | |
| # Feature enable flag computation | |
| is_shading_enabled: "{{ 'auto_shading_enabled' in auto_options }}" | |
| is_up_enabled: "{{ 'auto_up_enabled' in auto_options }}" | |
| is_down_enabled: "{{ 'auto_down_enabled' in auto_options }}" | |
| is_brightness_enabled: "{{ 'auto_brightness_enabled' in auto_options }}" | |
| is_sun_elevation_enabled: "{{ 'auto_sun_enabled' in auto_options }}" | |
| is_time_field_enabled: "{{ 'time_control_input' in time_control }}" | |
| is_time_control_disabled: "{{ 'time_control_disabled' in time_control }}" | |
| # Ventilation and tilt are only available for blinds, not awnings | |
| is_ventilation_enabled: "{{ not is_awning and 'auto_ventilate_enabled' in auto_options }}" | |
| # Reset override flag computation | |
| reset_override_config: !input reset_override_config | |
| reset_override_time: !input reset_override_time | |
| reset_override_timeout: !input reset_override_timeout | |
| is_reset_disabled: "{{ 'reset_disabled' in reset_override_config }}" | |
| is_reset_fixed_time: "{{ 'reset_fixed_time' in reset_override_config }}" | |
| is_reset_timeout: "{{ 'reset_timeout' in reset_override_config }}" | |
| # Force entities | |
| auto_up_force: !input auto_up_force | |
| auto_down_force: !input auto_down_force | |
| auto_ventilate_force: !input auto_ventilate_force | |
| auto_shading_start_force: !input auto_shading_start_force | |
| # Cover status helper configuration | |
| cover_status_options: !input cover_status_options | |
| cover_status_helper: !input cover_status_helper | |
| # Invalid states list - Shared constant | |
| invalid_states: | |
| - '' # Empty string | |
| - 'unavailable' # Entity unavailable | |
| - 'unknown' # State unknown | |
| - 'none' # Python none (lowercase) | |
| - 'None' # Python None (capitalized) | |
| - 'null' # JSON null | |
| - 'query failed' # Failed sensor queries | |
| - [] # Empty list | |
| ################################################################################ | |
| # VARIABLES | |
| ################################################################################ | |
| variables: | |
| version: "2025.12.03" | |
| # Basic entity state reading | |
| blind_entities: "{{ expand(blind) | map(attribute='entity_id') | list }}" | |
| current_position: "{{ state_attr(blind, 'current_position') | int(default=101) }}" | |
| current_tilt_position: "{{ state_attr(blind, 'current_tilt_position') | int(default=101) }}" | |
| # COVER TYPE & POSITION COMPARISON LOGIC | |
| # Position comparison results | |
| # Handles both blind (0%=closed) and awning (0%=retracted) logic automatically | |
| position_comparisons: | |
| # Current position vs targets (considers awning inversion) | |
| current_above_open: "{{ current_position < open_position if is_awning else current_position > open_position }}" | |
| current_above_close: "{{ current_position < close_position if is_awning else current_position > close_position }}" | |
| current_above_shading: "{{ current_position < shading_position if is_awning else current_position > shading_position }}" | |
| current_above_ventilate: "{{ current_position < ventilate_position if is_awning else current_position > ventilate_position }}" | |
| current_below_open: "{{ current_position > open_position if is_awning else current_position < open_position }}" | |
| current_below_close: "{{ current_position > close_position if is_awning else current_position < close_position }}" | |
| current_below_shading: "{{ current_position > shading_position if is_awning else current_position < shading_position }}" | |
| current_below_ventilate: "{{ current_position > ventilate_position if is_awning else current_position < ventilate_position }}" | |
| # Position ordering (for config validation) | |
| open_above_close: "{{ open_position < close_position if is_awning else open_position > close_position }}" | |
| open_above_ventilate: "{{ open_position < ventilate_position if is_awning else open_position > ventilate_position }}" | |
| shading_above_close: "{{ shading_position < close_position if is_awning else shading_position > close_position }}" | |
| shading_below_open: "{{ shading_position > open_position if is_awning else shading_position < open_position }}" | |
| ventilate_above_close: "{{ ventilate_position < close_position if is_awning else ventilate_position > close_position }}" | |
| current_sun_azimuth: "{{ state_attr(default_sun_sensor, 'azimuth') }}" | |
| current_sun_elevation: "{{ state_attr(default_sun_sensor, 'elevation') }}" | |
| # Current sun elevation thresholds (dynamic or fixed) | |
| sun_elevation_up_current: >- | |
| {% set sensor = sun_elevation_up_sensor %} | |
| {% if sensor != [] and states(sensor) not in invalid_states and is_number(states(sensor)) %} | |
| {{ states(sensor) | float }} | |
| {% else %} | |
| {{ sun_elevation_up | float }} | |
| {% endif %} | |
| sun_elevation_down_current: >- | |
| {% set sensor = sun_elevation_down_sensor %} | |
| {% if sensor != [] and states(sensor) not in invalid_states and is_number(states(sensor)) %} | |
| {{ states(sensor) | float }} | |
| {% else %} | |
| {{ sun_elevation_down | float }} | |
| {% endif %} | |
| is_today_off: "{{ workday_sensor_today != [] and is_state(workday_sensor_today, 'off') }}" | |
| is_tomorrow_off: "{{ workday_sensor_tomorrow != [] and is_state(workday_sensor_tomorrow, 'off') }}" | |
| is_tomorrow_on: "{{ workday_sensor_tomorrow != [] and is_state(workday_sensor_tomorrow, 'on') }}" | |
| is_cover_tilt_enabled_and_possible: "{{ is_cover_tilt_enabled and state_attr(blind, 'current_tilt_position') != none }}" | |
| is_cover_tilt_reposition_enabled: "{{ 'cover_tilt_reposition_enabled' in cover_tilt_reposition_config }}" | |
| # Delays and Timing | |
| drive_delay_fix: !input drive_delay_fix | |
| drive_delay_random: !input drive_delay_random | |
| drive_time: !input drive_time | |
| time_up_early_today: "{{ time_up_early_non_workday if is_today_off else time_up_early }}" | |
| time_up_late_today: "{{ time_up_late_non_workday if is_today_off else time_up_late }}" | |
| time_down_early_today: >- | |
| {{ | |
| time_down_early_non_workday if is_tomorrow_off else | |
| time_down_early if is_tomorrow_on else | |
| time_down_early_non_workday if is_today_off else | |
| time_down_early | |
| }} | |
| time_down_late_today: >- | |
| {{ | |
| time_down_late_non_workday if is_tomorrow_off else | |
| time_down_late if is_tomorrow_on else | |
| time_down_late_non_workday if is_today_off else | |
| time_down_late | |
| }} | |
| # SHADING TILT POSITION - Elevation-based selection | |
| # Determine tilt angle based on current sun elevation (multi-stage) | |
| shading_tilt_position: >- | |
| {% set elevation = current_sun_elevation | int(default=0) %} | |
| {% if elevation >= shading_tilt_elevation_3 | int %} | |
| {{ shading_tilt_position_3 | int }} | |
| {% elif elevation >= shading_tilt_elevation_2 | int %} | |
| {{ shading_tilt_position_2 | int }} | |
| {% elif elevation >= shading_tilt_elevation_1 | int %} | |
| {{ shading_tilt_position_1 | int }} | |
| {% else %} | |
| {{ shading_tilt_position_0 | int }} | |
| {% endif %} | |
| # POSITION CHECKERS - Single source of truth | |
| # Check if current position matches target (with tolerance and tilt validation) | |
| in_open_position: >- | |
| {% set min = open_position - position_tolerance %} | |
| {% set max = open_position + position_tolerance %} | |
| {{ | |
| (current_position >= min and current_position <= max) | |
| and ( | |
| (not is_cover_tilt_enabled_and_possible) or | |
| (is_cover_tilt_enabled_and_possible and current_tilt_position == open_tilt_position | int) | |
| ) | |
| }} | |
| in_close_position: >- | |
| {% set min = close_position - position_tolerance %} | |
| {% set max = close_position + position_tolerance %} | |
| {{ | |
| (current_position >= min and current_position <= max) | |
| and ( | |
| (not is_cover_tilt_enabled_and_possible) or | |
| (is_cover_tilt_enabled_and_possible and current_tilt_position == close_tilt_position | int) | |
| ) | |
| }} | |
| in_shading_position: >- | |
| {% set min = shading_position - position_tolerance %} | |
| {% set max = shading_position + position_tolerance %} | |
| {{ | |
| is_shading_enabled | |
| and (current_position >= min and current_position <= max) | |
| and ( | |
| (not is_cover_tilt_enabled_and_possible) or | |
| (current_tilt_position == shading_tilt_position | int) | |
| ) | |
| }} | |
| in_ventilate_position: >- | |
| {% set min = ventilate_position - position_tolerance %} | |
| {% set max = ventilate_position + position_tolerance %} | |
| {{ | |
| is_ventilation_enabled | |
| and (current_position >= min and current_position <= max) | |
| and ( | |
| (not is_cover_tilt_enabled_and_possible) or | |
| (current_tilt_position == ventilate_tilt_position | int) | |
| ) | |
| }} | |
| # Brightness and sun configuration | |
| brightness_time_duration: !input brightness_time_duration | |
| sun_time_duration: !input sun_time_duration | |
| # SHADING - Additional Configuration Flags | |
| # Shading timing and retry | |
| shading_waitingtime_start: !input shading_waitingtime_start | |
| shading_waitingtime_end: !input shading_waitingtime_end | |
| shading_start_max_duration: !input shading_start_max_duration | |
| shading_end_max_duration: !input shading_end_max_duration | |
| # shading_end_behavior: !input shading_end_behavior | |
| shading_config: !input shading_config | |
| # Additional shading configuration passthrough | |
| shading_end_immediate_by_sun_position: !input shading_end_immediate_by_sun_position | |
| is_shading_end_immediate_by_sun_position: "{{ shading_end_immediate_by_sun_position }}" | |
| # Forecast source determination | |
| # Priority: sensor > weather entity > attributes | |
| forecast_source: | |
| use_sensor: "{{ shading_forecast_temp_sensor != [] }}" | |
| use_weather: "{{ shading_forecast_sensor != [] and shading_forecast_temp_sensor == [] }}" | |
| prevent_service: "{{ 'weather_attributes' in shading_forecast_type }}" | |
| # Shading condition enablement | |
| # Which START conditions are enabled (combines AND + OR lists) | |
| shading_start_condition_enabled: | |
| azimuth: "{{ 'cond_azimuth' in shading_conditions_start_and or 'cond_azimuth' in shading_conditions_start_or }}" | |
| elevation: "{{ 'cond_elevation' in shading_conditions_start_and or 'cond_elevation' in shading_conditions_start_or }}" | |
| brightness: "{{ 'cond_brightness' in shading_conditions_start_and or 'cond_brightness' in shading_conditions_start_or }}" | |
| temp1: "{{ 'cond_temp1' in shading_conditions_start_and or 'cond_temp1' in shading_conditions_start_or }}" | |
| temp2: "{{ 'cond_temp2' in shading_conditions_start_and or 'cond_temp2' in shading_conditions_start_or }}" | |
| forecast_temp: "{{ 'cond_forecast_temp' in shading_conditions_start_and or 'cond_forecast_temp' in shading_conditions_start_or }}" | |
| forecast_weather: "{{ 'cond_forecast_weather' in shading_conditions_start_and or 'cond_forecast_weather' in shading_conditions_start_or }}" | |
| # Which END conditions are enabled (combines AND + OR lists) | |
| shading_end_condition_enabled: | |
| azimuth: "{{ 'cond_azimuth' in shading_conditions_end_and or 'cond_azimuth' in shading_conditions_end_or }}" | |
| elevation: "{{ 'cond_elevation' in shading_conditions_end_and or 'cond_elevation' in shading_conditions_end_or }}" | |
| brightness: "{{ 'cond_brightness' in shading_conditions_end_and or 'cond_brightness' in shading_conditions_end_or }}" | |
| temp1: "{{ 'cond_temp1' in shading_conditions_end_and or 'cond_temp1' in shading_conditions_end_or }}" | |
| temp2: "{{ 'cond_temp2' in shading_conditions_end_and or 'cond_temp2' in shading_conditions_end_or }}" | |
| forecast_temp: "{{ 'cond_forecast_temp' in shading_conditions_end_and or 'cond_forecast_temp' in shading_conditions_end_or }}" | |
| forecast_weather: "{{ 'cond_forecast_weather' in shading_conditions_end_and or 'cond_forecast_weather' in shading_conditions_end_or }}" | |
| # Cover Status Helper – JSON parsing | |
| # Determine if status helper is properly configured and valid | |
| is_status_helper_enabled: >- | |
| {{ | |
| 'cover_helper_enabled' in cover_status_options and | |
| cover_status_helper != [] and | |
| states(cover_status_helper) not in invalid_states and | |
| states(cover_status_helper) | regex_match("((\[[^\}]+)?\{s*[^\}\{]{3,}?:.*\}([^\{]+\])?)") | |
| }} | |
| # Parse JSON helper state or create default structure | |
| helper_json: > | |
| {% if is_status_helper_enabled %} | |
| {{ states(cover_status_helper) | from_json }} | |
| {% else %} | |
| {{ | |
| { | |
| 'open': {'a': 0, 't': 0}, | |
| 'close': {'a': 0, 't': 0}, | |
| 'shading': {'a': 0, 't': 0, 'p': 0, 'q': 0}, | |
| 'vpart': {'a': 0, 't': 0}, | |
| 'vfull': {'a': 0, 't': 0}, | |
| 'manual': {'a': 0, 't': 0}, | |
| 'v': 5, | |
| 't': as_timestamp(now()) | round(0) | |
| } | to_json | from_json | |
| }} | |
| {% endif %} | |
| # Helper status flags (active/inactive states) | |
| helper_status: | |
| open: "{{ (is_status_helper_enabled | default(false)) and (helper_json | regex_search('open')) and (helper_json.open.a | default(false) | bool) }}" | |
| closed: "{{ (is_status_helper_enabled | default(false)) and (helper_json | regex_search('close')) and (helper_json.close.a | default(false) | bool) }}" | |
| shaded: "{{ (is_status_helper_enabled | default(false)) and (helper_json | regex_search('shading')) and (helper_json.shading.a | default(false) | bool) and not (helper_json.shading.p | default(false) | bool) }}" | |
| shading_start_pending: "{{ (is_status_helper_enabled | default(false)) and (helper_json | regex_search('shading')) and (helper_json.shading.p | default(false) | bool) }}" | |
| shading_end_pending: "{{ (is_status_helper_enabled | default(false)) and (helper_json | regex_search('shading')) and (helper_json.shading.q | default(false) | bool) }}" | |
| vent_partial: "{{ (is_status_helper_enabled | default(false)) and (helper_json | regex_search('vpart')) and (helper_json.vpart.a | default(false) | bool) }}" | |
| vent_full: "{{ (is_status_helper_enabled | default(false)) and (helper_json | regex_search('vfull')) and (helper_json.vfull.a | default(false) | bool) }}" | |
| manual: "{{ (is_status_helper_enabled | default(false)) and (helper_json | regex_search('manual')) and (helper_json.manual.a | default(false) | bool) }}" | |
| # Timestamp dictionary - When each status was last set | |
| helper_ts: | |
| open: "{{ (helper_json.open.t | default(0)) if ((is_status_helper_enabled | default(false)) and (helper_json | regex_search('open'))) else 0 }}" | |
| closed: "{{ (helper_json.close.t | default(0)) if ((is_status_helper_enabled | default(false)) and (helper_json | regex_search('close'))) else 0 }}" | |
| shaded: "{{ (helper_json.shading.t | default(0)) if ((is_status_helper_enabled | default(false)) and (helper_json | regex_search('shading'))) else 0 }}" | |
| vent_partial: "{{ (helper_json.vpart.t | default(0)) if ((is_status_helper_enabled | default(false)) and (helper_json | regex_search('vpart'))) else 0 }}" | |
| vent_full: "{{ (helper_json.vfull.t | default(0)) if ((is_status_helper_enabled | default(false)) and (helper_json | regex_search('vfull'))) else 0 }}" | |
| manual: "{{ (helper_json.manual.t | default(0)) if ((is_status_helper_enabled | default(false)) and (helper_json | regex_search('manual'))) else 0 }}" | |
| # Force entity disabled states (true = force is NOT active) | |
| force_disabled: | |
| up: "{{ auto_up_force == [] or (auto_up_force != [] and states(auto_up_force) in ['false', 'off']) }}" | |
| down: "{{ auto_down_force == [] or (auto_down_force != [] and states(auto_down_force) in ['false', 'off']) }}" | |
| ventilate: "{{ auto_ventilate_force == [] or (auto_ventilate_force != [] and states(auto_ventilate_force) in ['false', 'off']) }}" | |
| shading_start: "{{ auto_shading_start_force == [] or (auto_shading_start_force != [] and states(auto_shading_start_force) in ['false', 'off']) }}" | |
| is_cover_movement_blocked: | |
| open: "{{ not force_disabled.down or not force_disabled.ventilate or not force_disabled.shading_start }}" | |
| close: "{{ not force_disabled.up or not force_disabled.ventilate or not force_disabled.shading_start }}" | |
| ventilate: "{{ not force_disabled.up or not force_disabled.down or not force_disabled.shading_start }}" | |
| shading: "{{ not force_disabled.up or not force_disabled.down or not force_disabled.ventilate }}" | |
| any: "{{ not force_disabled.up or not force_disabled.down or not force_disabled.ventilate or not force_disabled.shading_start }}" | |
| # Resident sensor configuration | |
| resident_config: !input resident_config | |
| resident_flags: | |
| opening_enabled: "{{ 'resident_opening_enabled' in resident_config }}" | |
| closing_enabled: "{{ 'resident_closing_enabled' in resident_config }}" | |
| allow_shading: "{{ 'resident_allow_shading' in resident_config }}" | |
| allow_opening: "{{ 'resident_allow_opening' in resident_config }}" | |
| allow_ventilation: "{{ 'resident_allow_ventilation' in resident_config }}" | |
| # Override flags | |
| ignore_after_manual_config: !input ignore_after_manual_config | |
| override_flags: | |
| opening: "{{ 'ignore_opening_after_manual' in ignore_after_manual_config }}" | |
| closing: "{{ 'ignore_closing_after_manual' in ignore_after_manual_config }}" | |
| ventilation: "{{ 'ignore_ventilation_after_manual' in ignore_after_manual_config }}" | |
| shading: "{{ 'ignore_shading_after_manual' in ignore_after_manual_config }}" | |
| # Ventilation configuration | |
| auto_ventilate_options: !input auto_ventilate_options | |
| contact_delay_trigger: !input contact_delay_trigger | |
| contact_delay_status: !input contact_delay_status | |
| ventilation_flags: | |
| delay_enabled: "{{ 'ventilation_delay_enabled' in auto_ventilate_options }}" | |
| if_lower_enabled: "{{ 'ventilation_if_lower_enabled' in auto_ventilate_options }}" | |
| after_shading_end: "{{ 'ventilation_after_shading_end' in auto_ventilate_options }}" | |
| # Individual configuration | |
| prevent_config: !input individual_config | |
| prevent_flags: | |
| higher_position_closing: "{{ 'prevent_higher_position_closing' in prevent_config }}" | |
| lowering_when_closing_if_shaded: "{{ 'prevent_lowering_when_closing_if_shaded' in prevent_config }}" | |
| shading_end_if_closed: "{{ 'prevent_shading_end_if_closed' in prevent_config }}" | |
| opening_after_shading_end: "{{ 'prevent_opening_after_shading_end' in prevent_config }}" | |
| opening_after_ventilation_end: "{{ 'prevent_opening_after_ventilation_end' in prevent_config }}" | |
| default_cover_actions: "{{ 'prevent_default_cover_actions' in prevent_config }}" | |
| shading_multiple_times: "{{ 'prevent_shading_multiple_times' in prevent_config }}" | |
| opening_multiple_times: "{{ 'prevent_opening_multiple_times' in prevent_config }}" | |
| closing_multiple_times: "{{ 'prevent_closing_multiple_times' in prevent_config }}" | |
| enable_background_state_tracking: !input enable_background_state_tracking | |
| # Configuration check | |
| check_config: !input check_config | |
| check_config_debuglevel: !input check_config_debuglevel | |
| check_status_helper_length: "{{ state_attr(cover_status_helper, 'max') if cover_status_helper != [] else None }}" | |
| # CCA uses 'queued' mode to prevent race conditions when multiple sensors | |
| # change state simultaneously (e.g., window sensor + lock sensor). | |
| # - If automation is running, new triggers are queued (max 10) | |
| # - Each trigger is processed in order with current sensor values | |
| # - This ensures no trigger is lost due to timing issues | |
| mode: queued | |
| max: 10 | |
| max_exceeded: warning | |
| ################################################################################ | |
| # TRIGGERS | |
| ################################################################################ | |
| triggers: | |
| ######################################## | |
| # Trigger for opening cover | |
| ######################################## | |
| - platform: template | |
| value_template: >- | |
| {% set is_today_off = workday_sensor_today != [] and is_state(workday_sensor_today, 'off') %} | |
| {% set time_compare = time_up_early_non_workday if is_today_off else time_up_early %} | |
| {{ now() >= today_at(time_compare) }} | |
| enabled: "{{ is_time_field_enabled }}" | |
| id: "t_open_1" | |
| - platform: template # comparing the times to avoid double triggering if early+late are identical | |
| value_template: >- | |
| {% set is_today_off = workday_sensor_today != [] and is_state(workday_sensor_today, 'off') %} | |
| {% set early = time_up_early_non_workday if is_today_off else time_up_early %} | |
| {% set late = time_up_late_non_workday if is_today_off else time_up_late %} | |
| {{ early != late and now() >= today_at(late) }} | |
| enabled: "{{ is_time_field_enabled }}" | |
| id: "t_open_2" | |
| - platform: template | |
| value_template: "{{ states(default_brightness_sensor) | float(default=brightness_up) > (brightness_up + brightness_hysteresis) }}" | |
| for: | |
| seconds: !input brightness_time_duration | |
| enabled: "{{ is_brightness_enabled and default_brightness_sensor != [] }}" | |
| id: "t_open_4" | |
| # Trigger on fixed or dynamic sensor threshold | |
| - platform: template | |
| value_template: >- | |
| {% set sensor = sun_elevation_up_sensor %} | |
| {% set current_elevation = state_attr(default_sun_sensor, 'elevation') | float(0) %} | |
| {% if sensor != [] and states(sensor) not in invalid_states and is_number(states(sensor)) %} | |
| {{ current_elevation > (states(sensor) | float) }} | |
| {% else %} | |
| {{ current_elevation > sun_elevation_up }} | |
| {% endif %} | |
| for: | |
| seconds: !input sun_time_duration | |
| enabled: "{{ is_sun_elevation_enabled and default_sun_sensor != [] }}" | |
| id: "t_open_5" | |
| - platform: state | |
| entity_id: !input resident_sensor | |
| from: "on" | |
| to: "off" | |
| enabled: "{{ resident_sensor != [] }}" | |
| id: "t_open_6" | |
| - platform: state | |
| entity_id: !input auto_up_force | |
| from: "off" | |
| to: "on" | |
| enabled: "{{ auto_up_force != [] }}" | |
| id: "t_force_open_on" | |
| - platform: state | |
| entity_id: !input auto_up_force | |
| from: "on" | |
| to: "off" | |
| enabled: "{{ auto_up_force != [] }}" | |
| id: "t_force_disabled" | |
| ######################################## | |
| # Trigger for closing cover | |
| ######################################## | |
| - platform: template | |
| value_template: >- | |
| {% set is_tomorrow_off = workday_sensor_tomorrow != [] and is_state(workday_sensor_tomorrow, 'off') %} | |
| {% set is_tomorrow_on = workday_sensor_tomorrow != [] and is_state(workday_sensor_tomorrow, 'on') %} | |
| {% set is_today_off = workday_sensor_today != [] and is_state(workday_sensor_today, 'off') %} | |
| {% set time_compare = | |
| time_down_early_non_workday if is_tomorrow_off else | |
| time_down_early if is_tomorrow_on else | |
| time_down_early_non_workday if is_today_off else | |
| time_down_early %} | |
| {{ now() >= today_at(time_compare) }} | |
| enabled: "{{ is_time_field_enabled }}" | |
| id: "t_close_1" | |
| - platform: template # Avoid double triggering with 'early != late' if early and late times are identical | |
| value_template: >- | |
| {% set is_tomorrow_off = workday_sensor_tomorrow != [] and is_state(workday_sensor_tomorrow, 'off') %} | |
| {% set is_tomorrow_on = workday_sensor_tomorrow != [] and is_state(workday_sensor_tomorrow, 'on') %} | |
| {% set is_today_off = workday_sensor_today != [] and is_state(workday_sensor_today, 'off') %} | |
| {% set time_compare = | |
| time_down_late_non_workday if is_tomorrow_off else | |
| time_down_late if is_tomorrow_on else | |
| time_down_late_non_workday if is_today_off else | |
| time_down_late %} | |
| {{ (time_down_early_non_workday != time_compare and time_down_early != time_compare) and now() >= today_at(time_compare) }} | |
| enabled: "{{ is_time_field_enabled }}" | |
| id: "t_close_2" | |
| - platform: template | |
| value_template: "{{ states(default_brightness_sensor) | float(default=brightness_down) < (brightness_down - brightness_hysteresis) }}" | |
| for: | |
| seconds: !input brightness_time_duration | |
| enabled: "{{ is_brightness_enabled and default_brightness_sensor != [] }}" | |
| id: "t_close_4" | |
| - platform: template | |
| value_template: >- | |
| {% set sensor = sun_elevation_down_sensor %} | |
| {% set current_elevation = state_attr(default_sun_sensor, 'elevation') | float(0) %} | |
| {% if sensor != [] and states(sensor) not in invalid_states and is_number(states(sensor)) %} | |
| {{ current_elevation < (states(sensor) | float) }} | |
| {% else %} | |
| {{ current_elevation < sun_elevation_down }} | |
| {% endif %} | |
| for: | |
| seconds: !input sun_time_duration | |
| enabled: "{{ is_sun_elevation_enabled and default_sun_sensor != [] }}" | |
| id: "t_close_5" | |
| - platform: state | |
| entity_id: !input resident_sensor | |
| from: "off" | |
| to: "on" | |
| enabled: "{{ resident_sensor != [] }}" | |
| id: "t_close_6" | |
| - platform: state | |
| entity_id: !input auto_down_force | |
| from: "off" | |
| to: "on" | |
| enabled: "{{ auto_down_force != [] }}" | |
| id: "t_force_close_on" | |
| - platform: state | |
| entity_id: !input auto_down_force | |
| from: "on" | |
| to: "off" | |
| enabled: "{{ auto_down_force != [] }}" | |
| id: "t_force_disabled" | |
| ######################################## | |
| # Calendar-based triggers | |
| ######################################## | |
| # Calendar event starts (state becomes "on") | |
| - platform: state | |
| entity_id: !input calendar_entity | |
| to: "on" | |
| enabled: "{{ is_calendar_enabled }}" | |
| id: "t_calendar_event_start" | |
| # Calendar event ends (state becomes "off") | |
| - platform: state | |
| entity_id: !input calendar_entity | |
| to: "off" | |
| enabled: "{{ is_calendar_enabled }}" | |
| id: "t_calendar_event_end" | |
| ################################################# | |
| # Trigger for ventilation - COMBINED | |
| # Single trigger per sensor to prevent race conditions | |
| ################################################# | |
| - platform: state | |
| entity_id: !input contact_window_tilted | |
| enabled: "{{ is_ventilation_enabled and contact_window_tilted != [] }}" | |
| id: "t_contact_tilted_changed" | |
| for: | |
| seconds: !input contact_delay_trigger | |
| - platform: state | |
| entity_id: !input contact_window_opened | |
| enabled: "{{ is_ventilation_enabled and contact_window_opened != [] }}" | |
| id: "t_contact_opened_changed" | |
| for: | |
| seconds: !input contact_delay_trigger | |
| - platform: state | |
| entity_id: !input auto_ventilate_force | |
| from: "off" | |
| to: "on" | |
| enabled: "{{ auto_ventilate_force != [] }}" | |
| id: "t_force_vent_on" | |
| - platform: state | |
| entity_id: !input auto_ventilate_force | |
| from: "on" | |
| to: "off" | |
| enabled: "{{ auto_ventilate_force != [] }}" | |
| id: "t_force_disabled" | |
| ######################################## | |
| # Triggers for shading start | |
| ######################################## | |
| - platform: template | |
| value_template: >- | |
| {{ | |
| state_attr(default_sun_sensor, 'azimuth') | float(default=shading_azimuth_start) > shading_azimuth_start and | |
| state_attr(default_sun_sensor, 'azimuth') | float(default=shading_azimuth_end) < shading_azimuth_end and | |
| state_attr(default_sun_sensor, 'elevation') | float(default=shading_elevation_min) > shading_elevation_min and | |
| state_attr(default_sun_sensor, 'elevation') | float(default=shading_elevation_max) < shading_elevation_max | |
| }} | |
| enabled: "{{ is_shading_enabled and default_sun_sensor != [] }}" | |
| id: "t_shading_start_pending_1" | |
| - platform: template | |
| value_template: "{{ states(shading_brightness_sensor) not in invalid_states and states(shading_brightness_sensor) | float(default=shading_sun_brightness_start) > (shading_sun_brightness_start + shading_sun_brightness_hysteresis) }}" | |
| enabled: "{{ is_shading_enabled and shading_brightness_sensor != [] }}" | |
| id: "t_shading_start_pending_2" | |
| - platform: template | |
| value_template: "{{ states(shading_temperatur_sensor1) not in invalid_states and states(shading_temperatur_sensor1) | float(default=shading_min_temperatur1) > (shading_min_temperatur1 + shading_temperature_hysteresis1) }}" | |
| enabled: "{{ is_shading_enabled and shading_temperatur_sensor1 != [] }}" | |
| id: "t_shading_start_pending_3" | |
| - platform: template | |
| value_template: "{{ states(shading_temperatur_sensor2) not in invalid_states and states(shading_temperatur_sensor2) | float(default=shading_min_temperatur2) > (shading_min_temperatur2 + shading_temperature_hysteresis2) }}" | |
| enabled: "{{ is_shading_enabled and shading_temperatur_sensor2 != [] }}" | |
| id: "t_shading_start_pending_4" | |
| - platform: template | |
| value_template: "{{ states(shading_forecast_sensor) not in invalid_states and states(shading_forecast_sensor) in shading_weather_conditions }}" | |
| enabled: "{{ is_shading_enabled and shading_forecast_sensor != [] }}" | |
| id: "t_shading_start_pending_5" | |
| - platform: template # Required to recognize the maximum temperature value of the weather forecast before opening. | |
| value_template: "{{ now() >= today_at([time_up_early, time_up_early_non_workday] | min) - timedelta(hours = 1) }}" | |
| enabled: "{{ is_shading_enabled }}" | |
| id: "t_shading_start_pending_6" | |
| - platform: template | |
| value_template: >- | |
| {% set helper_state = states(cover_status_helper) %} | |
| {{ | |
| helper_state not in invalid_states and | |
| helper_state | regex_match("((\[[^\}]+)?\{s*[^\}\{]{3,}?:.*\}([^\{]+\])?)") and | |
| helper_state | from_json | regex_search('shading') and | |
| (helper_state | from_json).shading.p is defined and | |
| (helper_state | from_json).shading.p > 0 and | |
| now() >= ((helper_state | from_json).shading.p) | as_datetime | as_local | |
| }} | |
| enabled: "{{ is_shading_enabled and cover_status_helper != [] }}" | |
| id: "t_shading_start_execution" | |
| - platform: state | |
| entity_id: !input auto_shading_start_force | |
| from: "off" | |
| to: "on" | |
| enabled: "{{ auto_shading_start_force != [] }}" | |
| id: "t_force_shading_start_on" | |
| - platform: state | |
| entity_id: !input auto_shading_start_force | |
| from: "on" | |
| to: "off" | |
| enabled: "{{ auto_shading_start_force != [] }}" | |
| id: "t_force_disabled" | |
| ######################################## | |
| # Triggers for shading tilt | |
| ######################################## | |
| - platform: template | |
| value_template: >- | |
| {{ | |
| states(cover_status_helper) not in invalid_states and | |
| states(cover_status_helper) | regex_search('"shading"\s*:\s*\{[^}]*"a"\s*:\s*1') and | |
| state_attr(default_sun_sensor, 'elevation') < shading_tilt_elevation_1 | |
| }} | |
| enabled: "{{ is_shading_enabled and cover_status_helper != [] and is_cover_tilt_enabled and default_sun_sensor != [] }}" | |
| id: t_shading_tilt_1 | |
| - platform: template | |
| value_template: >- | |
| {{ | |
| states(cover_status_helper) not in invalid_states and | |
| states(cover_status_helper) | regex_search('"shading"\s*:\s*\{[^}]*"a"\s*:\s*1') and | |
| state_attr(default_sun_sensor, 'elevation') < shading_tilt_elevation_2 and | |
| state_attr(default_sun_sensor, 'elevation') >= shading_tilt_elevation_1 | |
| }} | |
| enabled: "{{ is_shading_enabled and cover_status_helper != [] and is_cover_tilt_enabled and default_sun_sensor != [] }}" | |
| id: t_shading_tilt_2 | |
| - platform: template | |
| value_template: >- | |
| {{ | |
| states(cover_status_helper) not in invalid_states and | |
| states(cover_status_helper) | regex_search('"shading"\s*:\s*\{[^}]*"a"\s*:\s*1') and | |
| state_attr(default_sun_sensor, 'elevation') < shading_tilt_elevation_3 and | |
| state_attr(default_sun_sensor, 'elevation') >= shading_tilt_elevation_2 | |
| }} | |
| enabled: "{{ is_shading_enabled and cover_status_helper != [] and is_cover_tilt_enabled and default_sun_sensor != [] }}" | |
| id: t_shading_tilt_3 | |
| - platform: template | |
| value_template: >- | |
| {{ | |
| states(cover_status_helper) not in invalid_states and | |
| states(cover_status_helper) | regex_search('"shading"\s*:\s*\{[^}]*"a"\s*:\s*1') and | |
| state_attr(default_sun_sensor, 'elevation') >= shading_tilt_elevation_3 | |
| }} | |
| enabled: "{{ is_shading_enabled and cover_status_helper != [] and is_cover_tilt_enabled and default_sun_sensor != [] }}" | |
| id: t_shading_tilt_4 | |
| ######################################## | |
| # Triggers for shading end | |
| ######################################## | |
| - platform: template | |
| value_template: "{{ states(shading_temperatur_sensor1) not in invalid_states and states(shading_temperatur_sensor1) | float(default=shading_min_temperatur1) < (shading_min_temperatur1 - shading_temperature_hysteresis1) }}" | |
| enabled: "{{ is_shading_enabled and cover_status_helper != [] and shading_temperatur_sensor1 != [] }}" | |
| id: "t_shading_end_pending_1" | |
| - platform: template | |
| value_template: "{{ states(shading_temperatur_sensor2) not in invalid_states and states(shading_temperatur_sensor2) | float(default=shading_min_temperatur2) < (shading_min_temperatur2 - shading_temperature_hysteresis2) }}" | |
| enabled: "{{ is_shading_enabled and cover_status_helper != [] and shading_temperatur_sensor2 != [] }}" | |
| id: "t_shading_end_pending_2" | |
| - platform: template | |
| value_template: "{{ states(shading_brightness_sensor) not in invalid_states and states(shading_brightness_sensor) | float(default=shading_sun_brightness_end) < (shading_sun_brightness_end - shading_sun_brightness_hysteresis) }}" | |
| enabled: "{{ is_shading_enabled and cover_status_helper != [] and shading_brightness_sensor != [] }}" | |
| id: "t_shading_end_pending_3" | |
| - platform: template | |
| value_template: "{{ states(shading_forecast_sensor) not in shading_weather_conditions }}" | |
| enabled: "{{ is_shading_enabled and cover_status_helper != [] and shading_weather_conditions != [] and shading_forecast_sensor != [] }}" | |
| id: "t_shading_end_pending_4" | |
| - platform: template | |
| value_template: >- | |
| {{ | |
| (state_attr(default_sun_sensor, 'azimuth') | float(default=shading_azimuth_end) > shading_azimuth_end) or | |
| (state_attr(default_sun_sensor, 'elevation') | float(default=shading_elevation_max) > shading_elevation_max) or | |
| (state_attr(default_sun_sensor, 'elevation') | float(default=shading_elevation_min) < shading_elevation_min) | |
| }} | |
| enabled: "{{ is_shading_enabled and cover_status_helper != [] and default_sun_sensor != [] }}" | |
| id: "t_shading_end_pending_5" | |
| - platform: template | |
| value_template: >- | |
| {% set helper_state = states(cover_status_helper) %} | |
| {{ | |
| helper_state not in invalid_states and | |
| helper_state | regex_match("((\[[^\}]+)?\{s*[^\}\{]{3,}?:.*\}([^\{]+\])?)") and | |
| helper_state | from_json | regex_search('shading') and | |
| (helper_state | from_json).shading.q is defined and | |
| (helper_state | from_json).shading.q > 0 and | |
| now() >= ((helper_state | from_json).shading.q) | as_datetime | as_local | |
| }} | |
| enabled: "{{ is_shading_enabled and cover_status_helper != [] }}" | |
| id: "t_shading_end_execution" | |
| - platform: template | |
| value_template: "{{ now() >= today_at('23:55:00') }}" | |
| id: "t_shading_reset" | |
| enabled: "{{ is_shading_enabled }}" | |
| ######################################## | |
| # Trigger for manual cover controls | |
| ######################################## | |
| - platform: state | |
| entity_id: !input blind | |
| attribute: current_position | |
| id: "t_manual_1" | |
| for: "00:01:00" | |
| - platform: template | |
| value_template: "{{ now() >= today_at(reset_override_time) }}" | |
| enabled: "{{ is_reset_fixed_time }}" | |
| id: "t_manual_2" | |
| for: "00:00:02" | |
| - platform: template | |
| value_template: >- | |
| {% set helper_state = states(cover_status_helper) %} | |
| {{ | |
| helper_state not in invalid_states and | |
| helper_state | regex_match("((\[[^\}]+)?\{s*[^\}\{]{3,}?:.*\}([^\{]+\])?)") and | |
| helper_state | from_json | regex_search('manual') and | |
| (helper_state | from_json).manual.t is defined and | |
| (helper_state | from_json).manual.a is defined and | |
| (helper_state | from_json).manual.a | bool is true and | |
| now() >= ((helper_state | from_json).manual.t + 60 * reset_override_timeout) | as_datetime | as_local | |
| }} | |
| enabled: "{{ is_reset_timeout and cover_status_helper != [] }}" | |
| id: "t_manual_3" | |
| for: "00:00:02" | |
| - platform: state | |
| entity_id: !input blind | |
| attribute: current_tilt_position | |
| id: "t_manual_4" | |
| for: "00:01:00" | |
| ################################################################################ | |
| # GLOBAL CONDITIONS | |
| ################################################################################ | |
| conditions: | |
| - condition: !input auto_global_condition | |
| - or: | |
| - "{{ trigger.to_state is not defined }}" # for "platform: time"-Trigger | |
| - "{{ trigger.to_state is defined and trigger.to_state.state not in invalid_states }}" # The following values are not valid to trigger | |
| - or: | |
| - "{{ trigger.id is not defined }}" | |
| - "{{ not trigger.id | regex_match('^t_calendar_event') }}" | |
| - and: | |
| - "{{ trigger.id | regex_match('^t_calendar_event') }}" | |
| - "{{ state_attr(calendar_entity, 'message') is not none }}" | |
| - >- | |
| {{ | |
| calendar_open_title_lc in (state_attr(calendar_entity, 'message') | lower) or | |
| calendar_close_title_lc in (state_attr(calendar_entity, 'message') | lower) | |
| }} | |
| - condition: template | |
| value_template: >- | |
| {% if is_status_helper_enabled %} | |
| {% if trigger.id is match('^t_shading_start_pending_[1-5]$') %} | |
| {{ states(cover_status_helper) | regex_search('"shading"\s*:\s*\{[^}]*"a"\s*:\s*0') }} | |
| {% elif trigger.id is match('^t_shading_end_pending_[1-5]$') %} | |
| {{ states(cover_status_helper) | regex_search('"shading"\s*:\s*\{[^}]*"a"\s*:\s*1') }} | |
| {% else %} | |
| true | |
| {% endif %} | |
| {% else %} | |
| true | |
| {% endif %} | |
| ################################################################################ | |
| # ACTIONS | |
| ################################################################################ | |
| actions: | |
| # YAML Anchors ############################################################### | |
| - variables: | |
| cover_move_action: &cover_move_action | |
| sequence: | |
| - repeat: | |
| for_each: "{{ blind_entities | list }}" | |
| sequence: | |
| - choose: | |
| - conditions: | |
| - "{{ not prevent_flags.default_cover_actions }}" | |
| - "{{ target_position | default(101) == 0 }}" | |
| sequence: | |
| - alias: "Close Cover" | |
| service: cover.close_cover | |
| target: | |
| entity_id: "{{ repeat.item if repeat is defined else '' }}" | |
| - conditions: | |
| - "{{ not prevent_flags.default_cover_actions }}" | |
| - "{{ target_position | default(101) == 100 }}" | |
| sequence: | |
| - alias: "Open Cover" | |
| service: cover.open_cover | |
| target: | |
| entity_id: "{{ repeat.item if repeat is defined else '' }}" | |
| - conditions: | |
| - "{{ not prevent_flags.default_cover_actions }}" | |
| - "{{ target_position | default(101) not in [0, 100] }}" | |
| sequence: | |
| - alias: "Moving the cover to target position" | |
| service: cover.set_cover_position | |
| data: | |
| position: "{{ target_position | default(101) }}" | |
| target: | |
| entity_id: "{{ repeat.item if repeat is defined else '' }}" | |
| - delay: | |
| seconds: "{{ (range(1, 3) | random | int) }}" | |
| tilt_move_action: &tilt_move_action | |
| sequence: | |
| - if: | |
| - "{{ not prevent_flags.default_cover_actions }}" | |
| - "{{ is_cover_tilt_enabled_and_possible }}" | |
| - "{{ expand(blind_entities) | selectattr('attributes.current_tilt_position', 'defined') | list | count > 0 }}" | |
| then: | |
| - repeat: | |
| for_each: "{{ blind_entities | list }}" | |
| sequence: | |
| - if: | |
| - "{{ tilt_first | default(false) }}" | |
| then: | |
| - alias: "Reset Tilt" | |
| service: cover.set_cover_tilt_position | |
| data: | |
| tilt_position: 0 | |
| target: | |
| entity_id: "{{ repeat.item if repeat is defined else '' }}" | |
| - delay: | |
| seconds: !input tilt_delay | |
| - choose: # Wait strategy based on mode | |
| - conditions: # Mode: Wait until idle | |
| - "{{ is_tilt_wait_idle_mode | default(false) }}" | |
| sequence: | |
| - alias: "Wait for cover to become idle" | |
| wait_template: >- | |
| {% set cover_state = states(repeat.item if repeat is defined else '') %} | |
| {{ cover_state in ['open', 'closed'] }} | |
| timeout: "{{ tilt_wait_timeout | int }}" | |
| continue_on_timeout: true | |
| - if: | |
| - "{{ wait.completed if wait is defined else ''}}" | |
| then: | |
| - alias: "Cover reached idle state - apply safety delay" | |
| delay: | |
| seconds: 1 | |
| default: # Mode: Fixed delay (default/backward compatible) | |
| - alias: "Standard tilt delay" | |
| delay: | |
| seconds: !input tilt_delay | |
| - alias: "Moving the cover to tilt position" | |
| service: cover.set_cover_tilt_position | |
| data: | |
| tilt_position: "{{ target_tilt_position | default(101) }}" | |
| target: | |
| entity_id: "{{ repeat.item if repeat is defined else '' }}" | |
| - delay: | |
| seconds: "{{ (range(1, 3) | random | int) }}" | |
| helper_update: &helper_update | |
| if: | |
| - "{{ is_status_helper_enabled }}" | |
| - or: | |
| - "{{ enable_background_state_tracking }}" # Enable background updates for return-to-target | |
| - and: # Or check that no force function is currently blocking | |
| - "{{ force_disabled.up }}" | |
| - "{{ force_disabled.down }}" | |
| - "{{ force_disabled.ventilate }}" | |
| - "{{ force_disabled.shading_start }}" | |
| then: | |
| - alias: "Update cover status helper" | |
| service: input_text.set_value | |
| data: | |
| entity_id: !input cover_status_helper | |
| value: |- | |
| {% set dict_var = helper_json %} | |
| {% set dict_new = dict(dict_var, **(update_values | default({})) ) %} | |
| {{ dict_new | to_json }} | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| # Load weather forecast data from service (only when needed) | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| - if: | |
| - "{{ is_shading_enabled }}" | |
| - "{{ forecast_source.use_weather and not forecast_source.prevent_service }}" | |
| - "{{ states(shading_forecast_sensor) not in invalid_states }}" | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id | regex_match('^(t_shading_start|t_open_1|t_open_3)') }}" # Important: All triggers for shading start necessary! | |
| then: | |
| - action: weather.get_forecasts | |
| target: | |
| entity_id: !input shading_forecast_sensor | |
| data: | |
| type: !input shading_forecast_type | |
| response_variable: weather_forecast | |
| continue_on_error: true | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| # Load calendar events for today (00:00 to 23:59) | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| - if: | |
| # Only load when calendar is enabled and entity is valid | |
| - "{{ is_calendar_enabled }}" | |
| - "{{ calendar_entity != [] }}" | |
| - "{{ states(calendar_entity) not in invalid_states }}" | |
| - "{{ trigger.id is defined }}" | |
| # Performance: Only load on relevant triggers | |
| - or: | |
| - "{{ trigger.id | regex_match('^t_open') }}" | |
| - "{{ trigger.id | regex_match('^t_close') }}" | |
| - "{{ trigger.id | regex_match('^t_shading_start') }}" | |
| - "{{ trigger.id | regex_match('^t_calendar_event') }}" | |
| - "{{ trigger.id == 't_force_disabled' }}" | |
| then: | |
| - action: calendar.get_events | |
| target: | |
| entity_id: !input calendar_entity | |
| data: | |
| # Get all events for today (00:00:00 to 23:59:59) | |
| start_date_time: "{{ today_at('00:00:00').isoformat() }}" | |
| end_date_time: "{{ (today_at('23:59:59') + timedelta(seconds=1)).isoformat() }}" | |
| response_variable: calendar_events | |
| continue_on_error: true # Don't fail if calendar is temporarily unavailable | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| # Recalculate ALL forecast-dependent variables (even if forecast wasn't loaded) | |
| # This ensures variables are always defined and prevents crashes | |
| # ════════════════════════════════════════════════════════════════════════════ | |
| - variables: | |
| # Forecast data normalisation. | |
| # Temperature from sensor (priority) or weather entity | |
| forecast_temp_raw: >- | |
| {% set sensor = shading_forecast_temp_sensor %} | |
| {% set weather = shading_forecast_sensor %} | |
| {% if sensor != [] and states(sensor) not in invalid_states %} | |
| {{ states(sensor) | float(default=None) }} | |
| {% elif weather == [] or states(weather) in invalid_states %} | |
| {{ None }} | |
| {% elif 'weather_attributes' in shading_forecast_type %} | |
| {{ state_attr(weather, 'temperature') | float(default=None) }} | |
| {% elif weather_forecast is defined and weather in weather_forecast %} | |
| {{ weather_forecast[weather].forecast[0].temperature | default(None) | float(default=None) }} | |
| {% else %} | |
| {{ None }} | |
| {% endif %} | |
| # Weather condition (only from weather entity) | |
| forecast_weather_condition_raw: >- | |
| {% set weather = shading_forecast_sensor %} | |
| {% if weather == [] or states(weather) in invalid_states %} | |
| {{ None }} | |
| {% elif 'weather_attributes' in shading_forecast_type %} | |
| {{ state_attr(weather, 'condition') }} | |
| {% elif weather_forecast is defined and weather in weather_forecast %} | |
| {{ weather_forecast[weather].forecast[0].condition | default(None) }} | |
| {% else %} | |
| {{ None }} | |
| {% endif %} | |
| # SHADING START - Individual Condition Evaluations | |
| # All START conditions evaluated and stored in one dictionary | |
| shading_start_condition_states: | |
| azimuth_valid: >- | |
| {{ | |
| not is_shading_enabled or | |
| not shading_start_condition_enabled.azimuth or | |
| default_sun_sensor == [] or | |
| ( | |
| current_sun_azimuth | float(default=0) > shading_azimuth_start and | |
| current_sun_azimuth | float(default=0) < shading_azimuth_end | |
| ) | |
| }} | |
| elevation_valid: >- | |
| {{ | |
| not is_shading_enabled or | |
| not shading_start_condition_enabled.elevation or | |
| default_sun_sensor == [] or | |
| ( | |
| current_sun_elevation | float(default=0) > shading_elevation_min and | |
| current_sun_elevation | float(default=0) < shading_elevation_max | |
| ) | |
| }} | |
| brightness_valid: >- | |
| {{ | |
| not is_shading_enabled or | |
| not shading_start_condition_enabled.brightness or | |
| shading_brightness_sensor == [] or | |
| ( | |
| states(shading_brightness_sensor) not in invalid_states and | |
| states(shading_brightness_sensor) | float(default=shading_sun_brightness_start) > (shading_sun_brightness_start + shading_sun_brightness_hysteresis) | |
| ) | |
| }} | |
| temp1_valid: >- | |
| {{ | |
| not is_shading_enabled or | |
| not shading_start_condition_enabled.temp1 or | |
| shading_temperatur_sensor1 == [] or | |
| ( | |
| states(shading_temperatur_sensor1) not in invalid_states and | |
| states(shading_temperatur_sensor1) | float(default=shading_min_temperatur1) > (shading_min_temperatur1 + shading_temperature_hysteresis1) | |
| ) | |
| }} | |
| temp2_valid: >- | |
| {{ | |
| not is_shading_enabled or | |
| not shading_start_condition_enabled.temp2 or | |
| shading_temperatur_sensor2 == [] or | |
| ( | |
| states(shading_temperatur_sensor2) not in invalid_states and | |
| states(shading_temperatur_sensor2) | float(default=shading_min_temperatur2) > (shading_min_temperatur2 + shading_temperature_hysteresis2) | |
| ) | |
| }} | |
| forecast_temp_valid: >- | |
| {{ | |
| not is_shading_enabled or | |
| not shading_start_condition_enabled.forecast_temp or | |
| shading_forecast_temp == [] or | |
| ( | |
| forecast_temp_raw is not none and | |
| forecast_temp_raw > (shading_forecast_temp + shading_forecast_temp_hysteresis) | |
| ) or | |
| ( | |
| 'shading_compare_forecast_with_sensor2' in shading_config and | |
| shading_temperatur_sensor2 != [] and | |
| shading_forecast_temp != [] and | |
| (states(shading_temperatur_sensor2) | float(default=0) > (shading_forecast_temp + shading_forecast_temp_hysteresis)) | |
| ) | |
| }} | |
| forecast_weather_valid: >- | |
| {{ | |
| not is_shading_enabled or | |
| not shading_start_condition_enabled.forecast_weather or | |
| shading_weather_conditions == [] or | |
| forecast_source.use_sensor or | |
| ( | |
| forecast_weather_condition_raw is not none and | |
| forecast_weather_condition_raw not in invalid_states and | |
| forecast_weather_condition_raw in shading_weather_conditions | |
| ) | |
| }} | |
| # SHADING START - Combined AND/OR Logic | |
| # START AND: All required conditions must be true | |
| shading_start_and_result: >- | |
| {{ | |
| ( | |
| ('cond_azimuth' not in shading_conditions_start_and or | |
| default_sun_sensor == [] or | |
| shading_start_condition_states.azimuth_valid) | |
| and | |
| ('cond_elevation' not in shading_conditions_start_and or | |
| default_sun_sensor == [] or | |
| shading_start_condition_states.elevation_valid) | |
| and | |
| ('cond_brightness' not in shading_conditions_start_and or | |
| shading_brightness_sensor == [] or | |
| shading_start_condition_states.brightness_valid) | |
| and | |
| ('cond_temp1' not in shading_conditions_start_and or | |
| shading_temperatur_sensor1 == [] or | |
| shading_start_condition_states.temp1_valid) | |
| and | |
| ('cond_temp2' not in shading_conditions_start_and or | |
| shading_temperatur_sensor2 == [] or | |
| shading_start_condition_states.temp2_valid) | |
| and | |
| ('cond_forecast_temp' not in shading_conditions_start_and or | |
| shading_forecast_temp == [] or | |
| shading_start_condition_states.forecast_temp_valid) | |
| and | |
| ('cond_forecast_weather' not in shading_conditions_start_and or | |
| shading_weather_conditions == [] or | |
| shading_start_condition_states.forecast_weather_valid) | |
| ) | |
| }} | |
| # START OR: At least one optional condition must be true (or none required) | |
| shading_start_or_result: >- | |
| {{ | |
| (shading_conditions_start_or | count == 0) or | |
| ( | |
| ('cond_azimuth' in shading_conditions_start_or and | |
| default_sun_sensor != [] and | |
| shading_start_condition_states.azimuth_valid) | |
| or | |
| ('cond_elevation' in shading_conditions_start_or and | |
| default_sun_sensor != [] and | |
| shading_start_condition_states.elevation_valid) | |
| or | |
| ('cond_brightness' in shading_conditions_start_or and | |
| shading_brightness_sensor != [] and | |
| shading_start_condition_states.brightness_valid) | |
| or | |
| ('cond_temp1' in shading_conditions_start_or and | |
| shading_temperatur_sensor1 != [] and | |
| shading_start_condition_states.temp1_valid) | |
| or | |
| ('cond_temp2' in shading_conditions_start_or and | |
| shading_temperatur_sensor2 != [] and | |
| shading_start_condition_states.temp2_valid) | |
| or | |
| ('cond_forecast_temp' in shading_conditions_start_or and | |
| shading_forecast_temp != [] and | |
| shading_start_condition_states.forecast_temp_valid) | |
| or | |
| ('cond_forecast_weather' in shading_conditions_start_or and | |
| shading_weather_conditions != [] and | |
| shading_start_condition_states.forecast_weather_valid) | |
| ) | |
| }} | |
| # Final START condition: All AND conditions + at least one OR condition | |
| shading_start_conditions_met: >- | |
| {{ | |
| is_shading_enabled and | |
| shading_start_and_result and | |
| shading_start_or_result | |
| }} | |
| # SHADING END - Individual Condition Evaluations | |
| # All END conditions evaluated with inverse logic and stored in one dictionary | |
| shading_end_condition_states: | |
| azimuth_invalid: >- | |
| {{ | |
| is_shading_enabled and | |
| shading_end_condition_enabled.azimuth and | |
| default_sun_sensor != [] and | |
| ( | |
| current_sun_azimuth | float(default=999) <= shading_azimuth_start or | |
| current_sun_azimuth | float(default=0) >= shading_azimuth_end | |
| ) | |
| }} | |
| elevation_invalid: >- | |
| {{ | |
| is_shading_enabled and | |
| shading_end_condition_enabled.elevation and | |
| default_sun_sensor != [] and | |
| ( | |
| current_sun_elevation | float(default=999) <= shading_elevation_min or | |
| current_sun_elevation | float(default=0) >= shading_elevation_max | |
| ) | |
| }} | |
| brightness_invalid: >- | |
| {{ | |
| is_shading_enabled and | |
| shading_end_condition_enabled.brightness and | |
| shading_brightness_sensor != [] and | |
| states(shading_brightness_sensor) not in invalid_states and | |
| states(shading_brightness_sensor) | float(default=999999) < (shading_sun_brightness_end - shading_sun_brightness_hysteresis) | |
| }} | |
| temp1_invalid: >- | |
| {{ | |
| is_shading_enabled and | |
| shading_end_condition_enabled.temp1 and | |
| shading_temperatur_sensor1 != [] and | |
| states(shading_temperatur_sensor1) not in invalid_states and | |
| states(shading_temperatur_sensor1) | float(default=999) < (shading_min_temperatur1 - shading_temperature_hysteresis1) | |
| }} | |
| temp2_invalid: >- | |
| {{ | |
| is_shading_enabled and | |
| shading_end_condition_enabled.temp2 and | |
| shading_temperatur_sensor2 != [] and | |
| states(shading_temperatur_sensor2) not in invalid_states and | |
| states(shading_temperatur_sensor2) | float(default=999) < (shading_min_temperatur2 - shading_temperature_hysteresis2) | |
| }} | |
| forecast_temp_invalid: >- | |
| {{ | |
| is_shading_enabled and | |
| shading_end_condition_enabled.forecast_temp and | |
| shading_forecast_temp != [] and | |
| forecast_temp_raw is not none and | |
| forecast_temp_raw < (shading_forecast_temp - shading_forecast_temp_hysteresis) | |
| }} | |
| forecast_weather_invalid: >- | |
| {{ | |
| is_shading_enabled and | |
| shading_end_condition_enabled.forecast_weather and | |
| shading_weather_conditions != [] and | |
| ( | |
| forecast_weather_condition_raw is none or | |
| forecast_weather_condition_raw in invalid_states or | |
| forecast_weather_condition_raw not in shading_weather_conditions | |
| ) | |
| }} | |
| # SHADING END - Combined AND/OR Logic | |
| # END AND: All specified conditions must become invalid | |
| shading_end_and_result: >- | |
| {{ | |
| shading_conditions_end_and | count > 0 and | |
| ( | |
| ('cond_azimuth' not in shading_conditions_end_and or shading_end_condition_states.azimuth_invalid) | |
| and | |
| ('cond_elevation' not in shading_conditions_end_and or shading_end_condition_states.elevation_invalid) | |
| and | |
| ('cond_brightness' not in shading_conditions_end_and or shading_end_condition_states.brightness_invalid) | |
| and | |
| ('cond_temp1' not in shading_conditions_end_and or shading_end_condition_states.temp1_invalid) | |
| and | |
| ('cond_temp2' not in shading_conditions_end_and or shading_end_condition_states.temp2_invalid) | |
| and | |
| ('cond_forecast_temp' not in shading_conditions_end_and or shading_end_condition_states.forecast_temp_invalid) | |
| and | |
| ('cond_forecast_weather' not in shading_conditions_end_and or shading_end_condition_states.forecast_weather_invalid) | |
| ) | |
| }} | |
| # END OR: At least one specified condition must become invalid | |
| shading_end_or_result: >- | |
| {{ | |
| shading_conditions_end_or | count > 0 and | |
| ( | |
| shading_end_condition_states.azimuth_invalid or | |
| shading_end_condition_states.elevation_invalid or | |
| shading_end_condition_states.brightness_invalid or | |
| shading_end_condition_states.temp1_invalid or | |
| shading_end_condition_states.temp2_invalid or | |
| shading_end_condition_states.forecast_temp_invalid or | |
| shading_end_condition_states.forecast_weather_invalid | |
| ) | |
| }} | |
| # Final END condition: AND result OR OR result | |
| shading_end_conditions_met: >- | |
| {{ | |
| is_shading_enabled and | |
| ( | |
| shading_end_and_result or | |
| shading_end_or_result | |
| ) | |
| }} | |
| # ╔════════════════════════════════════════════════════════════════════╗ | |
| # ║ CALENDAR EVENT PARSING ║ | |
| # ║ Parse today's calendar events to extract time windows ║ | |
| # ╚════════════════════════════════════════════════════════════════════╝ | |
| # Parse "Open Cover" event from today's calendar events | |
| # Returns dictionary with: found (bool), start (datetime), end (datetime) | |
| calendar_open_event: >- | |
| {% set result = namespace(found=false, start=none, end=none) %} | |
| {% if is_calendar_enabled and calendar_events is defined and calendar_entity in calendar_events %} | |
| {% set events = calendar_events[calendar_entity].events | default([]) %} | |
| {% for event in events %} | |
| {% set summary = (event.summary | default('')) | lower | trim %} | |
| {% if calendar_open_title_lc != '' and calendar_open_title_lc in summary %} | |
| {% set result.found = true %} | |
| {% set result.start = event.start %} | |
| {% set result.end = event.end %} | |
| {% endif %} | |
| {% endfor %} | |
| {% endif %} | |
| {{ { | |
| 'found': result.found, | |
| 'start': result.start, | |
| 'end': result.end | |
| } }} | |
| # Parse "Close Cover" event from today's calendar events | |
| calendar_close_event: >- | |
| {% set result = namespace(found=false, start=none, end=none) %} | |
| {% if is_calendar_enabled and calendar_events is defined and calendar_entity in calendar_events %} | |
| {% set events = calendar_events[calendar_entity].events | default([]) %} | |
| {% for event in events %} | |
| {% set summary = (event.summary | default('')) | lower | trim %} | |
| {% if calendar_close_title_lc != '' and calendar_close_title_lc in summary %} | |
| {% set result.found = true %} | |
| {% set result.start = event.start %} | |
| {% set result.end = event.end %} | |
| {% endif %} | |
| {% endfor %} | |
| {% endif %} | |
| {{ { | |
| 'found': result.found, | |
| 'start': result.start, | |
| 'end': result.end | |
| } }} | |
| # ╔════════════════════════════════════════════════════════════════════╗ | |
| # ║ CALENDAR TIMESTAMPS ║ | |
| # ║ Convert event times to timestamps for numeric comparisons ║ | |
| # ╚════════════════════════════════════════════════════════════════════╝ | |
| # Current time as timestamp (for calendar time comparisons) | |
| now_ts: "{{ as_timestamp(now()) }}" | |
| # Open event start time (timestamp) - equivalent to time_up_early | |
| calendar_open_start: >- | |
| {% if calendar_open_event.found and calendar_open_event.start is not none %} | |
| {{ as_timestamp(calendar_open_event.start) }} | |
| {% else %} | |
| {{ none }} | |
| {% endif %} | |
| # Open event end time (timestamp) - equivalent to time_up_late | |
| calendar_open_end: >- | |
| {% if calendar_open_event.found and calendar_open_event.end is not none %} | |
| {{ as_timestamp(calendar_open_event.end) }} | |
| {% else %} | |
| {{ none }} | |
| {% endif %} | |
| # Close event start time (timestamp) - equivalent to time_down_early | |
| calendar_close_start: >- | |
| {% if calendar_close_event.found and calendar_close_event.start is not none %} | |
| {{ as_timestamp(calendar_close_event.start) }} | |
| {% else %} | |
| {{ none }} | |
| {% endif %} | |
| # Close event end time (timestamp) - equivalent to time_down_late | |
| calendar_close_end: >- | |
| {% if calendar_close_event.found and calendar_close_event.end is not none %} | |
| {{ as_timestamp(calendar_close_event.end) }} | |
| {% else %} | |
| {{ none }} | |
| {% endif %} | |
| # ╔════════════════════════════════════════════════════════════════════╗ | |
| # ║ TIME PHASE DETECTION (UNIFIED FOR TIME AND CALENDAR) ║ | |
| # ║ These variables work for BOTH time input and calendar mode ║ | |
| # ╚════════════════════════════════════════════════════════════════════╝ | |
| # Opening Phase = Time window between early and late opening | |
| # Used for: Environmental condition checks during opening window | |
| is_opening_phase: >- | |
| {{ | |
| ( | |
| is_time_field_enabled and | |
| now() >= today_at(time_up_early_today) and | |
| now() < today_at(time_up_late_today) | |
| ) or | |
| ( | |
| is_calendar_enabled and | |
| calendar_open_start is not none and | |
| calendar_open_end is not none and | |
| now_ts >= calendar_open_start and | |
| now_ts < calendar_open_end | |
| ) | |
| }} | |
| # Daytime Phase = Cover should be open (after opening until closing starts) | |
| # Used for: Resident logic, general "day" detection, shading availability | |
| is_daytime_phase: >- | |
| {{ | |
| is_time_control_disabled or | |
| ( | |
| is_time_field_enabled and | |
| now() >= today_at(time_up_early_today) and | |
| now() < today_at(time_down_early_today)) or | |
| ( | |
| is_calendar_enabled and | |
| calendar_open_start is not none and | |
| calendar_close_start is not none and | |
| now_ts >= calendar_open_start and | |
| now_ts < calendar_close_start) | |
| }} | |
| # Closing Phase = Time window between early and late closing | |
| # Used for: Environmental condition checks during closing window | |
| is_closing_phase: >- | |
| {{ | |
| ( | |
| is_time_field_enabled and | |
| now() >= today_at(time_down_early_today) and | |
| now() < today_at(time_down_late_today) | |
| ) or | |
| ( | |
| is_calendar_enabled and | |
| calendar_close_start is not none and | |
| calendar_close_end is not none and | |
| now_ts >= calendar_close_start and | |
| now_ts < calendar_close_end | |
| ) | |
| }} | |
| # Evening Phase = Cover should be closed (from closing time onwards) | |
| # Used for: Prevent unwanted opening after close time | |
| is_evening_phase: >- | |
| {{ | |
| not is_time_control_disabled and | |
| ( | |
| ( | |
| is_time_field_enabled and | |
| now() >= today_at(time_down_early_today) | |
| ) or | |
| ( | |
| is_calendar_enabled and | |
| calendar_close_start is not none and | |
| now_ts >= calendar_close_start | |
| ) | |
| ) | |
| }} | |
| # Late opening time reached (forced opening trigger) | |
| is_time_up_late: >- | |
| {{ | |
| ( | |
| is_time_field_enabled and | |
| now() >= today_at(time_up_late_today) and | |
| now() <= today_at(time_down_late_today) - timedelta(seconds = 5) | |
| ) or | |
| ( | |
| is_calendar_enabled and | |
| calendar_open_end is not none and | |
| now_ts >= calendar_open_end | |
| ) | |
| }} | |
| # Late closing time reached (forced closing trigger) | |
| is_time_down_late: >- | |
| {{ | |
| ( | |
| is_time_field_enabled and | |
| now() >= today_at(time_down_late_today) | |
| ) or | |
| ( | |
| is_calendar_enabled and | |
| calendar_close_end is not none and | |
| now_ts >= calendar_close_end | |
| ) | |
| }} | |
| # Extended daytime window: Allows shading even during closing phase | |
| # Used in: All shading logic to ensure shading only during daytime | |
| is_shading_allowed_window: >- | |
| {{ | |
| is_time_control_disabled or | |
| ( | |
| is_time_field_enabled and | |
| now() >= today_at(time_up_early_today) and | |
| now() <= today_at(time_down_late_today) + timedelta(seconds = 5) | |
| ) or | |
| ( | |
| is_calendar_enabled and | |
| calendar_open_start is not none and | |
| calendar_close_end is not none and | |
| now_ts >= calendar_open_start and | |
| now_ts <= calendar_close_end | |
| ) | |
| }} | |
| # ========== ENVIRONMENTAL CONDITIONS ========== | |
| # ALL enabled sensors must allow opening (AND logic) | |
| environment_allows_opening: >- | |
| {{ | |
| ( | |
| not is_brightness_enabled or | |
| default_brightness_sensor == [] or | |
| states(default_brightness_sensor) | float(default=brightness_up) > (brightness_up + brightness_hysteresis) | |
| ) and | |
| ( | |
| not is_sun_elevation_enabled or | |
| default_sun_sensor == [] or | |
| current_sun_elevation | float(default=sun_elevation_up_current) > sun_elevation_up_current | |
| ) | |
| }} | |
| # At least one enabled sensor must require closing (OR logic) | |
| environment_allows_closing: >- | |
| {{ | |
| ( | |
| is_brightness_enabled and | |
| ( | |
| default_brightness_sensor == [] or | |
| states(default_brightness_sensor) | float(default=brightness_down) < (brightness_down - brightness_hysteresis) | |
| ) | |
| ) or | |
| ( | |
| is_sun_elevation_enabled and | |
| ( | |
| default_sun_sensor == [] or | |
| current_sun_elevation | float(default=sun_elevation_down_current) < sun_elevation_down_current | |
| ) | |
| ) | |
| }} | |
| # ========== COMBINED CONDITIONS ========== | |
| # Should cover be open right now? (for resident logic) | |
| should_be_open_now: >- | |
| {{ | |
| is_daytime_phase and | |
| not is_evening_phase and | |
| ( | |
| (not is_brightness_enabled and not is_sun_elevation_enabled) or | |
| environment_allows_opening | |
| ) | |
| }} | |
| # Should cover be closed right now? (not use atm) | |
| should_be_closed_now: >- | |
| {{ | |
| is_evening_phase and | |
| ( | |
| (not is_brightness_enabled and not is_sun_elevation_enabled) or | |
| environment_allows_closing | |
| ) | |
| }} | |
| # Initialise empty helper with JSON default values ########################### | |
| - if: | |
| - "{{ 'cover_helper_enabled' in cover_status_options }}" | |
| - "{{ cover_status_helper != [] }}" | |
| - or: | |
| - "{{ states(cover_status_helper) in invalid_states }}" | |
| - '{{ not states(cover_status_helper) | regex_match("((\[[^\}]+)?\{s*[^\}\{]{3,}?:.*\}([^\{]+\])?)") }}' | |
| then: | |
| - action: input_text.set_value # I cannot use update_values here, because is is is_status_helper_enabled before | |
| data: | |
| entity_id: !input cover_status_helper | |
| value: "{{ helper_json | to_json }}" | |
| # Upgrade JSON-helper to current version ##################################### | |
| - if: | |
| - "{{ is_status_helper_enabled }}" | |
| - "{{ helper_json.v != 5 }}" | |
| then: | |
| - variables: | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" | |
| - variables: | |
| update_values: | |
| open: { a: "{{ helper_status.open | int | default(0) }}", t: "{{ helper_ts.open | default(ts_now) }}" } | |
| close: { a: "{{ helper_status.closed | int | default(0) }}", t: "{{ helper_ts.closed | default(ts_now) }}" } | |
| shading: { a: "{{ helper_status.shaded | int | default(0) }}", t: "{{ helper_ts.shaded | default(ts_now) }}", p: 0, q: 0 } | |
| vpart: { a: "{{ helper_status.vent_partial | int | default(0) }}", t: "{{ helper_ts.vent_partial | default(ts_now) }}" } | |
| vfull: { a: "{{ helper_status.vent_full | int | default(0) }}", t: "{{ helper_ts.vent_full | default(ts_now) }}" } | |
| manual: { a: "{{ helper_status.manual | int | default(0) }}", t: "{{ helper_ts.manual | default(ts_now) }}" } | |
| v: 5 | |
| t: "{{ helper_json.t | round(0) }}" | |
| - *helper_update | |
| - choose: | |
| ############################################################ | |
| # ANCHOR (0): OPEN | |
| # Source: All (Open, Close, Ventilation, Shading) | |
| # Target: Open, Shading | |
| ############################################################ | |
| - alias: "Check for opening" | |
| conditions: | |
| - "{{ is_up_enabled }}" | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id | regex_match('^(t_open|t_force_disabled|t_calendar_event)') }}" | |
| - condition: !input auto_up_condition | |
| - and: | |
| - "{{ (force_disabled.down or enable_background_state_tracking) }}" | |
| - "{{ (force_disabled.ventilate or enable_background_state_tracking) }}" | |
| - "{{ (force_disabled.shading_start or enable_background_state_tracking) }}" | |
| - or: # Check the helper status or the target status (issue #244) | |
| - "{{ is_status_helper_enabled and not helper_status.open }}" | |
| - "{{ not in_open_position }}" | |
| - "{{ not (is_status_helper_enabled and helper_status.manual and override_flags.opening) }}" # Override check | |
| - or: # Only once a day? | |
| - "{{ not is_status_helper_enabled }}" | |
| - "{{ not prevent_flags.opening_multiple_times }}" | |
| - "{{ is_status_helper_enabled and prevent_flags.opening_multiple_times and (now().day != helper_ts.open|timestamp_custom('%-d')|int) }}" | |
| - or: # Check whether the resident trigger is allowed | |
| - "{{ trigger.id != 't_open_6' }}" # Continue if other trigger | |
| - and: # For t_open_6: check conditions | |
| - "{{ trigger.id == 't_open_6' }}" | |
| - or: | |
| - and: # Immediate opening: only during daytime phase | |
| - "{{ resident_flags.opening_enabled }}" | |
| - "{{ is_daytime_phase }}" | |
| - "{{ should_be_open_now }}" # OR all conditions met | |
| - or: # Resident status must always be checked | |
| - "{{ resident_sensor == [] }}" | |
| - "{{ states(resident_sensor) in ['false', 'off'] }}" | |
| - and: | |
| - "{{ resident_flags.allow_opening }}" | |
| - "{{ states(resident_sensor) in ['true', 'on'] }}" | |
| - or: # Opening scenarios - works for BOTH time and calendar mode | |
| - "{{ is_time_control_disabled }}" | |
| - "{{ is_time_up_late }}" | |
| - and: # Early opening with environmental conditions | |
| - "{{ is_opening_phase or is_daytime_phase }}" | |
| - "{{ environment_allows_opening }}" | |
| sequence: | |
| - delay: | |
| seconds: "{{ range(drive_delay_fix|int(0), drive_delay_fix|int(0) + drive_delay_random|int(0) +1) | random }}" | |
| - choose: | |
| ################################### | |
| # Cover should be shaded | |
| ################################### | |
| - alias: "Shading detected. Move to shading position" | |
| conditions: | |
| - "{{ is_status_helper_enabled }}" | |
| - "{{ helper_status.shaded }}" | |
| - "{{ not in_shading_position }}" # Even if this is hardly possible, there may be situations that require it. Purely as a precautionary measure in case the target state and actual state do not match. | |
| sequence: | |
| - choose: [] | |
| default: !input auto_shading_start_action_before | |
| - variables: | |
| target_position: !input shading_position | |
| target_tilt_position: "{{ shading_tilt_position | int }}" | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 1, t: "{{ ts_now }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 1, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } # Set shading to true, but do not overwrite shading time | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_shading_start_action | |
| ################################### | |
| # Cover should be opened | |
| ################################### | |
| - alias: "Normal opening of the cover" | |
| conditions: | |
| - "{{ not in_shading_position }}" # In this part, there is no plan for an open roller blind to switch from Shading to Open. I cannot check the helper here because Open+Shading could be set to true there. | |
| - or: | |
| - "{{ is_status_helper_enabled and not helper_status.open }}" # including ventilation, window open-status and more possibilities | |
| - "{{ not in_open_position }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_up_action_before | |
| - variables: | |
| target_position: !input open_position | |
| target_tilt_position: "{{ open_tilt_position | int }}" | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 1, t: "{{ ts_now }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - if: | |
| - "{{ not is_cover_movement_blocked.open }}" | |
| then: | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - if: | |
| - "{{ not is_cover_movement_blocked.open }}" | |
| then: | |
| - choose: [] | |
| default: !input auto_up_action | |
| # - stop: "Stop automation: OPEN" | |
| ############################################################ | |
| # ANCHOR (1): CLOSE | |
| # Source: All (Open, Close, Ventilation, Shading) | |
| # Target: Close, Ventilation | |
| ############################################################ | |
| - alias: "Check for closing cover" | |
| conditions: | |
| - "{{ is_down_enabled }}" | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id | regex_match('^(t_close|t_force_disabled|t_calendar_event)') }}" | |
| - condition: !input auto_down_condition | |
| - and: | |
| - "{{ (force_disabled.up or enable_background_state_tracking) }}" | |
| - "{{ (force_disabled.ventilate or enable_background_state_tracking) }}" | |
| - "{{ (force_disabled.shading_start or enable_background_state_tracking) }}" | |
| - or: # Check the helper status or the target status | |
| - "{{ is_status_helper_enabled and not helper_status.closed }}" | |
| - "{{ not in_close_position }}" | |
| # - "{{ in_open_position }}" | |
| - "{{ not (is_status_helper_enabled and helper_status.manual and override_flags.closing) }}" # Override check | |
| - or: # Only once a day? | |
| - "{{ not is_status_helper_enabled }}" | |
| - "{{ not prevent_flags.closing_multiple_times }}" | |
| - "{{ is_status_helper_enabled and prevent_flags.closing_multiple_times and (helper_ts.closed < today_at(time_down_early_today) | as_timestamp) }}" | |
| - or: # Closing scenarios - works for BOTH time and calendar mode | |
| - "{{ is_time_control_disabled }}" | |
| - "{{ is_time_down_late }}" | |
| - and: # Early closing with environmental conditions | |
| - "{{ is_closing_phase or is_evening_phase }}" | |
| - "{{ environment_allows_closing }}" | |
| - and: # Resident goes sleeping | |
| - "{{ trigger.id == 't_close_6' }}" | |
| - "{{ resident_sensor != [] }}" | |
| - "{{ states(resident_sensor) in ['true', 'on'] }}" | |
| - "{{ resident_flags.closing_enabled }}" | |
| sequence: | |
| - choose: | |
| ################################# | |
| # Lockout protection when closing | |
| ################################# | |
| - alias: "Lockout protection when closing" | |
| conditions: | |
| - "{{ is_ventilation_enabled }}" | |
| - "{{ is_status_helper_enabled }}" | |
| - or: | |
| - and: | |
| - "{{ contact_window_opened != [] }}" | |
| - "{{ states(contact_window_opened) in ['true', 'on'] }}" | |
| - and: | |
| - "{{ lockout_tilted_when_closing }}" | |
| - "{{ contact_window_tilted != [] }}" | |
| - "{{ states(contact_window_tilted) in ['true', 'on'] }}" | |
| sequence: | |
| - variables: | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 0, t: "{{ helper_json.open.t }}" } | |
| close: { a: 1, t: "{{ ts_now }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 1, t: "{{ ts_now }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| ############################################################################ | |
| # Window tilted. No lockout. Move to ventilation position instead of closing | |
| ############################################################################ | |
| - alias: "Window tilted. No lockout. Move to ventilation position instead of closing" | |
| conditions: | |
| - "{{ is_ventilation_enabled }}" | |
| - "{{ is_status_helper_enabled }}" | |
| - "{{ contact_window_tilted != [] }}" | |
| - "{{ states(contact_window_tilted) in ['true', 'on'] }}" | |
| - "{{ not lockout_tilted_when_closing }}" | |
| - or: | |
| - "{{ contact_window_opened == [] }}" | |
| - "{{ states(contact_window_opened) in ['false', 'off'] }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_ventilate_action_before | |
| - delay: | |
| seconds: "{{ range(drive_delay_fix|int(0), drive_delay_fix|int(0) + drive_delay_random|int(0) +1) | random }}" | |
| - variables: | |
| target_position: !input ventilate_position | |
| target_tilt_position: !input ventilate_tilt_position | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 1, t: "{{ ts_now }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 1, t: "{{ ts_now }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - if: | |
| - "{{ not is_cover_movement_blocked.ventilate }}" | |
| then: | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - if: | |
| - "{{ not is_cover_movement_blocked.ventilate }}" | |
| then: | |
| - choose: [] | |
| default: !input auto_ventilate_action | |
| ################################### | |
| # Cover is already (almost) closed | |
| ################################### | |
| - alias: "Only status change if cover is already 'near' the close position" # No driving here! | |
| conditions: | |
| - or: | |
| - and: # Stop if cover is shaded and close position is lower than shading position | |
| - "{{ is_status_helper_enabled }}" | |
| - "{{ prevent_flags.lowering_when_closing_if_shaded }}" | |
| - "{{ helper_status.shaded }}" | |
| - "{{ not position_comparisons.shading_above_close }}" | |
| - and: # Stop if cover is shaded and close position is lower than shading position | |
| - "{{ prevent_flags.lowering_when_closing_if_shaded }}" | |
| - "{{ in_shading_position }}" # Always, no further checks for 'is_status_helper_enabled' | |
| - "{{ not position_comparisons.shading_above_close }}" | |
| - and: # Stop if close-position is higher than the current position | |
| - "{{ prevent_flags.higher_position_closing }}" | |
| - "{{ position_comparisons.current_below_close or current_position == close_position }}" | |
| sequence: | |
| - variables: | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 0, t: "{{ helper_json.open.t }}" } | |
| close: { a: 1, t: "{{ ts_now }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| # manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| ################################### | |
| # Normal closing of the cover | |
| ################################### | |
| default: | |
| - choose: [] | |
| default: !input auto_down_action_before | |
| - alias: "Normal closing of the cover" | |
| delay: | |
| seconds: "{{ range(drive_delay_fix|int(0), drive_delay_fix|int(0) + drive_delay_random|int(0) +1) | random }}" | |
| - variables: | |
| target_position: !input close_position | |
| target_tilt_position: !input close_tilt_position | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 0, t: "{{ helper_json.open.t }}" } | |
| close: { a: 1, t: "{{ ts_now }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } # It is intentional that the shading is deleted when the roller blind is closed. The day is over and the roller blind is low enough. | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - if: | |
| - "{{ not is_cover_movement_blocked.close }}" | |
| then: | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - if: | |
| - "{{ not is_cover_movement_blocked.close }}" | |
| then: | |
| - choose: [] | |
| default: !input auto_down_action | |
| # - stop: "Stop automation: CLOSE" | |
| ############################################################ | |
| # ANCHOR (2): SHADING START | |
| # Source: Open | |
| # Target: Shading, Lockout | |
| ############################################################ | |
| - alias: "Check for shading start" | |
| conditions: | |
| - "{{ is_shading_enabled }}" | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id | regex_match('^(t_shading_start|t_open_1|t_open_3|t_force_disabled)') }}" | |
| - "{{ is_status_helper_enabled }}" | |
| - condition: !input auto_shading_start_condition | |
| - and: | |
| - "{{ (force_disabled.up or enable_background_state_tracking) }}" | |
| - "{{ (force_disabled.down or enable_background_state_tracking) }}" | |
| - "{{ (force_disabled.ventilate or enable_background_state_tracking) }}" | |
| - or: # Check the helper status or the target status | |
| - "{{ not helper_status.shaded }}" | |
| - "{{ not (helper_status.vent_partial or helper_status.vent_full) }}" | |
| - or: # Try to avoid starting the shading several times TODO / FIXME? | |
| - "{{ not is_cover_tilt_enabled_and_possible and not in_shading_position }}" | |
| - "{{ is_cover_tilt_enabled_and_possible }}" | |
| - "{{ not (helper_status.manual and override_flags.shading) }}" # Override check | |
| - or: # Only once a day? | |
| - "{{ not prevent_flags.shading_multiple_times }}" | |
| - "{{ prevent_flags.shading_multiple_times and (now().day != helper_ts.shaded|timestamp_custom('%-d') | int) }}" | |
| - "{{ trigger.id | regex_match('^(t_shading_start_execution)') }}" # Execution must not be stopped if there is a shading pending in the helper beforehand. | |
| sequence: | |
| - if: | |
| - or: | |
| - and: # Independent temperature-based shading | |
| - "{{ 'shading_temp_comparison_independent' in shading_config }}" | |
| - "{{ shading_start_condition_states.forecast_temp_valid }}" | |
| - "{{ shading_start_conditions_met }}" # Use flexible AND/OR logic | |
| then: | |
| - choose: | |
| ##################################################################### | |
| # Shading start detected. Save next execution time and pending status | |
| ##################################################################### | |
| - alias: "Shading detected. Save next execution time and pending status" | |
| conditions: | |
| - "{{ trigger.id | regex_match('^(t_shading_start_pending)') }}" | |
| - "{{ not helper_status.shading_start_pending }}" # Not if a pending is already active | |
| sequence: | |
| - variables: | |
| local_waitingtime_start: "{{ shading_waitingtime_start | int }}" | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| shading: {a: 1, t: "{{ ts_now }}", p: "{{ (ts_now + local_waitingtime_start) | round(0) }}", q: 0} | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| ################################################## | |
| # Consider lockout protection when shading starts | |
| ################################################## | |
| - alias: "Consider lockout protection when shading starts" | |
| conditions: | |
| - "{{ trigger.id | regex_match('^(t_shading_start_execution)') }}" | |
| - "{{ helper_status.shading_start_pending }}" # Only when it comes back from pending status | |
| - "{{ is_ventilation_enabled }}" | |
| - or: | |
| - and: | |
| - "{{ contact_window_opened != [] }}" | |
| - "{{ states(contact_window_opened) in ['true', 'on'] }}" | |
| - and: | |
| - "{{ lockout_tilted_when_shading_starts }}" | |
| - "{{ contact_window_tilted != [] }}" | |
| - "{{ states(contact_window_tilted) in ['true', 'on'] }}" | |
| sequence: | |
| - variables: | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 1, t: "{{ ts_now }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 1, t: "{{ ts_now }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 1, t: "{{ ts_now }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| ################################### | |
| # Start Shading | |
| ################################### | |
| - alias: "Start Shading" | |
| conditions: | |
| - "{{ trigger.id | regex_match('^(t_shading_start_execution)') }}" | |
| - "{{ helper_status.shading_start_pending }}" # Only when it comes back from pending status | |
| - or: | |
| - and: | |
| - "{{ not is_cover_tilt_enabled_and_possible }}" | |
| - "{{ position_comparisons.current_above_shading }}" | |
| - and: | |
| - "{{ is_cover_tilt_enabled_and_possible }}" | |
| - "{{ position_comparisons.current_above_shading or current_position == shading_position }}" | |
| - or: # Shading during daytime window | |
| - "{{ is_shading_allowed_window }}" | |
| - or: | |
| - "{{ resident_sensor == [] }}" | |
| - "{{ states(resident_sensor) in ['false', 'off'] }}" | |
| - and: # Issue 131 | |
| - "{{ resident_flags.allow_shading }}" | |
| - "{{ states(resident_sensor) in ['true', 'on'] }}" | |
| sequence: | |
| - delay: | |
| seconds: "{{ range(drive_delay_fix | int(0), drive_delay_fix | int(0) + drive_delay_random | int(0) +1) | random }}" | |
| - choose: [] | |
| default: !input auto_shading_start_action_before | |
| - variables: | |
| target_position: !input shading_position | |
| target_tilt_position: "{{ shading_tilt_position | int }}" | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 1, t: "{{ ts_now }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 1, t: "{{ ts_now }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } # TODO: Do I really want to overwrite the status here? | |
| t: "{{ ts_now }}" | |
| - if: | |
| - "{{ not is_cover_movement_blocked.shading }}" | |
| then: | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - if: | |
| - "{{ not is_cover_movement_blocked.shading }}" | |
| then: | |
| - choose: [] | |
| default: !input auto_shading_start_action | |
| #################################### | |
| # Save shading state for the future | |
| #################################### | |
| - alias: "Save shading state for the future" | |
| conditions: | |
| - "{{ trigger.id | regex_match('^(t_shading_start_execution)') }}" | |
| - "{{ helper_status.shading_start_pending }}" # Only when it comes back from pending status | |
| - "{{ helper_status.closed }}" # At this point, the times for shading are not taken into account so that the correct value is available when the cover is opened. | |
| sequence: | |
| - variables: | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| shading: { a: 0, t: "{{ ts_now }}", p: 0, q: 0 } | |
| t: "{{ ts_now }}" | |
| - if: | |
| - "{{ not is_cover_movement_blocked.any }}" | |
| then: | |
| - *tilt_move_action | |
| - *helper_update | |
| # - stop: "Stop automation: SHADING START" | |
| else: # Shading conditions were no longer met or not met again. | |
| - choose: | |
| ##################################################################### | |
| # Shading start conditions not met - check if we should continue | |
| ##################################################################### | |
| - alias: "Shading start conditions not met - check if we should continue" | |
| conditions: | |
| - "{{ trigger.id | regex_match('^(t_shading_start_execution)') }}" | |
| - "{{ helper_status.shading_start_pending }}" | |
| - "{{ shading_start_max_duration > 0 }}" | |
| - or: # Continue only within time window AND before timeout | |
| - "{{ is_shading_allowed_window }}" | |
| - "{{ (as_timestamp(now()) - helper_json.shading.t) <= shading_start_max_duration }}" | |
| sequence: | |
| - variables: | |
| local_waitingtime_start: "{{ shading_waitingtime_start | int }}" | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| shading: { a: 1, t: "{{ helper_json.shading.t }}", p: "{{ (ts_now + local_waitingtime_start) | round(0) }}", q: 0 } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| ###################################################################### | |
| # Shading start - stop retry (timeout OR outside time window OR disabled) | |
| ###################################################################### | |
| - alias: "Shading start - stop retry" | |
| conditions: | |
| - "{{ trigger.id | regex_match('^(t_shading_start_execution)') }}" | |
| - "{{ helper_status.shading_start_pending }}" | |
| sequence: | |
| - variables: | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| shading: { a: 0, t: "{{ ts_now }}", p: 0, q: 0 } | |
| t: "{{ ts_now}}" | |
| - *helper_update | |
| ############################################################ | |
| # ANCHOR (3): SHADING Tilt | |
| ############################################################ | |
| - alias: "Check for shading tilt" | |
| conditions: | |
| - "{{ is_shading_enabled }}" | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id | regex_match('^(t_shading_tilt)') }}" | |
| - "{{ is_status_helper_enabled }}" | |
| - and: | |
| - "{{ helper_status.shaded }}" | |
| - "{{ not helper_status.shading_start_pending }}" | |
| - "{{ is_cover_tilt_enabled_and_possible }}" | |
| - condition: !input auto_shading_tilt_condition | |
| - and: | |
| - "{{ force_disabled.up }}" | |
| - "{{ force_disabled.down }}" | |
| - "{{ force_disabled.ventilate }}" | |
| - "{{ not (helper_status.vent_partial or helper_status.vent_full) }}" | |
| - "{{ not (helper_status.manual and override_flags.shading) }}" # Override check | |
| sequence: | |
| - variables: | |
| target_tilt_position: "{{ shading_tilt_position | int }}" | |
| tilt_first: "{{ is_cover_tilt_reposition_enabled | bool }}" # tilt the cover to close position before | |
| update_values: | |
| t: "{{ as_timestamp(now()) | round(0) }}" | |
| - *tilt_move_action | |
| - *helper_update | |
| ############################################################ | |
| # ANCHOR (4): SHADING END | |
| # Source: Shading | |
| # Target: Open, Lockout, Ventilation | |
| ############################################################ | |
| - alias: "Check for shading end" | |
| conditions: | |
| - "{{ is_shading_enabled }}" | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id | regex_match('^(t_shading_end)') }}" | |
| - "{{ is_status_helper_enabled }}" | |
| - condition: !input auto_shading_end_condition | |
| - and: | |
| - "{{ (force_disabled.up or enable_background_state_tracking) }}" | |
| - "{{ (force_disabled.down or enable_background_state_tracking) }}" | |
| - "{{ (force_disabled.ventilate or enable_background_state_tracking) }}" | |
| - "{{ (force_disabled.shading_start or enable_background_state_tracking) }}" | |
| - or: | |
| - "{{ is_shading_allowed_window }}" | |
| - "{{ not (helper_status.manual and override_flags.shading) }}" # Override check | |
| - "{{ not helper_status.closed }}" | |
| - or: | |
| - "{{ helper_status.shaded }}" | |
| - "{{ in_shading_position }}" # Be careful if the tolerance value is too high! | |
| - or: # Optional: Continue if cover is not already closed | |
| - "{{ not prevent_flags.shading_end_if_closed }}" | |
| - "{{ prevent_flags.shading_end_if_closed and not in_close_position}}" | |
| - or: # Issue #116 | |
| - "{{ resident_sensor == [] }}" | |
| - "{{ states(resident_sensor) in ['false', 'off'] }}" | |
| sequence: | |
| - if: | |
| - "{{ shading_end_conditions_met }}" # Use flexible AND/OR logic for END | |
| then: | |
| - choose: | |
| ##################################################################### | |
| # Shading end detected. Save next execution time and pending status | |
| ##################################################################### | |
| - alias: "Shading end detected. Save next execution time and pending status" | |
| conditions: | |
| - "{{ trigger.id | regex_match('^(t_shading_end_pending)') }}" | |
| - "{{ not helper_status.shading_end_pending }}" | |
| sequence: | |
| - variables: | |
| local_waitingtime_end: > | |
| {% set local_waitingtime_end = shading_waitingtime_end | int %} | |
| {% if (is_shading_end_immediate_by_sun_position | default(false)) | bool and trigger.id == 't_shading_end_pending_5' %} | |
| {# Almost immediately end shading when sun position out of range. Earlier triggers do not work. #} | |
| {% set local_waitingtime_end = 20 %} | |
| {% endif %} | |
| {{ local_waitingtime_end }} | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| shading: { a: 1, t: "{{ ts_now }}", p: 0, q: "{{ (ts_now + (local_waitingtime_end | int)) | round(0) }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| ###################################### | |
| # Only tilt open after shading ends | |
| ###################################### | |
| - alias: "Only tilt open after shading ends" | |
| conditions: | |
| - "{{ trigger.id | regex_match('^(t_shading_end_execution)') }}" | |
| - "{{ helper_status.shading_end_pending }}" | |
| - "{{ is_cover_tilt_enabled_and_possible }}" | |
| - "{{ prevent_flags.opening_after_shading_end}}" | |
| sequence: | |
| - variables: | |
| target_tilt_position: 50 # Moving the cover to open tilt position | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| shading: { a: 0, t: "{{ ts_now }}", p: 0, q: 0 } | |
| t: "{{ ts_now }}" | |
| - if: | |
| - "{{ not is_cover_movement_blocked.any }}" | |
| then: | |
| - *tilt_move_action | |
| - *helper_update | |
| ###################################### | |
| # Lockout protection when shading ends | |
| ###################################### | |
| - alias: "Lockout protection when shading ends" | |
| conditions: | |
| - "{{ trigger.id | regex_match('^(t_shading_end_execution)') }}" | |
| - "{{ helper_status.shading_end_pending }}" | |
| - "{{ is_ventilation_enabled }}" | |
| - "{{ position_comparisons.current_below_ventilate }}" | |
| - or: | |
| - and: | |
| - "{{ contact_window_opened != [] }}" | |
| - "{{ states(contact_window_opened) in ['true', 'on'] }}" | |
| - and: | |
| - "{{ contact_window_tilted != [] }}" | |
| - "{{ states(contact_window_tilted) in ['true', 'on'] }}" | |
| - "{{ lockout_tilted_when_shading_ends }}" | |
| sequence: | |
| - variables: | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 1, t: "{{ ts_now }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 1, t: "{{ ts_now }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| ################################ | |
| # Ventilation after shading ends | |
| ################################ | |
| - alias: "Ventilation after shading ends" | |
| conditions: | |
| - "{{ trigger.id | regex_match('^(t_shading_end_execution)') }}" | |
| - "{{ helper_status.shading_end_pending }}" | |
| - "{{ is_ventilation_enabled }}" | |
| - "{{ ventilation_flags.after_shading_end }}" | |
| - "{{ contact_window_tilted != [] }}" | |
| - "{{ states(contact_window_tilted) in ['true', 'on'] }}" | |
| - "{{ not lockout_tilted_when_closing }}" | |
| - or: | |
| - "{{ contact_window_opened == [] }}" | |
| - "{{ states(contact_window_opened) in ['false', 'off'] }}" | |
| - or: | |
| - "{{ position_comparisons.current_below_ventilate }}" | |
| - "{{ ventilation_flags.if_lower_enabled and position_comparisons.current_above_ventilate or current_position == ventilate_position }}" | |
| sequence: | |
| - delay: # TODO: I can no longer recognize the original trigger here. That might be a bad idea here. | |
| seconds: > | |
| {% if (is_shading_end_immediate_by_sun_position | default(false)) | bool %} | |
| {{ range(0, 2) | random }} | |
| {% else %} | |
| {{ range(drive_delay_fix|int(0), drive_delay_fix|int(0) + drive_delay_random|int(0) +1) | random }} | |
| {% endif %} | |
| - choose: [] | |
| default: !input auto_ventilate_action_before | |
| - variables: | |
| target_position: !input ventilate_position | |
| target_tilt_position: !input ventilate_tilt_position | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 1, t: "{{ ts_now }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 1, t: "{{ ts_now }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - if: | |
| - "{{ not is_cover_movement_blocked.any }}" | |
| then: | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - if: | |
| - "{{ not is_cover_movement_blocked.any }}" | |
| then: | |
| - choose: [] | |
| default: !input auto_ventilate_action | |
| ######################################################## | |
| # Move cover after shading end - conditions still valid | |
| ######################################################## | |
| - alias: "Move cover after shading end - conditions still valid" | |
| conditions: | |
| - "{{ trigger.id | regex_match('^(t_shading_end_execution)') }}" | |
| - "{{ helper_status.shading_end_pending }}" | |
| - or: | |
| - "{{ current_sun_azimuth > shading_azimuth_end }}" | |
| - "{{ current_sun_elevation > shading_elevation_max }}" | |
| - "{{ current_sun_elevation < shading_elevation_min }}" | |
| - "{{ shading_brightness_sensor != [] and states(shading_brightness_sensor) | float(default=999999) < (shading_sun_brightness_end - shading_sun_brightness_hysteresis) }}" | |
| - "{{ shading_temperatur_sensor1 != [] and states(shading_temperatur_sensor1) | float(default=999) < (shading_min_temperatur1 - shading_temperature_hysteresis1) }}" | |
| - "{{ shading_temperatur_sensor2 != [] and states(shading_temperatur_sensor2) | float(default=999) < (shading_min_temperatur2 - shading_temperature_hysteresis2) }}" | |
| - "{{ shading_end_condition_states.forecast_temp_invalid }}" # Checks during shading end execution whether conditions are still valid | |
| sequence: | |
| - delay: # TODO: I can no longer recognize the original trigger here. That might be a bad idea here. | |
| seconds: > | |
| {% if (is_shading_end_immediate_by_sun_position | default(false)) | bool %} | |
| {{ range(0, 2) | random }} | |
| {% else %} | |
| {{ range(drive_delay_fix|int(0), drive_delay_fix|int(0) + drive_delay_random|int(0) +1) | random }} | |
| {% endif %} | |
| - choose: [] | |
| default: !input auto_shading_end_action_before | |
| - if: | |
| - "{{ not prevent_flags.opening_after_shading_end}}" | |
| then: | |
| - variables: | |
| target_position: "{{ open_position }}" | |
| target_tilt_position: "{{ open_tilt_position | int }}" | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 1, t: "{{ ts_now }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - if: | |
| - "{{ not is_cover_movement_blocked.any }}" | |
| then: | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| else: | |
| - variables: | |
| update_values: | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| t: "{{ as_timestamp(now()) | round(0) }}" | |
| - *helper_update | |
| - if: | |
| - "{{ not is_cover_movement_blocked.any }}" | |
| then: | |
| - choose: [] | |
| default: !input auto_shading_end_action | |
| ##################################################################### | |
| # Shading end conditions not met - check if we should continue | |
| ##################################################################### | |
| - alias: "Shading end conditions not met - check if we should continue" | |
| conditions: | |
| - "{{ trigger.id | regex_match('^(t_shading_end_execution)') }}" | |
| - "{{ helper_status.shading_end_pending }}" | |
| - "{{ shading_end_max_duration > 0 }}" | |
| - or: # Continue only within time window AND before timeout | |
| - "{{ is_shading_allowed_window }}" | |
| - "{{ (as_timestamp(now()) - helper_json.shading.t) <= shading_end_max_duration }}" | |
| sequence: | |
| - variables: | |
| local_waitingtime_end: "{{ shading_waitingtime_end | int }}" | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| shading: { a: 1, t: "{{ helper_json.shading.t }}", p: 0, q: "{{ (ts_now + local_waitingtime_end) | round(0) }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| ###################################################################### | |
| # Shading end - stop retry (timeout OR outside time window OR disabled) | |
| ###################################################################### | |
| - alias: "Shading end - stop retry" | |
| conditions: | |
| - "{{ trigger.id | regex_match('^(t_shading_end_execution)') }}" | |
| - "{{ helper_status.shading_end_pending }}" | |
| sequence: | |
| - variables: | |
| # timeout_exceeded: "{{ shading_end_max_duration > 0 and (as_timestamp(now()) - helper_json.shading.t) > shading_end_max_duration }}" | |
| # periodic_disabled: "{{ shading_end_max_duration == 0 }}" | |
| update_values: | |
| shading: { a: 1, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| t: "{{ as_timestamp(now()) | round(0) }}" | |
| - *helper_update | |
| # - stop: "Stop automation: SHADING END" | |
| ############################################################ | |
| # ANCHOR (5): CONTACT CHANGED | |
| # Source: Shading, Close, Open | |
| # Target: Ventilation (Open/Partial), or back to previous state | |
| ############################################################ | |
| - alias: "Contact sensor state changed" | |
| conditions: | |
| - "{{ is_ventilation_enabled }}" | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id in ['t_contact_tilted_changed', 't_contact_opened_changed'] or trigger.id == 't_force_disabled' }}" | |
| - "{{ is_status_helper_enabled }}" | |
| - condition: !input auto_ventilate_condition | |
| - "{{ trigger.from_state.state not in invalid_states }}" | |
| - "{{ trigger.to_state.state not in invalid_states }}" | |
| - and: | |
| - "{{ (force_disabled.up or enable_background_state_tracking) }}" | |
| - "{{ (force_disabled.down or enable_background_state_tracking) }}" | |
| - "{{ (force_disabled.shading_start or enable_background_state_tracking) }}" | |
| - or: | |
| - "{{ resident_sensor == [] }}" | |
| - "{{ states(resident_sensor) in ['false', 'off'] }}" | |
| - and: | |
| - "{{ resident_flags.allow_ventilation }}" | |
| - "{{ states(resident_sensor) in ['true', 'on'] }}" | |
| sequence: | |
| - delay: # Wait for all sensor changes to settle | |
| seconds: !input contact_delay_status | |
| - variables: # Re-evaluate BOTH sensors with current values | |
| contact_opened_now: "{{ contact_window_opened != [] and states(contact_window_opened) in ['true', 'on'] }}" | |
| contact_tilted_now: "{{ contact_window_tilted != [] and states(contact_window_tilted) in ['true', 'on'] }}" | |
| - choose: | |
| ################################### | |
| # Window handle to open. Open cover | |
| ################################### | |
| - alias: "Window handle to open. Open cover" | |
| conditions: | |
| - "{{ contact_opened_now }}" | |
| - "{{ not in_open_position }}" | |
| sequence: | |
| - variables: | |
| target_position: !input open_position | |
| target_tilt_position: !input open_tilt_position | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 1, t: "{{ ts_now }}" } | |
| t: "{{ ts_now }}" | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| ############################################################################ | |
| # Window tilted (not fully opened) - Partial ventilation | |
| ############################################################################ | |
| - alias: "Window tilted - Partial ventilation" | |
| conditions: | |
| - "{{ contact_tilted_now }}" | |
| - "{{ not contact_opened_now }}" | |
| - or: | |
| - "{{ position_comparisons.current_below_ventilate }}" | |
| - and: | |
| - "{{ is_cover_tilt_enabled_and_possible }}" | |
| - "{{ position_comparisons.current_below_ventilate or current_position == ventilate_position }}" | |
| - "{{ (current_tilt_position <= ventilate_tilt_position) }}" | |
| - "{{ ventilation_flags.if_lower_enabled and position_comparisons.current_above_ventilate or current_position == ventilate_position }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_ventilate_action_before | |
| - variables: | |
| target_position: !input ventilate_position | |
| target_tilt_position: !input ventilate_tilt_position | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| vpart: { a: 1, t: "{{ ts_now }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| t: "{{ ts_now }}" | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_ventilate_action | |
| # - stop: "Stop automation: CONTACT OPEN" | |
| ############################################################ | |
| # ANCHOR (6): CONTACT CLOSED | |
| # Source: Ventilation, Lockout | |
| # Target: Open, Close, Shading | |
| ############################################################ | |
| - alias: "Contact sensor closed" | |
| conditions: | |
| - "{{ is_ventilation_enabled }}" | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id in ['t_contact_tilted_changed', 't_contact_opened_changed'] }}" | |
| - "{{ is_status_helper_enabled }}" | |
| - condition: !input auto_ventilate_end_condition | |
| - "{{ trigger.from_state.state not in invalid_states }}" | |
| - "{{ trigger.to_state.state not in invalid_states }}" | |
| - and: | |
| - "{{ force_disabled.up }}" | |
| - "{{ force_disabled.down }}" | |
| - "{{ force_disabled.ventilate }}" | |
| - "{{ force_disabled.shading_start }}" | |
| - "{{ not (helper_status.manual and override_flags.ventilation) }}" # Override check | |
| sequence: | |
| # Wait for all sensor changes to settle | |
| - delay: | |
| seconds: !input contact_delay_status | |
| # Re-evaluate BOTH sensors with current values | |
| - variables: | |
| contact_opened_now: "{{ contact_window_opened != [] and states(contact_window_opened) in ['true', 'on'] }}" | |
| contact_tilted_now: "{{ contact_window_tilted != [] and states(contact_window_tilted) in ['true', 'on'] }}" | |
| # Only proceed if BOTH sensors are now closed/off | |
| - condition: template | |
| value_template: "{{ not contact_opened_now and not contact_tilted_now }}" | |
| # Additional check: Was there ventilation active before? | |
| - condition: template | |
| value_template: >- | |
| {{ | |
| helper_status.vent_partial or | |
| helper_status.vent_full or | |
| in_ventilate_position or | |
| in_open_position | |
| }} | |
| - if: | |
| - "{{ ventilation_flags.delay_enabled }}" | |
| then: | |
| - delay: | |
| seconds: "{{ range(drive_delay_fix|int(0), drive_delay_fix|int(0) + drive_delay_random|int(0) +1) | random }}" | |
| - choose: | |
| ##################################################### | |
| # Contact sensor closed. Activate partial ventilation | |
| ##################################################### | |
| - alias: "Contact sensor closed. Activate partial ventilation" | |
| conditions: | |
| - "{{ helper_status.vent_full }}" # Was fully ventilated before :-) | |
| - "{{ contact_tilted_now }}" # Tilted sensor still active | |
| sequence: | |
| - choose: [] | |
| default: !input auto_ventilate_action_before | |
| - variables: | |
| target_position: !input ventilate_position | |
| target_tilt_position: !input ventilate_tilt_position | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| vpart: { a: 1, t: "{{ ts_now }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| t: "{{ ts_now }}" | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_ventilate_action | |
| ########################################## | |
| # Contact sensor closed. Activate shading | |
| ########################################## | |
| - alias: "Contact sensor closed. Activate shading" | |
| conditions: | |
| - "{{ helper_status.shaded }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_shading_start_action_before | |
| - variables: | |
| target_position: !input shading_position | |
| target_tilt_position: "{{ shading_tilt_position | int }}" | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 1, t: "{{ ts_now }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 1, t: "{{ ts_now }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_shading_start_action | |
| ######################################## | |
| # Contact sensor closed. Open the cover | |
| ######################################## | |
| - alias: "Contact sensor closed. Open the cover" | |
| conditions: | |
| - "{{ helper_status.open }}" | |
| - "{{ not helper_status.shaded }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_up_action_before | |
| - variables: | |
| target_position: !input open_position | |
| target_tilt_position: !input open_tilt_position | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 1, t: "{{ ts_now }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| - if: | |
| - "{{ not prevent_flags.opening_after_ventilation_end }}" | |
| then: | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - choose: [] | |
| default: !input auto_up_action | |
| ######################################## | |
| # Contact sensor closed. Close the cover | |
| ######################################## | |
| - alias: "Contact sensor closed. Close the cover" | |
| conditions: | |
| - "{{ helper_status.closed }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_down_action_before | |
| - variables: | |
| target_position: !input close_position | |
| target_tilt_position: !input close_tilt_position | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 0, t: "{{ helper_json.open.t }}" } | |
| close: { a: 1, t: "{{ ts_now }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_down_action | |
| # - stop: "Stop automation: CONTACT CLOSED" | |
| ############################################################ | |
| # ANCHOR (7): FORCE OPEN | |
| ############################################################ | |
| - alias: "Forced opening of the cover" | |
| conditions: | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id == 't_force_open_on' }}" | |
| - "{{ force_disabled.down }}" | |
| - "{{ force_disabled.ventilate }}" | |
| - "{{ force_disabled.shading_start }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_up_action_before | |
| - variables: | |
| target_position: !input open_position | |
| target_tilt_position: !input open_tilt_position | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 1, t: "{{ ts_now }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_up_action | |
| # - stop: "Stop automation: FORCE OPEN" | |
| ############################################################ | |
| # ANCHOR (8): FORCE CLOSE | |
| ############################################################ | |
| - alias: "Forced closing of the cover" | |
| conditions: | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id == 't_force_close_on' }}" | |
| - "{{ force_disabled.up }}" | |
| - "{{ force_disabled.ventilate }}" | |
| - "{{ force_disabled.shading_start }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_down_action_before | |
| - variables: | |
| target_position: !input close_position | |
| target_tilt_position: !input close_tilt_position | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 0, t: "{{ helper_json.open.t }}" } | |
| close: { a: 1, t: "{{ ts_now }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_down_action | |
| # - stop: "Stop automation: FORCE CLOSE" | |
| ############################################################ | |
| # ANCHOR (9): FORCE VENTILATION | |
| ############################################################ | |
| - alias: "Forced ventilation of the cover" | |
| conditions: | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id == 't_force_vent_on' }}" | |
| - "{{ is_status_helper_enabled }}" | |
| - "{{ force_disabled.up }}" | |
| - "{{ force_disabled.down }}" | |
| - "{{ force_disabled.shading_start }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_ventilate_action_before | |
| - variables: | |
| target_position: !input ventilate_position | |
| target_tilt_position: !input ventilate_tilt_position | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 0, t: "{{ helper_json.open.t }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 1, t: "{{ ts_now }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_ventilate_action | |
| # - stop: "Stop automation: FORCE VENTILATION" | |
| ############################################################ | |
| # ANCHOR (10): FORCE SHADING START | |
| ############################################################ | |
| - alias: "Forced activating of the sun shading" | |
| conditions: | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id == 't_force_shading_start_on' }}" | |
| - "{{ is_status_helper_enabled }}" | |
| - "{{ force_disabled.up }}" | |
| - "{{ force_disabled.down }}" | |
| - "{{ force_disabled.ventilate }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_shading_start_action_before | |
| - variables: | |
| target_position: !input shading_position | |
| target_tilt_position: "{{ shading_tilt_position |int }}" | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| open: { a: 0, t: "{{ helper_json.open.t }}" } # ‘Open’ is not additionally set to True, because we don't know where it should actually go after forcing. | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 1, t: "{{ ts_now }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 0, t: "{{ helper_json.manual.t }}" } | |
| t: "{{ ts_now }}" | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_shading_start_action | |
| # - stop: "Stop automation: FORCE SHADING START" | |
| ############################################################ | |
| # ANCHOR (11): MANUAL | |
| ############################################################ | |
| - alias: "Checking for manual position changes" | |
| conditions: | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id in ['t_manual_1', 't_manual_4'] }}" | |
| - "{{ this.attributes.current | int(99) == 0 }}" # Avoid multiple triggering. Problem: A recognised position change can interrupt ongoing delays (contact changes). | |
| - "{{ 'cover_helper_enabled' in cover_status_options }}" # Do not use 'is_status_helper_enabled' (with the embedded regex here), because it could be set after variable declaration | |
| - "{{ cover_status_helper != [] }}" | |
| - "{{ as_timestamp(now()) > helper_json.t + drive_time | default(90) + 60 }}" # Drive time + trigger waiting time | |
| - "{{ trigger.from_state.state not in invalid_states }}" | |
| - "{{ trigger.to_state.state not in invalid_states }}" | |
| - or: | |
| - and: | |
| - "{{ is_number(trigger.to_state.attributes.current_position | default(none)) }}" # Avoid status change from -unavailable- (common with Homematic) | |
| - "{{ is_number(trigger.from_state.attributes.current_position | default(none)) }}" | |
| - "{{ trigger.to_state.attributes.current_position != trigger.from_state.attributes.current_position }}" # Probably not necessary, otherwise the trigger would not have been activated. | |
| - and: | |
| - "{{ is_number(trigger.to_state.attributes.current_tilt_position | default(none)) }}" # Avoid status change from -unavailable- (common with Homematic) | |
| - "{{ is_number(trigger.from_state.attributes.current_tilt_position | default(none)) }}" | |
| - "{{ trigger.to_state.attributes.current_tilt_position != trigger.from_state.attributes.current_tilt_position }}" # Probably not necessary, otherwise the trigger would not have been activated. | |
| sequence: | |
| - variables: | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" | |
| - choose: | |
| - conditions: # Manually tilted | |
| - "{{ trigger.id == 't_manual_4' }}" | |
| sequence: | |
| - variables: | |
| update_values: | |
| manual: { a: 1, t: "{{ ts_now }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_manual_action | |
| - conditions: # Manually opened | |
| - "{{ is_up_enabled }}" | |
| - or: | |
| - "{{ in_open_position }}" | |
| - "{{ position_comparisons.current_above_open or current_position == open_position }}" | |
| - "{{ (is_awning and current_position == 0) or (not is_awning and current_position == 100) }}" | |
| sequence: | |
| - variables: | |
| update_values: | |
| open: { a: 1, t: "{{ ts_now }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 1, t: "{{ ts_now }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_manual_action | |
| - conditions: # Manually ventilated | |
| - "{{ is_ventilation_enabled }}" | |
| - "{{ in_ventilate_position }}" | |
| - "{{ not in_open_position }}" # If ventilate_position and open_position are too close together, the ventilation-status should not be assigned. | |
| sequence: | |
| - variables: | |
| update_values: | |
| open: { a: 0, t: "{{ helper_json.open.t }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 1, t: "{{ ts_now }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 1, t: "{{ ts_now }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_manual_action | |
| - conditions: # Manually shaded | |
| - "{{ is_shading_enabled }}" | |
| - "{{ in_shading_position }}" | |
| sequence: | |
| - variables: | |
| update_values: | |
| open: { a: 0, t: "{{ helper_json.open.t }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 1, t: "{{ ts_now }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 1, t: "{{ ts_now }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_manual_action | |
| - conditions: # Manually closed | |
| - "{{ is_down_enabled }}" | |
| - "{{ not in_shading_position }}" | |
| - or: | |
| - "{{ in_close_position }}" | |
| - "{{ position_comparisons.current_below_close or current_position == close_position }}" | |
| - "{{ (is_awning and current_position == 100) or (not is_awning and current_position == 0) }}" | |
| sequence: | |
| - variables: | |
| update_values: | |
| open: { a: 0, t: "{{ helper_json.open.t }}" } | |
| close: { a: 1, t: "{{ ts_now }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 1, t: "{{ ts_now }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_manual_action | |
| - conditions: # Defined positions cannot be assigned | |
| - not: | |
| - "{{ is_up_enabled and in_open_position }}" | |
| - "{{ is_down_enabled and in_close_position }}" | |
| - "{{ is_ventilation_enabled and in_ventilate_position }}" | |
| - "{{ is_shading_enabled and in_shading_position }}" | |
| sequence: | |
| - variables: | |
| update_values: | |
| open: { a: 0, t: "{{ helper_json.open.t }}" } | |
| close: { a: 0, t: "{{ helper_json.close.t }}" } | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| vpart: { a: 0, t: "{{ helper_json.vpart.t }}" } | |
| vfull: { a: 0, t: "{{ helper_json.vfull.t }}" } | |
| manual: { a: 1, t: "{{ ts_now }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_manual_action | |
| ############################################################ | |
| # ANCHOR (12): RESET MANUAL DETECTION | |
| ############################################################ | |
| - alias: "Reset manual detection" | |
| conditions: | |
| - "{{ trigger.id is defined }}" | |
| - "{{ not is_reset_disabled }}" | |
| - "{{ trigger.id in ['t_manual_2', 't_manual_3'] }}" | |
| sequence: | |
| - variables: | |
| ts_now: "{{ as_timestamp(now()) | round(0) }}" # Note: ts_now must be evaluated at runtime after delays, not at blueprint load time | |
| update_values: | |
| manual: { a: 0, t: "{{ ts_now }}" } | |
| t: "{{ ts_now }}" | |
| - *helper_update | |
| - choose: [] | |
| default: !input auto_override_reset_action | |
| ############################################################ | |
| # ANCHOR (13): RETURN TO BACKGROUND STATE AFTER FORCE DISABLE | |
| ############################################################ | |
| - alias: "Return to background position after force disable" | |
| conditions: | |
| - "{{ trigger.id == 't_force_disabled' }}" | |
| - "{{ enable_background_state_tracking }}" | |
| - "{{ is_status_helper_enabled }}" | |
| # All force functions must now be disabled | |
| - "{{ force_disabled.up }}" | |
| - "{{ force_disabled.down }}" | |
| - "{{ force_disabled.ventilate }}" | |
| - "{{ force_disabled.shading_start }}" | |
| sequence: | |
| - delay: | |
| seconds: "{{ range(drive_delay_fix|int(0), drive_delay_fix|int(0) + drive_delay_random|int(0) +1) | random }}" | |
| - variables: | |
| # Read current target state from helper | |
| background_is_open: "{{ helper_status.open }}" | |
| background_is_closed: "{{ helper_status.closed }}" | |
| background_is_shaded: "{{ helper_status.shaded }}" | |
| background_is_vpart: "{{ helper_status.vpart }}" | |
| background_is_vfull: "{{ helper_status.vfull }}" | |
| - choose: | |
| # Return to OPEN position | |
| - conditions: | |
| - "{{ background_is_open }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_up_action_before | |
| - variables: | |
| target_position: !input open_position | |
| target_tilt_position: "{{ open_tilt_position | int }}" | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - choose: [] | |
| default: !input auto_up_action | |
| # Return to CLOSE position | |
| - conditions: | |
| - "{{ background_is_closed }}" | |
| - "{{ not background_is_vpart }}" | |
| - "{{ not background_is_vfull }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_down_action_before | |
| - variables: | |
| target_position: !input close_position | |
| target_tilt_position: "{{ close_tilt_position | int }}" | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - choose: [] | |
| default: !input auto_down_action | |
| # Return to SHADING position | |
| - conditions: | |
| - "{{ background_is_shaded }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_shading_start_action_before | |
| - variables: | |
| target_position: !input shading_position | |
| target_tilt_position: "{{ shading_tilt_position | int }}" | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - choose: [] | |
| default: !input auto_shading_start_action | |
| # Return to VENTILATION position (partial) | |
| - conditions: | |
| - "{{ background_is_vpart }}" | |
| sequence: | |
| - choose: [] | |
| default: !input auto_ventilate_action_before | |
| - variables: | |
| target_position: !input ventilate_position | |
| target_tilt_position: !input ventilate_tilt_position | |
| - *cover_move_action | |
| - *tilt_move_action | |
| - choose: [] | |
| default: !input auto_ventilate_action | |
| default: [] | |
| ############################################################ | |
| # ANCHOR (14): RESET SHADING STATUS AT MIDNIGHT | |
| ############################################################ | |
| - alias: "Reset shading status that is no longer required - but still saved" | |
| conditions: | |
| - "{{ trigger.id is defined }}" | |
| - "{{ trigger.id == 't_shading_reset' }}" | |
| - "{{ is_status_helper_enabled }}" | |
| - or: | |
| - "{{ helper_status.shaded }}" | |
| - "{{ helper_status.shading_start_pending }}" # Also clear pending status | |
| - "{{ helper_status.shading_end_pending }}" # Also clear end‑pending status | |
| sequence: | |
| - delay: | |
| seconds: "{{ (range(0, 60) | random | int) }}" | |
| - variables: | |
| update_values: | |
| shading: { a: 0, t: "{{ helper_json.shading.t }}", p: 0, q: 0 } | |
| t: "{{ as_timestamp(now()) | round(0) }}" | |
| - *helper_update | |
| ############################################################ | |
| # DEFAULT BRANCH - CONFIG CHECK | |
| ############################################################ | |
| default: | |
| - if: | |
| - "{{ check_config }}" | |
| then: | |
| - repeat: | |
| for_each: | |
| # ========== TIME CONFIGURATION CHECKS ========== | |
| - condition: "{{ not is_time_control_disabled and today_at(time_up_early) >= today_at(time_up_late) }}" | |
| message: "time_up_early should be earlier than time_up_late" | |
| - condition: "{{ not is_time_control_disabled and today_at(time_up_early_non_workday) >= today_at(time_up_late_non_workday) }}" | |
| message: "time_up_early_non_workday should be earlier than time_up_late_non_workday" | |
| - condition: "{{ not is_time_control_disabled and today_at(time_down_early) >= today_at(time_down_late) }}" | |
| message: "time_down_early should be earlier than time_down_late" | |
| - condition: "{{ not is_time_control_disabled and today_at(time_down_early_non_workday) >= today_at(time_down_late_non_workday) }}" | |
| message: "time_down_early_non_workday should be earlier than time_down_late_non_workday" | |
| # ========== SHADING THRESHOLD CHECKS ========== | |
| - condition: "{{ shading_azimuth_start >= shading_azimuth_end }}" | |
| message: "shading_azimuth_start should be lower than shading_azimuth_end" | |
| - condition: "{{ shading_elevation_min >= shading_elevation_max }}" | |
| message: "shading_elevation_min should be lower than shading_elevation_max" | |
| - condition: "{{ shading_sun_brightness_start <= shading_sun_brightness_end }}" | |
| message: "shading_sun_brightness_start should be higher than shading_sun_brightness_end" | |
| # ========== POSITION VALUE CHECKS ========== | |
| # Generic position uniqueness check (works for both types) | |
| - condition: "{{ [in_open_position, in_close_position, in_shading_position, in_ventilate_position].count(True) > 1 }}" | |
| message: "Position values overlap when tolerance is applied. Ensure all positions are unique considering tolerance settings" | |
| # Blind-specific checks | |
| - condition: >- | |
| {{ | |
| not is_awning and | |
| not is_cover_tilt_enabled_and_possible and | |
| (open_position - position_tolerance) <= (close_position + position_tolerance) | |
| }} | |
| message: "For blinds: open_position ({{ open_position }}%) must be higher than close_position ({{ close_position }}%)" | |
| - condition: >- | |
| {{ | |
| not is_awning and | |
| not is_cover_tilt_enabled_and_possible and | |
| (open_position - position_tolerance) <= (ventilate_position + position_tolerance) | |
| }} | |
| message: "For blinds: open_position ({{ open_position }}%) must be higher than ventilate_position ({{ ventilate_position }}%)" | |
| - condition: >- | |
| {{ | |
| not is_awning and | |
| not is_cover_tilt_enabled_and_possible and | |
| (close_position + position_tolerance) >= (ventilate_position - position_tolerance) | |
| }} | |
| message: "For blinds: close_position ({{ close_position }}%) must be lower than ventilate_position ({{ ventilate_position }}%)" | |
| - condition: >- | |
| {{ | |
| not is_awning and | |
| not is_cover_tilt_enabled_and_possible and | |
| (shading_position - position_tolerance) <= (close_position + position_tolerance) | |
| }} | |
| message: "For blinds: shading_position ({{ shading_position }}%) must be higher than close_position ({{ close_position }}%)" | |
| - condition: >- | |
| {{ | |
| not is_awning and | |
| not is_cover_tilt_enabled_and_possible and | |
| (shading_position + position_tolerance) >= (open_position - position_tolerance) | |
| }} | |
| message: "For blinds: shading_position ({{ shading_position }}%) must be lower than open_position ({{ open_position }}%)" | |
| # Awning-specific checks | |
| - condition: >- | |
| {{ | |
| is_awning and | |
| not is_cover_tilt_enabled_and_possible and | |
| (open_position + position_tolerance) >= (close_position - position_tolerance) | |
| }} | |
| message: "For awnings: open_position ({{ open_position }}%) must be lower than close_position ({{ close_position }}%). Awnings: 0%=retracted, 100%=extended" | |
| - condition: >- | |
| {{ | |
| is_awning and | |
| not is_cover_tilt_enabled_and_possible and | |
| (open_position + position_tolerance) >= (ventilate_position - position_tolerance) | |
| }} | |
| message: "For awnings: open_position ({{ open_position }}%) must be lower than ventilate_position ({{ ventilate_position }}%)" | |
| - condition: >- | |
| {{ | |
| is_awning and | |
| not is_cover_tilt_enabled_and_possible and | |
| (close_position - position_tolerance) <= (ventilate_position + position_tolerance) | |
| }} | |
| message: "For awnings: close_position ({{ close_position }}%) must be higher than ventilate_position ({{ ventilate_position }}%)" | |
| - condition: >- | |
| {{ | |
| is_awning and | |
| not is_cover_tilt_enabled_and_possible and | |
| (shading_position + position_tolerance) >= (close_position - position_tolerance) | |
| }} | |
| message: "For awnings: shading_position ({{ shading_position }}%) must be lower than close_position ({{ close_position }}%). For shading, awning should be extended" | |
| - condition: >- | |
| {{ | |
| is_awning and | |
| not is_cover_tilt_enabled_and_possible and | |
| (shading_position - position_tolerance) <= (open_position + position_tolerance) | |
| }} | |
| message: "For awnings: shading_position ({{ shading_position }}%) must be higher than open_position ({{ open_position }}%)" | |
| # Position value range checks (apply to both types) | |
| - condition: "{{ open_position < 0 or open_position > 100 }}" | |
| message: "open_position ({{ open_position }}%) must be between 0 and 100" | |
| - condition: "{{ close_position < 0 or close_position > 100 }}" | |
| message: "close_position ({{ close_position }}%) must be between 0 and 100" | |
| - condition: "{{ shading_position < 0 or shading_position > 100 }}" | |
| message: "shading_position ({{ shading_position }}%) must be between 0 and 100" | |
| - condition: "{{ ventilate_position < 0 or ventilate_position > 100 }}" | |
| message: "ventilate_position ({{ ventilate_position }}%) must be between 0 and 100" | |
| # ========== ENTITY ATTRIBUTE & STATE CHECKS ========== | |
| - condition: "{{ state_attr(blind, 'current_position') is none }}" | |
| message: "Cover entity is missing the required 'current_position' attribute" | |
| - condition: "{{ state_attr(default_sun_sensor, 'elevation') is none }}" | |
| message: "Sun sensor is missing the required 'elevation' attribute" | |
| - condition: "{{ state_attr(default_sun_sensor, 'azimuth') is none }}" | |
| message: "Sun sensor is missing the required 'azimuth' attribute" | |
| - condition: "{{ resident_sensor != [] and states(resident_sensor) not in ['false', 'off', 'true', 'on'] }}" | |
| message: "resident_sensor has invalid state '{{ states(resident_sensor) }}'. Must be: on, off, true, or false" | |
| - condition: "{{ is_brightness_enabled and (default_brightness_sensor == [] or not is_number(states(default_brightness_sensor))) }}" | |
| message: "Brightness sensor not configured or has non-numeric state" | |
| # ========== HELPER CONFIGURATION CHECKS ========== | |
| - condition: "{{ 'cover_helper_enabled' in cover_status_options and state_attr(cover_status_helper, 'max') < 254 }}" | |
| message: "Cover status helper must have minimum length of 254 characters (currently: {{ state_attr(cover_status_helper, 'max') }})" | |
| - condition: "{{ (is_shading_enabled or is_ventilation_enabled) and not is_status_helper_enabled }}" | |
| message: "Shading and ventilation features require a cover status helper. Please configure one" | |
| # ========== TILT FEATURE CHECKS ========== | |
| - condition: "{{ is_cover_tilt_enabled and state_attr(blind, 'current_tilt_position') is none }}" | |
| message: "Tilt position control enabled but cover does not support 'current_tilt_position' attribute. Disable tilt features or check cover integration" | |
| - condition: "{{ is_cover_tilt_enabled and tilt_delay > 60 }}" | |
| message: "Tilt delay ({{ tilt_delay }}s) is very long. Recommended: 10 seconds maximum" | |
| # ========== TIMING VALIDATION CHECKS ========== | |
| - condition: "{{ drive_time < 5 }}" | |
| message: "Cover drive time ({{ drive_time }}s) is too short. Minimum recommended: 5 seconds" | |
| - condition: "{{ drive_time > 300 }}" | |
| message: "Cover drive time ({{ drive_time }}s) is very long. Maximum recommended: 300 seconds (5 minutes)" | |
| - condition: "{{ drive_delay_fix + drive_delay_random > 600 }}" | |
| message: "Combined drive delay ({{ drive_delay_fix + drive_delay_random }}s) is very high. May slow automation response" | |
| - condition: "{{ contact_delay_trigger > 10 }}" | |
| message: "Contact trigger delay ({{ contact_delay_trigger }}s) is very long. May slow window/door response" | |
| - condition: "{{ is_ventilation_enabled and contact_delay_status > 10 }}" | |
| message: "Contact status delay ({{ contact_delay_status }}s) is very long. May slow handle position changes" | |
| # ========== CONTACT SENSOR CHECKS ========== | |
| - condition: >- | |
| {{ | |
| is_ventilation_enabled and | |
| contact_window_tilted not in [[], none] and | |
| contact_window_opened not in [[], none] and | |
| contact_window_tilted == contact_window_opened | |
| }} | |
| message: "Contact sensors for tilted and opened window are the same. Use different sensors" | |
| # ========== WORKDAY SENSOR CHECKS ========== | |
| - condition: >- | |
| {{ | |
| workday_sensor_today not in [[], none] and | |
| states(workday_sensor_today) not in ['on', 'off', 'unknown', 'unavailable'] | |
| }} | |
| message: "Workday sensor today has invalid state '{{ states(workday_sensor_today) }}'. Expected: on or off" | |
| - condition: >- | |
| {{ | |
| workday_sensor_tomorrow not in [[], none] and | |
| states(workday_sensor_tomorrow) not in ['on', 'off', 'unknown', 'unavailable'] | |
| }} | |
| message: "Workday sensor tomorrow has invalid state '{{ states(workday_sensor_tomorrow) }}'. Expected: on or off" | |
| # ========== FORCE ENTITY CHECKS ========== | |
| - condition: >- | |
| {{ | |
| auto_up_force not in [[], none] and | |
| states(auto_up_force) not in ['on', 'off', 'true', 'false', 'unknown', 'unavailable'] | |
| }} | |
| message: "Force Open entity has invalid state '{{ states(auto_up_force) }}'. Must be binary: on/off" | |
| - condition: >- | |
| {{ | |
| auto_down_force not in [[], none] and | |
| states(auto_down_force) not in ['on', 'off', 'true', 'false', 'unknown', 'unavailable'] | |
| }} | |
| message: "Force Close entity has invalid state '{{ states(auto_down_force) }}'. Must be binary: on/off" | |
| - condition: >- | |
| {{ | |
| auto_ventilate_force not in [[], none] and | |
| states(auto_ventilate_force) not in ['on', 'off', 'true', 'false', 'unknown', 'unavailable'] | |
| }} | |
| message: "Force Ventilate entity has invalid state '{{ states(auto_ventilate_force) }}'. Must be binary: on/off" | |
| - condition: >- | |
| {{ | |
| auto_shading_start_force not in [[], none] and | |
| states(auto_shading_start_force) not in ['on', 'off', 'true', 'false', 'unknown', 'unavailable'] | |
| }} | |
| message: "Force Shading entity has invalid state '{{ states(auto_shading_start_force) }}'. Must be binary: on/off" | |
| - condition: >- | |
| {{ | |
| [ | |
| auto_up_force not in [[], none] and states(auto_up_force) in ['on', 'true'], | |
| auto_down_force not in [[], none] and states(auto_down_force) in ['on', 'true'], | |
| auto_ventilate_force not in [[], none] and states(auto_ventilate_force) in ['on', 'true'], | |
| auto_shading_start_force not in [[], none] and states(auto_shading_start_force) in ['on', 'true'] | |
| ].count(true) > 1 | |
| }} | |
| message: "CRITICAL: Multiple force entities are active. Only one force entity can be active at a time. Deactivate conflicting force entities" | |
| # ========== SUN SENSOR CHECKS ========== | |
| - condition: >- | |
| {{ | |
| default_sun_sensor not in [[], none] and | |
| states(default_sun_sensor) in ['unknown', 'unavailable'] | |
| }} | |
| message: "Sun sensor '{{ default_sun_sensor }}' is unavailable. Check sun integration is enabled" | |
| # ========== RESET OVERRIDE CHECKS ========== | |
| - condition: "{{ is_reset_timeout and reset_override_timeout < 1 }}" | |
| message: "Override timeout ({{ reset_override_timeout }}min) is very short. Overrides reset almost immediately" | |
| - condition: "{{ is_reset_timeout and reset_override_timeout > 1440 }}" | |
| message: "Override timeout ({{ reset_override_timeout }}min = {{ (reset_override_timeout / 60) | round(1) }}h) is very long" | |
| # ========== SHADING START RETRY CHECKS ========== | |
| - condition: "{{ shading_start_max_duration > 0 and shading_start_max_duration < 600 }}" | |
| message: "Shading start timeout ({{ shading_start_max_duration }}s) is too short. Minimum: 600s (10 minutes)" | |
| - condition: "{{ shading_start_max_duration > 14400 }}" | |
| message: "Shading start timeout ({{ shading_start_max_duration }}s = {{ (shading_start_max_duration / 3600) | round(1) }}h) is very long. Max: 4 hours" | |
| - condition: "{{ shading_start_max_duration == 0 }}" | |
| message: "Shading start retry disabled (0). May miss shading opportunities. Recommended: 3600-7200s" | |
| # ========== SHADING END RETRY CHECKS ========== | |
| - condition: "{{ shading_end_max_duration > 0 and shading_end_max_duration < 600 }}" | |
| message: "Shading end timeout ({{ shading_end_max_duration }}s) is too short. Minimum: 600s (10 minutes)" | |
| - condition: "{{ shading_end_max_duration > 14400 }}" | |
| message: "Shading end timeout ({{ shading_end_max_duration }}s = {{ (shading_end_max_duration / 3600) | round(1) }}h) is very long. Max: 4 hours" | |
| - condition: "{{ shading_end_max_duration == 0 }}" | |
| message: "Shading end retry disabled (0). May get stuck if conditions fluctuate. Recommended: 3600-7200s" | |
| # ========== FORECAST CONFIGURATION CHECKS ========== | |
| - condition: "{{ forecast_source.use_sensor and shading_weather_conditions | length > 0 }}" | |
| message: "Weather conditions configured with temperature sensor (not supported). Use weather entity or remove conditions" | |
| - condition: "{{ forecast_source.use_weather and states(shading_forecast_sensor) in ['unknown', 'unavailable'] }}" | |
| message: "Weather entity '{{ shading_forecast_sensor }}' is unavailable. Check weather integration" | |
| - condition: "{{ forecast_source.use_sensor and states(shading_forecast_temp_sensor) in ['unknown', 'unavailable'] }}" | |
| message: "Forecast temperature sensor '{{ shading_forecast_temp_sensor }}' is unavailable" | |
| - condition: "{{ forecast_source.use_sensor and not is_number(states(shading_forecast_temp_sensor)) }}" | |
| message: "Forecast temperature sensor '{{ shading_forecast_temp_sensor }}' is non-numeric: '{{ states(shading_forecast_temp_sensor) }}'" | |
| - condition: "{{ shading_forecast_temp != [] and not forecast_source.use_sensor and not forecast_source.use_weather }}" | |
| message: "Forecast temperature configured ({{ shading_forecast_temp }}°C) but no forecast source selected. Configure weather entity or temperature sensor" | |
| - condition: "{{ forecast_source.use_sensor and forecast_source.use_weather }}" | |
| message: "Both weather entity and temperature sensor configured. Temperature sensor takes priority (weather entity ignored)" | |
| # ========== SHADING CONDITIONS CHECKS ========== | |
| - condition: >- | |
| {{ | |
| is_shading_enabled and | |
| (shading_conditions_start_and | count + shading_conditions_start_or | count) == 0 | |
| }} | |
| message: "ERROR: Shading enabled but no START conditions selected. Shading will never activate" | |
| - condition: >- | |
| {{ | |
| is_shading_enabled and | |
| (shading_conditions_end_and | count + shading_conditions_end_or | count) == 0 | |
| }} | |
| message: "WARNING: No END conditions selected. Shading only ends at midnight reset" | |
| - condition: >- | |
| {{ | |
| (shading_conditions_start_and + shading_conditions_start_or) | unique | count < | |
| (shading_conditions_start_and | count + shading_conditions_start_or | count) | |
| }} | |
| message: "ERROR: Same condition in both START AND and OR lists. Each condition can only be in one list" | |
| - condition: >- | |
| {{ | |
| (shading_conditions_end_and + shading_conditions_end_or) | unique | count < | |
| (shading_conditions_end_and | count + shading_conditions_end_or | count) | |
| }} | |
| message: "ERROR: Same condition in both END AND and OR lists. Each condition can only be in one list" | |
| # ========== SHADING START CONDITION CONFIGURATION ========== | |
| - condition: >- | |
| {{ | |
| 'cond_azimuth' in (shading_conditions_start_and + shading_conditions_start_or) and | |
| default_sun_sensor == [] | |
| }} | |
| message: "Azimuth START condition enabled but sun sensor not configured. Condition will be ignored" | |
| - condition: >- | |
| {{ | |
| 'cond_elevation' in (shading_conditions_start_and + shading_conditions_start_or) and | |
| default_sun_sensor == [] | |
| }} | |
| message: "Elevation START condition enabled but sun sensor not configured. Condition will be ignored" | |
| - condition: >- | |
| {{ | |
| 'cond_brightness' in (shading_conditions_start_and + shading_conditions_start_or) and | |
| shading_brightness_sensor == [] | |
| }} | |
| message: "Brightness START condition enabled but brightness sensor not configured. Condition will be ignored" | |
| - condition: >- | |
| {{ | |
| 'cond_temp1' in (shading_conditions_start_and + shading_conditions_start_or) and | |
| shading_temperatur_sensor1 == [] | |
| }} | |
| message: "Temperature 1 START condition enabled but sensor not configured. Condition will be ignored" | |
| - condition: >- | |
| {{ | |
| 'cond_temp2' in (shading_conditions_start_and + shading_conditions_start_or) and | |
| shading_temperatur_sensor2 == [] | |
| }} | |
| message: "Temperature 2 START condition enabled but sensor not configured. Condition will be ignored" | |
| - condition: >- | |
| {{ | |
| 'cond_forecast' in (shading_conditions_start_and + shading_conditions_start_or) and | |
| shading_forecast_temp == [] | |
| }} | |
| message: "Forecast START condition enabled but threshold not configured. Condition will be ignored" | |
| # ========== SHADING END CONDITION CONFIGURATION ========== | |
| - condition: >- | |
| {{ | |
| 'cond_azimuth' in (shading_conditions_end_and + shading_conditions_end_or) and | |
| default_sun_sensor == [] | |
| }} | |
| message: "Azimuth END condition enabled but sun sensor not configured" | |
| - condition: >- | |
| {{ | |
| 'cond_elevation' in (shading_conditions_end_and + shading_conditions_end_or) and | |
| default_sun_sensor == [] | |
| }} | |
| message: "Elevation END condition enabled but sun sensor not configured" | |
| - condition: >- | |
| {{ | |
| 'cond_brightness' in (shading_conditions_end_and + shading_conditions_end_or) and | |
| shading_brightness_sensor == [] | |
| }} | |
| message: "Brightness END condition enabled but brightness sensor not configured" | |
| - condition: >- | |
| {{ | |
| 'cond_temp1' in (shading_conditions_end_and + shading_conditions_end_or) and | |
| shading_temperatur_sensor1 == [] | |
| }} | |
| message: "Temperature 1 END condition enabled but sensor not configured" | |
| - condition: >- | |
| {{ | |
| 'cond_temp2' in (shading_conditions_end_and + shading_conditions_end_or) and | |
| shading_temperatur_sensor2 == [] | |
| }} | |
| message: "Temperature 2 END condition enabled but sensor not configured" | |
| - condition: >- | |
| {{ | |
| 'cond_forecast' in (shading_conditions_end_and + shading_conditions_end_or) and | |
| shading_forecast_temp == [] | |
| }} | |
| message: "Forecast END condition enabled but threshold not configured" | |
| # ========== DYNAMIC SUN ELEVATION SENSOR CHECKS ========== | |
| - condition: >- | |
| {{ | |
| sun_elevation_up_sensor != [] and | |
| states(sun_elevation_up_sensor) in ['unknown', 'unavailable'] | |
| }} | |
| message: "Dynamic sun elevation up sensor '{{ sun_elevation_up_sensor }}' is unavailable. Check sensor configuration" | |
| - condition: >- | |
| {{ | |
| sun_elevation_up_sensor != [] and | |
| states(sun_elevation_up_sensor) not in invalid_states and | |
| not is_number(states(sun_elevation_up_sensor)) | |
| }} | |
| message: "Dynamic sun elevation up sensor '{{ sun_elevation_up_sensor }}' has non-numeric state: '{{ states(sun_elevation_up_sensor) }}'" | |
| - condition: >- | |
| {{ | |
| sun_elevation_up_sensor != [] and | |
| is_number(states(sun_elevation_up_sensor)) and | |
| (states(sun_elevation_up_sensor) | float < -90 or states(sun_elevation_up_sensor) | float > 90) | |
| }} | |
| message: "Dynamic sun elevation up sensor value ({{ states(sun_elevation_up_sensor) }}°) is outside valid range (-90° to 90°)" | |
| - condition: >- | |
| {{ | |
| sun_elevation_down_sensor != [] and | |
| states(sun_elevation_down_sensor) in ['unknown', 'unavailable'] | |
| }} | |
| message: "Dynamic sun elevation down sensor '{{ sun_elevation_down_sensor }}' is unavailable. Check sensor configuration" | |
| - condition: >- | |
| {{ | |
| sun_elevation_down_sensor != [] and | |
| states(sun_elevation_down_sensor) not in invalid_states and | |
| not is_number(states(sun_elevation_down_sensor)) | |
| }} | |
| message: "Dynamic sun elevation down sensor '{{ sun_elevation_down_sensor }}' has non-numeric state: '{{ states(sun_elevation_down_sensor) }}'" | |
| - condition: >- | |
| {{ | |
| sun_elevation_down_sensor != [] and | |
| is_number(states(sun_elevation_down_sensor)) and | |
| (states(sun_elevation_down_sensor) | float < -90 or states(sun_elevation_down_sensor) | float > 90) | |
| }} | |
| message: "Dynamic sun elevation down sensor value ({{ states(sun_elevation_down_sensor) }}°) is outside valid range (-90° to 90°)" | |
| - condition: >- | |
| {{ | |
| sun_elevation_up_sensor != [] and | |
| sun_elevation_down_sensor != [] and | |
| is_number(states(sun_elevation_up_sensor)) and | |
| is_number(states(sun_elevation_down_sensor)) and | |
| (states(sun_elevation_up_sensor) | float) <= (states(sun_elevation_down_sensor) | float) | |
| }} | |
| message: "Dynamic sun elevation up sensor ({{ states(sun_elevation_up_sensor) }}°) should be higher than down sensor ({{ states(sun_elevation_down_sensor) }}°)" | |
| - condition: >- | |
| {{ | |
| sun_elevation_up_sensor != [] and | |
| sun_elevation_down_sensor == [] and | |
| sun_elevation_up <= sun_elevation_down | |
| }} | |
| message: "WARNING: Using dynamic sensor for UP but fixed value for DOWN. Thresholds may be inconsistent (up={{ sun_elevation_up_sensor }}°, down={{ sun_elevation_down }}°)" | |
| - condition: >- | |
| {{ | |
| sun_elevation_up_sensor == [] and | |
| sun_elevation_down_sensor != [] and | |
| sun_elevation_up <= sun_elevation_down | |
| }} | |
| message: "WARNING: Using dynamic sensor for DOWN but fixed value for UP. Thresholds may be inconsistent (up={{ sun_elevation_up }}°, down={{ sun_elevation_down_sensor }}°)" | |
| # ========== CALENDAR CONFIGURATION CHECKS ========== | |
| - condition: >- | |
| {{ | |
| is_calendar_enabled and | |
| calendar_entity == [] | |
| }} | |
| message: "Calendar control enabled but no calendar entity selected" | |
| - condition: >- | |
| {{ | |
| is_calendar_enabled and | |
| calendar_entity != [] and | |
| states(calendar_entity) in ['unknown', 'unavailable'] | |
| }} | |
| message: "Calendar entity '{{ calendar_entity }}' is unavailable. Check calendar integration" | |
| - condition: >- | |
| {{ | |
| is_calendar_enabled and | |
| calendar_entity != [] and | |
| state_attr(calendar_entity, 'message') is none | |
| }} | |
| message: "Calendar entity '{{ calendar_entity }}' does not have 'message' attribute. Ensure it's a valid calendar entity" | |
| - condition: >- | |
| {{ | |
| is_calendar_enabled and | |
| calendar_open_title == calendar_close_title and | |
| calendar_open_title != '' | |
| }} | |
| message: "Open and Close event titles are identical ('{{ calendar_open_title }}'). Use different titles" | |
| - condition: >- | |
| {{ | |
| is_calendar_enabled and | |
| (calendar_open_title | trim == '' or calendar_close_title | trim == '') | |
| }} | |
| message: "Calendar event titles cannot be empty. Configure 'Open Event Title' and 'Close Event Title'" | |
| - condition: >- | |
| {{ | |
| is_calendar_enabled and | |
| calendar_events is defined and | |
| calendar_entity in calendar_events and | |
| calendar_open_event.found and | |
| calendar_close_event.found and | |
| calendar_open_end is not none and | |
| calendar_close_start is not none and | |
| calendar_open_end > calendar_close_start | |
| }} | |
| message: "WARNING: Open event ends ({{ calendar_open_end | timestamp_custom('%H:%M') }}) after Close event starts ({{ calendar_close_start | timestamp_custom('%H:%M') }}). Events should not overlap" | |
| - condition: >- | |
| {{ | |
| is_calendar_enabled and | |
| calendar_events is defined and | |
| calendar_entity in calendar_events and | |
| not calendar_open_event.found and | |
| not calendar_close_event.found | |
| }} | |
| message: "WARNING: No matching calendar events found today. Check event titles: Open='{{ calendar_open_title }}', Close='{{ calendar_close_title }}'" | |
| sequence: | |
| - if: | |
| - "{{ repeat.item.condition }}" | |
| then: | |
| - action: system_log.write | |
| data: | |
| level: "{{ check_config_debuglevel }}" | |
| message: "CCA Config issue: {{ repeat.item.message }} - {{ this.entity_id }}" | |
| logger: "blueprints.hvorragend.cca" | |
| # - stop: "Stopping the automation - DEFAULT" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment