Last active
December 6, 2025 18:01
-
-
Save Belphemur/37384d2027c5b207caf827df488f6800 to your computer and use it in GitHub Desktop.
Home Assistant: Automatically controls roof heating cables based on temperature and weather conditions.
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: "Smart Roof Heating - Restart-Proof Duty Cycle" | |
| description: | | |
| Redesigned heating cable controller with complete restart recovery. | |
| Automatically detects and recovers duty cycle state after Home Assistant restart. | |
| **Restart Recovery Features:** | |
| • Detects if duty cycle was active before restart | |
| • Automatically restarts timer with appropriate duration | |
| • Maintains cable state consistency | |
| • Handles all restart scenarios safely | |
| **Architecture:** | |
| • Weather triggers → Start duty cycle (if not running) | |
| • Duty cycle helper → Prevents multiple duty cycle starts | |
| • Only duty cycle → Controls cable ON/OFF switching | |
| • Only duty cycle → Stops itself based on safety temperatures | |
| • Startup recovery → Restores duty cycle after restart | |
| • Forecast → Next hour only for activation decisions | |
| domain: automation | |
| author: "Home Assistant Community" | |
| homeassistant: | |
| min_version: "2025.11.0" | |
| input: | |
| weather_entity: | |
| name: "Weather Entity" | |
| description: "Weather entity that provides forecast data" | |
| selector: | |
| entity: | |
| filter: | |
| domain: weather | |
| temperature_sensor: | |
| name: "Temperature Sensor" | |
| description: "Outdoor temperature sensor" | |
| selector: | |
| entity: | |
| filter: | |
| domain: sensor | |
| device_class: temperature | |
| precipitation_sensor: | |
| name: "Precipitation Type Sensor" | |
| description: "Sensor showing precipitation type (Snow, Rain, None, etc.)" | |
| selector: | |
| entity: | |
| filter: | |
| domain: sensor | |
| snow_rate_sensor: | |
| name: "Snow Rate Sensor" | |
| description: "Sensor showing snow precipitation rate in mm/h" | |
| selector: | |
| entity: | |
| filter: | |
| domain: sensor | |
| weather_condition_sensor: | |
| name: "Weather Condition Sensor" | |
| description: "Current weather condition sensor" | |
| selector: | |
| entity: | |
| filter: | |
| domain: sensor | |
| forecast_storage_helper: | |
| name: "Forecast Storage Helper" | |
| description: "Input text helper to store next hour forecast" | |
| selector: | |
| entity: | |
| filter: | |
| domain: input_text | |
| forecast_timestamp_helper: | |
| name: "Forecast Timestamp Helper" | |
| description: "Input datetime helper to track last forecast update" | |
| selector: | |
| entity: | |
| filter: | |
| domain: input_datetime | |
| duty_cycle_active_helper: | |
| name: "Duty Cycle Active Helper" | |
| description: "Input boolean helper to track if duty cycle is running" | |
| selector: | |
| entity: | |
| filter: | |
| domain: input_boolean | |
| duty_cycle_timer: | |
| name: "Duty Cycle Timer Helper" | |
| description: "Timer helper for duty cycling control" | |
| selector: | |
| entity: | |
| filter: | |
| domain: timer | |
| heating_cable_switch: | |
| name: "Heating Cable Switch" | |
| description: "Switch entity that controls the roof heating cable" | |
| selector: | |
| entity: | |
| filter: | |
| domain: switch | |
| notification_service: | |
| name: "Mobile Notification Service" | |
| description: "Mobile app notification service (e.g., mobile_app_pixel_8_pro)" | |
| selector: | |
| text: | |
| duty_cycle_on_duration: | |
| name: "Duty Cycle ON Duration" | |
| description: "How long to keep cable ON during duty cycle" | |
| default: 30 | |
| selector: | |
| number: | |
| min: 10 | |
| max: 120 | |
| unit_of_measurement: "minutes" | |
| duty_cycle_off_duration: | |
| name: "Duty Cycle OFF Duration" | |
| description: "How long to keep cable OFF during duty cycle" | |
| default: 30 | |
| selector: | |
| number: | |
| min: 10 | |
| max: 120 | |
| unit_of_measurement: "minutes" | |
| day_temperature_threshold: | |
| name: "Daytime Temperature Threshold" | |
| description: "Temperature below which duty cycle starts during day" | |
| default: 2 | |
| selector: | |
| number: | |
| min: -10 | |
| max: 10 | |
| unit_of_measurement: "°C" | |
| night_temperature_threshold: | |
| name: "Nighttime Temperature Threshold" | |
| description: "Temperature below which duty cycle starts at night" | |
| default: 4 | |
| selector: | |
| number: | |
| min: -10 | |
| max: 10 | |
| unit_of_measurement: "°C" | |
| safety_shutoff_high_temp: | |
| name: "Safety Shutoff Temperature (High)" | |
| description: "Temperature above which duty cycle stops" | |
| default: 10 | |
| selector: | |
| number: | |
| min: 5 | |
| max: 20 | |
| unit_of_measurement: "°C" | |
| safety_shutoff_low_temp: | |
| name: "Safety Shutoff Temperature (Low)" | |
| description: "Temperature below which duty cycle stops" | |
| default: -10 | |
| selector: | |
| number: | |
| min: -25 | |
| max: 0 | |
| unit_of_measurement: "°C" | |
| variables: | |
| weather_entity: !input weather_entity | |
| temperature_sensor: !input temperature_sensor | |
| precipitation_sensor: !input precipitation_sensor | |
| snow_rate_sensor: !input snow_rate_sensor | |
| weather_condition_sensor: !input weather_condition_sensor | |
| forecast_storage: !input forecast_storage_helper | |
| forecast_timestamp: !input forecast_timestamp_helper | |
| duty_cycle_active: !input duty_cycle_active_helper | |
| duty_timer: !input duty_cycle_timer | |
| heating_switch: !input heating_cable_switch | |
| notify_service: !input notification_service | |
| duty_on_minutes: !input duty_cycle_on_duration | |
| duty_off_minutes: !input duty_cycle_off_duration | |
| day_temp: !input day_temperature_threshold | |
| night_temp: !input night_temperature_threshold | |
| safety_high_temp: !input safety_shutoff_high_temp | |
| safety_low_temp: !input safety_shutoff_low_temp | |
| trigger: | |
| # ========== STARTUP RECOVERY TRIGGER ========== | |
| - platform: homeassistant | |
| event: start | |
| id: "startup_recovery" | |
| # Forecast data update (every 30 minutes) | |
| - platform: time_pattern | |
| minutes: "/30" | |
| id: "fetch_forecast" | |
| # Duty cycle timer finished | |
| - platform: event | |
| event_type: timer.finished | |
| event_data: | |
| entity_id: !input duty_cycle_timer | |
| id: "duty_cycle_timer_finished" | |
| # Weather condition triggers (for starting duty cycle) | |
| - platform: state | |
| entity_id: !input precipitation_sensor | |
| to: | |
| - "Snow" | |
| - "Snow and Rain" | |
| id: "weather_trigger" | |
| - platform: numeric_state | |
| entity_id: !input snow_rate_sensor | |
| above: 0 | |
| id: "weather_trigger" | |
| - platform: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: !input day_temperature_threshold | |
| id: "weather_trigger" | |
| - platform: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: !input night_temperature_threshold | |
| id: "weather_trigger" | |
| # Forecast data updates | |
| - platform: state | |
| entity_id: !input forecast_storage_helper | |
| id: "weather_trigger" | |
| condition: [] | |
| action: | |
| # Update forecast data if needed | |
| - if: | |
| - condition: or | |
| conditions: | |
| - condition: trigger | |
| id: "fetch_forecast" | |
| - condition: trigger | |
| id: "startup_recovery" | |
| - condition: template | |
| value_template: > | |
| {{ (now() - states[forecast_timestamp].last_changed).total_seconds() > 1800 }} | |
| then: | |
| # Get next hour forecast only | |
| - service: weather.get_forecasts | |
| data: | |
| type: hourly | |
| target: | |
| entity_id: "{{ weather_entity }}" | |
| response_variable: forecast_response | |
| # Store ONLY next hour forecast | |
| - service: input_text.set_value | |
| target: | |
| entity_id: "{{ forecast_storage }}" | |
| data: | |
| value: > | |
| {% if forecast_response and weather_entity in forecast_response %} | |
| {% set forecast = forecast_response[weather_entity] %} | |
| {% if forecast and 'forecast' in forecast and forecast.forecast|length > 0 %} | |
| {% set next_hour = forecast.forecast[0] %} | |
| {% if next_hour and 'condition' in next_hour %} | |
| {{ next_hour.condition }} | |
| {% else %} | |
| no_data | |
| {% endif %} | |
| {% else %} | |
| no_data | |
| {% endif %} | |
| {% else %} | |
| no_data | |
| {% endif %} | |
| # Update timestamp | |
| - service: input_datetime.set_datetime | |
| target: | |
| entity_id: "{{ forecast_timestamp }}" | |
| data: | |
| datetime: "{{ now().isoformat() }}" | |
| # Main control logic | |
| - choose: | |
| # ========== STARTUP RECOVERY (HIGHEST PRIORITY) ========== | |
| - conditions: | |
| - condition: trigger | |
| id: "startup_recovery" | |
| sequence: | |
| # Wait 30 seconds for all entities to be ready | |
| - delay: "00:00:30" | |
| # Check if duty cycle was active before restart | |
| - if: | |
| - condition: and | |
| conditions: | |
| # Duty cycle helper shows it was active | |
| - condition: state | |
| entity_id: !input duty_cycle_active_helper | |
| state: "on" | |
| # But timer is now idle (lost due to restart) | |
| - condition: state | |
| entity_id: !input duty_cycle_timer | |
| state: "idle" | |
| then: | |
| # Check if conditions still require heating | |
| - if: | |
| - condition: and | |
| conditions: | |
| # Temperature is within safe operating range | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| above: !input safety_shutoff_low_temp | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: !input safety_shutoff_high_temp | |
| # Temperature check (day/night aware) | |
| - condition: or | |
| conditions: | |
| # Daytime threshold | |
| - condition: and | |
| conditions: | |
| - condition: sun | |
| after: sunrise | |
| before: sunset | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: !input day_temperature_threshold | |
| # Nighttime threshold | |
| - condition: and | |
| conditions: | |
| - condition: sun | |
| after: sunset | |
| before: sunrise | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: !input night_temperature_threshold | |
| # Weather conditions (current OR next hour) | |
| - condition: or | |
| conditions: | |
| # Current snow conditions | |
| - condition: state | |
| entity_id: !input precipitation_sensor | |
| state: | |
| - "Snow" | |
| - "Snow and Rain" | |
| - condition: numeric_state | |
| entity_id: !input snow_rate_sensor | |
| above: 0 | |
| - condition: state | |
| entity_id: !input weather_condition_sensor | |
| state: | |
| - "snowy" | |
| - "snowy-rainy" | |
| - "hail" | |
| # Next hour forecast shows snow | |
| - condition: template | |
| value_template: > | |
| {% set next_hour = states(forecast_storage) %} | |
| {% if next_hour not in ['unavailable', 'unknown', 'no_data', ''] %} | |
| {{ 'snow' in next_hour.lower() or 'sleet' in next_hour.lower() }} | |
| {% else %} | |
| false | |
| {% endif %} | |
| # Freezing rain | |
| - condition: and | |
| conditions: | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: 0 | |
| - condition: state | |
| entity_id: !input weather_condition_sensor | |
| state: | |
| - "rainy" | |
| - "pouring" | |
| then: | |
| # RESTART DUTY CYCLE - Determine which phase to resume | |
| - if: | |
| - condition: state | |
| entity_id: !input heating_cable_switch | |
| state: "on" | |
| then: | |
| # Cable is ON, restart ON timer | |
| - service: timer.start | |
| target: | |
| entity_id: "{{ duty_timer }}" | |
| data: | |
| duration: "00:{{ duty_on_minutes }}:00" | |
| - service: persistent_notification.create | |
| data: | |
| notification_id: "roof_heating_status" | |
| title: "🔄 Roof Heating - DUTY CYCLE RECOVERED" | |
| message: | | |
| **Status:** Duty cycle recovered after restart - Cable ON for {{ duty_on_minutes }} minutes | |
| **Recovery Details:** | |
| • Duty cycle was active before restart | |
| • Timer restarted in ON phase | |
| • Conditions still require heating | |
| **Current Conditions:** | |
| • Temperature: {{ states(temperature_sensor) }}°C | |
| • Weather: {{ states(weather_condition_sensor) }} | |
| • Precipitation: {{ states(precipitation_sensor) }} | |
| *Duty cycle will continue normally* | |
| - service: "notify.{{ notify_service }}" | |
| data: | |
| title: "🔄 Roof Heating - RECOVERED" | |
| message: | | |
| Duty cycle recovered after restart | |
| Cable ON - timer restarted | |
| Temperature: {{ states(temperature_sensor) }}°C | |
| data: | |
| tag: roof_heating | |
| group: roof_heating | |
| else: | |
| # Cable is OFF, restart OFF timer | |
| - service: timer.start | |
| target: | |
| entity_id: "{{ duty_timer }}" | |
| data: | |
| duration: "00:{{ duty_off_minutes }}:00" | |
| - service: persistent_notification.create | |
| data: | |
| notification_id: "roof_heating_status" | |
| title: "🔄 Roof Heating - DUTY CYCLE RECOVERED" | |
| message: | | |
| **Status:** Duty cycle recovered after restart - Cable OFF for {{ duty_off_minutes }} minutes | |
| **Recovery Details:** | |
| • Duty cycle was active before restart | |
| • Timer restarted in OFF phase | |
| • Conditions still require heating | |
| **Current Conditions:** | |
| • Temperature: {{ states(temperature_sensor) }}°C | |
| • Weather: {{ states(weather_condition_sensor) }} | |
| • Precipitation: {{ states(precipitation_sensor) }} | |
| *Duty cycle will continue normally* | |
| - service: "notify.{{ notify_service }}" | |
| data: | |
| title: "🔄 Roof Heating - RECOVERED" | |
| message: | | |
| Duty cycle recovered after restart | |
| Cable OFF - timer restarted | |
| Temperature: {{ states(temperature_sensor) }}°C | |
| data: | |
| tag: roof_heating | |
| group: roof_heating | |
| else: | |
| # CONDITIONS NO LONGER REQUIRE HEATING - Stop duty cycle | |
| - service: input_boolean.turn_off | |
| target: | |
| entity_id: "{{ duty_cycle_active }}" | |
| - service: switch.turn_off | |
| target: | |
| entity_id: "{{ heating_switch }}" | |
| - service: persistent_notification.create | |
| data: | |
| notification_id: "roof_heating_status" | |
| title: "❄️ Roof Heating - STOPPED AFTER RESTART" | |
| message: | | |
| **Status:** Duty cycle stopped - conditions improved during restart | |
| **Recovery Details:** | |
| • Duty cycle was active before restart | |
| • Conditions no longer require heating | |
| • Cable turned off for safety | |
| **Current Conditions:** | |
| • Temperature: {{ states(temperature_sensor) }}°C | |
| • Weather: {{ states(weather_condition_sensor) }} | |
| • Precipitation: {{ states(precipitation_sensor) }} | |
| *Duty cycle will restart if weather conditions require heating* | |
| - service: "notify.{{ notify_service }}" | |
| data: | |
| title: "❄️ Roof Heating - STOPPED" | |
| message: | | |
| Duty cycle stopped after restart | |
| Conditions improved during downtime | |
| Temperature: {{ states(temperature_sensor) }}°C | |
| data: | |
| tag: roof_heating | |
| group: roof_heating | |
| # ========== DUTY CYCLE TIMER FINISHED ========== | |
| - conditions: | |
| - condition: trigger | |
| id: "duty_cycle_timer_finished" | |
| sequence: | |
| # Check safety temperatures first | |
| - if: | |
| - condition: or | |
| conditions: | |
| # Temperature too high | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| above: !input safety_shutoff_high_temp | |
| # Temperature too low | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: !input safety_shutoff_low_temp | |
| then: | |
| # STOP DUTY CYCLE - Safety temperature reached | |
| - service: input_boolean.turn_off | |
| target: | |
| entity_id: "{{ duty_cycle_active }}" | |
| - service: switch.turn_off | |
| target: | |
| entity_id: "{{ heating_switch }}" | |
| - service: persistent_notification.create | |
| data: | |
| notification_id: "roof_heating_status" | |
| title: "⚠️ Roof Heating - SAFETY STOP" | |
| message: | | |
| **Status:** Duty cycle STOPPED for safety | |
| **Current Temperature:** {{ states(temperature_sensor) }}°C | |
| **Safety Range:** {{ safety_low_temp }}°C to {{ safety_high_temp }}°C | |
| **Reason:** {% if states(temperature_sensor)|float > safety_high_temp %}Temperature too high{% else %}Temperature too low{% endif %} | |
| *Duty cycle will restart when conditions are safe and weather requires heating* | |
| - service: "notify.{{ notify_service }}" | |
| data: | |
| title: "⚠️ Roof Heating - SAFETY STOP" | |
| message: | | |
| Duty cycle stopped for safety | |
| Temperature: {{ states(temperature_sensor) }}°C | |
| Reason: {% if states(temperature_sensor)|float > safety_high_temp %}Too hot (>{{ safety_high_temp }}°C){% else %}Too cold (<{{ safety_low_temp }}°C){% endif %} | |
| data: | |
| tag: roof_heating | |
| group: roof_heating | |
| priority: high | |
| else: | |
| # Check if conditions still require heating | |
| - if: | |
| - condition: and | |
| conditions: | |
| # Temperature check (day/night aware) | |
| - condition: or | |
| conditions: | |
| # Daytime threshold | |
| - condition: and | |
| conditions: | |
| - condition: sun | |
| after: sunrise | |
| before: sunset | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: !input day_temperature_threshold | |
| # Nighttime threshold | |
| - condition: and | |
| conditions: | |
| - condition: sun | |
| after: sunset | |
| before: sunrise | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: !input night_temperature_threshold | |
| # Weather conditions (current OR next hour) | |
| - condition: or | |
| conditions: | |
| # Current snow conditions | |
| - condition: state | |
| entity_id: !input precipitation_sensor | |
| state: | |
| - "Snow" | |
| - "Snow and Rain" | |
| - condition: numeric_state | |
| entity_id: !input snow_rate_sensor | |
| above: 0 | |
| - condition: state | |
| entity_id: !input weather_condition_sensor | |
| state: | |
| - "snowy" | |
| - "snowy-rainy" | |
| - "hail" | |
| # Next hour forecast shows snow | |
| - condition: template | |
| value_template: > | |
| {% set next_hour = states(forecast_storage) %} | |
| {% if next_hour not in ['unavailable', 'unknown', 'no_data', ''] %} | |
| {{ 'snow' in next_hour.lower() or 'sleet' in next_hour.lower() }} | |
| {% else %} | |
| false | |
| {% endif %} | |
| # Freezing rain | |
| - condition: and | |
| conditions: | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: 0 | |
| - condition: state | |
| entity_id: !input weather_condition_sensor | |
| state: | |
| - "rainy" | |
| - "pouring" | |
| then: | |
| # CONTINUE DUTY CYCLE - Toggle cable state | |
| - if: | |
| - condition: state | |
| entity_id: !input heating_cable_switch | |
| state: "on" | |
| then: | |
| # Cable is ON, turn OFF and start OFF timer | |
| - service: switch.turn_off | |
| target: | |
| entity_id: "{{ heating_switch }}" | |
| - service: timer.start | |
| target: | |
| entity_id: "{{ duty_timer }}" | |
| data: | |
| duration: "00:{{ duty_off_minutes }}:00" | |
| - service: persistent_notification.create | |
| data: | |
| notification_id: "roof_heating_status" | |
| title: "🔄 Roof Heating - DUTY CYCLE OFF" | |
| message: | | |
| **Status:** Cable OFF for {{ duty_off_minutes }} minutes | |
| **Current Conditions:** | |
| • Temperature: {{ states(temperature_sensor) }}°C | |
| • Weather: {{ states(weather_condition_sensor) }} | |
| • Precipitation: {{ states(precipitation_sensor) }} | |
| • Next Hour: {{ states(forecast_storage) }} | |
| **Energy Saving:** Duty cycling active | |
| *Cable will turn ON automatically in {{ duty_off_minutes }} minutes* | |
| else: | |
| # Cable is OFF, turn ON and start ON timer | |
| - service: switch.turn_on | |
| target: | |
| entity_id: "{{ heating_switch }}" | |
| - service: timer.start | |
| target: | |
| entity_id: "{{ duty_timer }}" | |
| data: | |
| duration: "00:{{ duty_on_minutes }}:00" | |
| - service: persistent_notification.create | |
| data: | |
| notification_id: "roof_heating_status" | |
| title: "🔥 Roof Heating - DUTY CYCLE ON" | |
| message: | | |
| **Status:** Cable ON for {{ duty_on_minutes }} minutes | |
| **Current Conditions:** | |
| • Temperature: {{ states(temperature_sensor) }}°C | |
| • Weather: {{ states(weather_condition_sensor) }} | |
| • Precipitation: {{ states(precipitation_sensor) }} | |
| • Next Hour: {{ states(forecast_storage) }} | |
| **Energy Saving:** Duty cycling active | |
| *Cable will turn OFF automatically in {{ duty_on_minutes }} minutes* | |
| else: | |
| # STOP DUTY CYCLE - Conditions no longer require heating | |
| - service: input_boolean.turn_off | |
| target: | |
| entity_id: "{{ duty_cycle_active }}" | |
| - service: switch.turn_off | |
| target: | |
| entity_id: "{{ heating_switch }}" | |
| - service: persistent_notification.create | |
| data: | |
| notification_id: "roof_heating_status" | |
| title: "❄️ Roof Heating - INACTIVE" | |
| message: | | |
| **Status:** Duty cycle stopped - conditions improved | |
| **Current Conditions:** | |
| • Temperature: {{ states(temperature_sensor) }}°C | |
| • Weather: {{ states(weather_condition_sensor) }} | |
| • Precipitation: {{ states(precipitation_sensor) }} | |
| • Next Hour: {{ states(forecast_storage) }} | |
| *Duty cycle will restart if weather conditions require heating* | |
| - service: "notify.{{ notify_service }}" | |
| data: | |
| title: "❄️ Roof Heating Deactivated" | |
| message: | | |
| Duty cycle stopped - conditions improved | |
| Temperature: {{ states(temperature_sensor) }}°C | |
| Weather: {{ states(weather_condition_sensor) }} | |
| data: | |
| tag: roof_heating | |
| group: roof_heating | |
| # ========== WEATHER TRIGGERS (START DUTY CYCLE) ========== | |
| - conditions: | |
| - condition: and | |
| conditions: | |
| # Duty cycle is NOT currently active | |
| - condition: state | |
| entity_id: !input duty_cycle_active_helper | |
| state: "off" | |
| # Temperature is within safe operating range | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| above: !input safety_shutoff_low_temp | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: !input safety_shutoff_high_temp | |
| # Temperature check (day/night aware) | |
| - condition: or | |
| conditions: | |
| # Daytime threshold | |
| - condition: and | |
| conditions: | |
| - condition: sun | |
| after: sunrise | |
| before: sunset | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: !input day_temperature_threshold | |
| # Nighttime threshold | |
| - condition: and | |
| conditions: | |
| - condition: sun | |
| after: sunset | |
| before: sunrise | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: !input night_temperature_threshold | |
| # Weather conditions (current OR next hour) | |
| - condition: or | |
| conditions: | |
| # Current snow conditions | |
| - condition: state | |
| entity_id: !input precipitation_sensor | |
| state: | |
| - "Snow" | |
| - "Snow and Rain" | |
| - condition: numeric_state | |
| entity_id: !input snow_rate_sensor | |
| above: 0 | |
| - condition: state | |
| entity_id: !input weather_condition_sensor | |
| state: | |
| - "snowy" | |
| - "snowy-rainy" | |
| - "hail" | |
| # Next hour forecast shows snow | |
| - condition: template | |
| value_template: > | |
| {% set next_hour = states(forecast_storage) %} | |
| {% if next_hour not in ['unavailable', 'unknown', 'no_data', ''] %} | |
| {{ 'snow' in next_hour.lower() or 'sleet' in next_hour.lower() }} | |
| {% else %} | |
| false | |
| {% endif %} | |
| # Freezing rain | |
| - condition: and | |
| conditions: | |
| - condition: numeric_state | |
| entity_id: !input temperature_sensor | |
| below: 0 | |
| - condition: state | |
| entity_id: !input weather_condition_sensor | |
| state: | |
| - "rainy" | |
| - "pouring" | |
| sequence: | |
| # START DUTY CYCLE | |
| - service: input_boolean.turn_on | |
| target: | |
| entity_id: "{{ duty_cycle_active }}" | |
| # Turn on cable and start first ON timer | |
| - service: switch.turn_on | |
| target: | |
| entity_id: "{{ heating_switch }}" | |
| - service: timer.start | |
| target: | |
| entity_id: "{{ duty_timer }}" | |
| data: | |
| duration: "00:{{ duty_on_minutes }}:00" | |
| # Notifications | |
| - service: persistent_notification.create | |
| data: | |
| notification_id: "roof_heating_status" | |
| title: "🔥 Roof Heating - DUTY CYCLE STARTED" | |
| message: | | |
| **Status:** Duty cycle started - Cable ON for {{ duty_on_minutes }} minutes | |
| **Current Conditions:** | |
| • Temperature: {{ states(temperature_sensor) }}°C | |
| • Weather: {{ states(weather_condition_sensor) }} | |
| • Precipitation: {{ states(precipitation_sensor) }} | |
| • Next Hour: {{ states(forecast_storage) }} | |
| **Duty Cycle:** {{ duty_on_minutes }}min ON / {{ duty_off_minutes }}min OFF | |
| **Safety Range:** {{ safety_low_temp }}°C to {{ safety_high_temp }}°C | |
| *Only duty cycle controls cable from now on* | |
| - service: "notify.{{ notify_service }}" | |
| data: | |
| title: "🔥 Roof Heating - DUTY CYCLE STARTED" | |
| message: | | |
| Duty cycle started - {{ duty_on_minutes }}min ON / {{ duty_off_minutes }}min OFF | |
| Temperature: {{ states(temperature_sensor) }}°C | |
| Weather: {{ states(weather_condition_sensor) }} | |
| Next Hour: {{ states(forecast_storage) }} | |
| data: | |
| tag: roof_heating | |
| group: roof_heating | |
| priority: high | |
| mode: queued | |
| max: 5 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment