blueprint: name: "Smart Thermostat Controller" author: Andreas Gammelgaard Damsbo description: | Advanced thermostat automation with window/door sensors, presence detection, time schedules, and holiday calendar integration. SETUP REQUIRED: 1. Create an input_boolean helper (Settings > Devices & Services > Helpers > Toggle) for manual override tracking 2. Create a timer helper (Settings > Devices & Services > Helpers > Timer) for override duration domain: automation source_url: https://gdamsbo.dk/forgejo/andreas/ha-smart-thermostat-control/raw/branch/main/ha-smart-thermostat-control.yaml input: thermostat: name: Thermostat description: Select the thermostat(s) to control. selector: entity: domain: - climate multiple: true manual_override_helper: name: Manual Override Helper description: Select an input_boolean helper to track manual temperature overrides. Create one in Settings > Devices & Services > Helpers. selector: entity: domain: - input_boolean multiple: false override_timer: name: Override Timer description: Select a timer helper to manage override duration. Create one in Settings > Devices & Services > Helpers > Timer. selector: entity: domain: - timer multiple: false override_duration: name: Manual Override Duration description: 'How long manual temperature changes should override the schedule. This sets the timer duration. (Default = 120 minutes)' default: 120 selector: number: mode: box min: 30.0 max: 480.0 unit_of_measurement: minutes step: 30.0 window_sensor: name: Window / Door Sensor Group description: Select your grouped or single window / door sensor. selector: entity: domain: - binary_sensor multiple: true window_delay: name: Window / Door Sensor Delay description: 'Time the sensor needs to stay the same after change to trigger the automation. (Default = 5s)' default: 5 selector: number: mode: box min: 0.0 max: 600.0 unit_of_measurement: seconds step: 5.0 outdoor: name: Outdoor Temperature Sensor description: Select your outdoor temperature sensor. selector: entity: domain: - sensor multiple: false wintermode: name: Winter Mode Threshold description: 'The outside temperature needs to be below this to activate winter mode. (Default = 16°C)' default: 16 selector: number: step: 1 min: 10.0 max: 25.0 unit_of_measurement: °C or °F mode: slider wintermode_delay: name: Winter Mode Delay description: 'Time the outside temperature needs to stay above the winter mode temperature to turn the heating off.' default: 30 selector: number: mode: box min: 1.0 max: 1440.0 unit_of_measurement: minutes step: 5.0 schedule_helper: name: Schedule Helper description: Select the schedule helper entity to determine day/night heating times. selector: entity: domain: - schedule multiple: false day_temp: name: Day Time Temperature Target default: 21 selector: number: step: 0.5 min: 10.0 max: 30.0 unit_of_measurement: °C or °F mode: slider night_temp: name: Night Time Temperature Target default: 18 selector: number: step: 0.5 min: 10.0 max: 30.0 unit_of_measurement: °C or °F mode: slider away_temp: name: Away Temperature Target description: Temperature when away/on holiday. default: 16 selector: number: step: 0.5 min: 10.0 max: 30.0 unit_of_measurement: °C or °F mode: slider away_calendar: name: Holiday or Away Calendar description: Calendar entity that indicates away/holiday periods. selector: entity: domain: - calendar multiple: false return_offset: name: Return Offset description: 'Time offset to start heating before returning from away. Negative value = minutes before return.' default: -120 selector: number: mode: box min: -1440.0 max: 0.0 unit_of_measurement: minutes step: 30.0 trigger: - platform: state entity_id: !input window_sensor for: seconds: !input window_delay id: window_change - platform: numeric_state entity_id: !input outdoor below: !input wintermode for: minutes: !input wintermode_delay id: cold_weather - platform: numeric_state entity_id: !input outdoor above: !input wintermode for: minutes: !input wintermode_delay id: warm_weather - platform: state entity_id: !input schedule_helper id: schedule_change - platform: state entity_id: !input thermostat attribute: temperature id: manual_adjustment - platform: state entity_id: !input manual_override_helper to: 'off' id: override_expired - platform: state entity_id: !input override_timer to: 'idle' id: timer_finished - platform: calendar event: end entity_id: !input away_calendar offset: !input return_offset id: return_soon - platform: calendar event: start entity_id: !input away_calendar id: leaving - platform: homeassistant event: start id: ha_start - platform: event event_type: automation_reloaded id: reload variables: thermostat_entities: !input thermostat triggered_thermostat: "{{ trigger.entity_id | default('none') }}" # Access temperature safely: # 1. Get the 'to_state', defaulting to a simple object/dict if missing. # 2. Get the 'attributes' from that object, defaulting to a dict if missing. # 3. Get the 'temperature' from the attributes, defaulting to 0, then convert to float. new_temperature: > {{ trigger.to_state.attributes.temperature | default(0) | float(0) if trigger.to_state is defined and trigger.to_state.attributes is defined else 0 }} old_temperature: > {{ trigger.from_state.attributes.temperature | default(0) | float(0) if trigger.from_state is defined and trigger.from_state.attributes is defined else 0 }} override_helper: !input manual_override_helper override_duration_id: !input override_duration day_temp_input: !input day_temp night_temp_input: !input night_temp away_temp_input: !input away_temp schedule_helper_id: !input schedule_helper away_calendar_id: !input away_calendar condition: [] action: - choose: # Priority 0: Manual Adjustment - Detect and enable override - conditions: - condition: trigger id: manual_adjustment # Only if temperature actually changed - condition: template value_template: "{{ (new_temperature - old_temperature) | abs > 0.1 }}" # Only if the new temp is different from current schedule targets - condition: template value_template: > {% if is_state(away_calendar_id, 'on') %} {{ (new_temperature - away_temp_input) | abs > 0.1 }} {% elif is_state(schedule_helper_id, 'on') %} {{ (new_temperature - day_temp_input) | abs > 0.1 }} {% else %} {{ (new_temperature - night_temp_input) | abs > 0.1 }} {% endif %} sequence: - service: logbook.log data: name: Smart Thermostat message: 'Manual Override: {{ triggered_thermostat }} changed to {{ new_temperature }}°, syncing others' # Sync temperature to all other thermostats - repeat: for_each: "{{ thermostat_entities | reject('eq', triggered_thermostat) | list }}" sequence: - service: climate.set_temperature target: entity_id: "{{ repeat.item }}" data: temperature: "{{ new_temperature }}" # Enable manual override mode - service: input_boolean.turn_on target: entity_id: !input manual_override_helper # Start timer - service: timer.start target: entity_id: !input override_timer data: duration: "{{ (override_duration_id * 60) | int }}" # Priority 0b: Timer Finished - Disable override and resume schedule - conditions: - condition: trigger id: timer_finished sequence: - service: input_boolean.turn_off target: entity_id: !input manual_override_helper - service: logbook.log data: name: Smart Thermostat message: 'Manual Override: Timer expired, resuming schedule' # Priority 1: Windows/Doors Open - Turn OFF heating (overrides manual) - conditions: - condition: state entity_id: !input window_sensor state: 'on' match: any sequence: - service: climate.turn_off target: entity_id: !input thermostat - service: input_boolean.turn_off target: entity_id: !input manual_override_helper - service: timer.cancel target: entity_id: !input override_timer - service: logbook.log data: name: Smart Thermostat message: 'Heating OFF: Window or door is open' # Priority 2: Too Warm Outside - Turn OFF heating (overrides manual) - conditions: - condition: numeric_state entity_id: !input outdoor above: !input wintermode sequence: - service: climate.turn_off target: entity_id: !input thermostat - service: input_boolean.turn_off target: entity_id: !input manual_override_helper - service: timer.cancel target: entity_id: !input override_timer - service: logbook.log data: name: Smart Thermostat message: 'Heating OFF: Outside temperature above threshold' # Priority 3: Manual Override Active - Do nothing, keep manual temperature - conditions: - condition: state entity_id: !input manual_override_helper state: 'on' sequence: - service: logbook.log data: name: Smart Thermostat message: 'Manual override active - maintaining user-set temperature' # Priority 4: Away/Holiday Mode - Set to away temperature - conditions: - condition: state entity_id: !input away_calendar state: 'on' - condition: numeric_state entity_id: !input outdoor below: !input wintermode - condition: state entity_id: !input window_sensor state: 'off' match: all sequence: - service: climate.set_temperature target: entity_id: !input thermostat data: temperature: !input away_temp hvac_mode: heat - service: logbook.log data: name: Smart Thermostat message: 'Away Mode: Heating set to away temperature' # Priority 5: Schedule Day Time - Set to day temperature - conditions: - condition: state entity_id: !input schedule_helper state: 'on' - condition: numeric_state entity_id: !input outdoor below: !input wintermode - condition: state entity_id: !input window_sensor state: 'off' match: all - condition: state entity_id: !input away_calendar state: 'off' sequence: - service: climate.set_temperature target: entity_id: !input thermostat data: temperature: !input day_temp hvac_mode: heat - service: logbook.log data: name: Smart Thermostat message: 'Day Mode: Heating set to day temperature' # Priority 6: Schedule Night Time - Set to night temperature - conditions: - condition: state entity_id: !input schedule_helper state: 'off' - condition: numeric_state entity_id: !input outdoor below: !input wintermode - condition: state entity_id: !input window_sensor state: 'off' match: all - condition: state entity_id: !input away_calendar state: 'off' sequence: - service: climate.set_temperature target: entity_id: !input thermostat data: temperature: !input night_temp hvac_mode: heat - service: logbook.log data: name: Smart Thermostat message: 'Night Mode: Heating set to night temperature' # Default: Turn off if no conditions match default: - service: climate.turn_off target: entity_id: !input thermostat - service: logbook.log data: name: Smart Thermostat message: 'Heating OFF: No active heating conditions met' mode: single max_exceeded: silent