Files
2026-06-30 17:03:35 -04:00

779 lines
28 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Climate Control - Seasonal HVAC automations and sensors.
# Thermostat helpers for upstairs/downstairs comfort.
# -------------------------------------------------------------------
# Related Issue: 1571, 273
# Notes: Filter due alerts use a 350h runtime threshold, 9-month hard limit, 3-day snooze, and Filter Changed push actions.
# Notes: Condenser line cleanout uses a single 360-day reminder for both HVAC systems.
# Notes: The snooze notification pattern is also covered at https://youtu.be/y47KSflS1aw.
# Video: https://youtu.be/nScl2JxYSB8
######################################################################
# Contact: @CCOSTAN | Entity reference details below
# Entity reference and attribute summaries:
#
# Entity ID: climate.downstairs
# Supported hvac_modes: heat, cool, heat_cool, off
# Supported fan_modes: on, off
# Supported preset_modes: none, eco
# Supported temperature range: 50°F (min_temp) 90°F (max_temp)
#
# Current state (example):
# current_temperature: 77
# temperature: 83
# target_temp_high: null
# target_temp_low: null
# current_humidity: 58
# fan_mode: off
# hvac_action: idle
# preset_mode: none
# Supported features: 411
# --------------------------------------------------------------
# Entity ID: climate.upstairs
# Supported hvac_modes: off, heat, cool, heat_cool
# Supported temperature range: 50°F (min_temp) 90°F (max_temp)
#
# Current state (example):
# current_temperature: 76
# temperature: 78
# target_temp_high: null
# target_temp_low: null
# friendly_name: Upstairs
# Supported features: 387
######################################################################
# ---------------------------------------------------------------------------
# Template helpers compressor status flags and numeric runtime helpers
# ---------------------------------------------------------------------------
template:
- binary_sensor:
- name: "Downstairs AC is Cooling"
unique_id: downstairs_ac_cooling
state: >
{% set action = state_attr('climate.downstairs', 'hvac_action') %}
{% set current = state_attr('climate.downstairs', 'current_temperature') | float(none) %}
{% set target = state_attr('climate.downstairs', 'temperature') | float(none) %}
{{
action == 'cooling'
or (
action is none
and is_state('climate.downstairs', 'cool')
and current is not none
and target is not none
and current > target
)
}}
- name: "Upstairs AC is Cooling"
unique_id: upstairs_ac_cooling
state: >
{% set action = state_attr('climate.upstairs', 'hvac_action') %}
{% set current = state_attr('climate.upstairs', 'current_temperature') | float(none) %}
{% set target = state_attr('climate.upstairs', 'temperature') | float(none) %}
{{
action == 'cooling'
or (
action is none
and is_state('climate.upstairs', 'cool')
and current is not none
and target is not none
and current > target
)
}}
sensor:
- name: "Downstairs AC Cooling Numeric"
unique_id: downstairs_ac_cooling_numeric
state: "{{ 1 if is_state('binary_sensor.downstairs_ac_is_cooling', 'on') else 0 }}"
- name: "Upstairs AC Cooling Numeric"
unique_id: upstairs_ac_cooling_numeric
state: "{{ 1 if is_state('binary_sensor.upstairs_ac_is_cooling', 'on') else 0 }}"
input_datetime:
downstairs_last_filter_change:
name: Downstairs Last Filter Change
has_date: true
has_time: true
downstairs_filter_snooze_until:
name: Downstairs Filter Snooze Until
has_date: true
has_time: true
upstairs_last_filter_change:
name: Upstairs Last Filter Change
has_date: true
has_time: true
upstairs_filter_snooze_until:
name: Upstairs Filter Snooze Until
has_date: true
has_time: true
hvac_condenser_lines_last_cleaned:
name: HVAC Condenser Lines Last Cleaned
has_date: true
has_time: false
initial: "2026-06-28"
hvac_condenser_lines_cleaning_snooze_until:
name: HVAC Condenser Lines Cleaning Snooze Until
has_date: true
has_time: true
initial: "2026-06-28 00:00:00"
# ---------------------------------------------------------------------------
# Integration sensors tally runtime based on compressor state
# ---------------------------------------------------------------------------
sensor:
- platform: integration
name: Downstairs AC Runtime Raw
source: sensor.downstairs_ac_cooling_numeric
unit_time: h
method: left
round: 2
- platform: integration
name: Upstairs AC Runtime Raw
source: sensor.upstairs_ac_cooling_numeric
unit_time: h
method: left
round: 2
# ---------------------------------------------------------------------------
# Utilitymeters that act like odometers (reset when you swap the filter)
# ---------------------------------------------------------------------------
utility_meter:
downstairs_ac_runtime_since_last_filter_change:
source: sensor.downstairs_ac_runtime_raw
upstairs_ac_runtime_since_last_filter_change:
source: sensor.upstairs_ac_runtime_raw
script:
reset_downstairs_filter:
alias: Reset Downstairs Filter
mode: queued
sequence:
- service: input_datetime.set_datetime
data:
entity_id: input_datetime.downstairs_last_filter_change
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
- service: input_datetime.set_datetime
data:
entity_id: input_datetime.downstairs_filter_snooze_until
datetime: "{{ (now() - timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') }}"
- service: utility_meter.reset
target:
entity_id: sensor.downstairs_ac_runtime_since_last_filter_change
reset_upstairs_filter:
alias: Reset Upstairs Filter
mode: queued
sequence:
- service: input_datetime.set_datetime
data:
entity_id: input_datetime.upstairs_last_filter_change
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
- service: input_datetime.set_datetime
data:
entity_id: input_datetime.upstairs_filter_snooze_until
datetime: "{{ (now() - timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') }}"
- service: utility_meter.reset
target:
entity_id: sensor.upstairs_ac_runtime_since_last_filter_change
reset_hvac_condenser_lines_cleaned:
alias: Reset HVAC Condenser Lines Cleaned
mode: queued
sequence:
- service: input_datetime.set_datetime
data:
entity_id: input_datetime.hvac_condenser_lines_last_cleaned
date: "{{ now().strftime('%Y-%m-%d') }}"
- service: input_datetime.set_datetime
data:
entity_id: input_datetime.hvac_condenser_lines_cleaning_snooze_until
datetime: "{{ (now() - timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') }}"
set_downstairs_target_temp_based_on_conditions:
alias: Set Downstairs Target Temperature Based on Conditions
mode: single
sequence:
- choose:
- conditions:
- condition: state
entity_id: group.family
state: 'not_home'
sequence:
- service: script.send_to_logbook
data:
topic: "CLIMATE"
message: "Downstairs HVAC set to ECO (family not_home)."
- service: climate.set_preset_mode
data:
entity_id: climate.downstairs
preset_mode: 'eco'
- conditions:
- condition: and
conditions:
- condition: state
entity_id: group.family
state: 'home'
- condition: template
value_template: >-
{{ states('sensor.pirateweather_temperature') in ['unknown','unavailable','none',''] }}
sequence:
- service: script.send_to_logbook
data:
topic: "CLIMATE"
message: "Outside temp unavailable; allowing downstairs cool mode (defaulting to 80F)."
- service: climate.set_hvac_mode
data:
entity_id: climate.downstairs
hvac_mode: cool
- service: climate.set_temperature
data:
entity_id: climate.downstairs
temperature: 80
- conditions:
- condition: and
conditions:
- condition: state
entity_id: group.family
state: 'home'
- condition: numeric_state
entity_id: sensor.pirateweather_temperature
below: 78
sequence:
- service: script.send_to_logbook
data:
topic: "CLIMATE"
message: "Downstairs target set to 78F (family home, outside temp <78F)."
- service: climate.set_hvac_mode
data:
entity_id: climate.downstairs
hvac_mode: cool
- service: climate.set_temperature
data:
entity_id: climate.downstairs
temperature: 78
- conditions:
- condition: and
conditions:
- condition: state
entity_id: group.family
state: 'home'
- condition: numeric_state
entity_id: sensor.pirateweather_temperature
above: 92
sequence:
- service: script.send_to_logbook
data:
topic: "CLIMATE"
message: "Downstairs target set to 78F (family home, temp >92F)."
- service: climate.set_hvac_mode
data:
entity_id: climate.downstairs
hvac_mode: cool
- service: climate.set_temperature
data:
entity_id: climate.downstairs
temperature: 78
- conditions:
- condition: and
conditions:
- condition: state
entity_id: group.family
state: 'home'
- condition: template
value_template: "{{ states('sensor.pirateweather_temperature')|float(-999) >= 78 }}"
sequence:
- service: script.send_to_logbook
data:
topic: "CLIMATE"
message: "Downstairs target set to 80F (family home)."
- service: climate.set_hvac_mode
data:
entity_id: climate.downstairs
hvac_mode: cool
- service: climate.set_temperature
data:
entity_id: climate.downstairs
temperature: 80
set_downstairs_daytime_target:
alias: Set Downstairs Daytime Target
mode: single
sequence:
- service: script.set_downstairs_target_temp_based_on_conditions
set_downstairs_hvac_cool:
alias: Set Downstairs HVAC Cool
mode: single
sequence:
- service: climate.set_hvac_mode
data:
entity_id: climate.downstairs
hvac_mode: cool
set_upstairs_cool_82:
alias: Set Upstairs Cool 82
mode: single
sequence:
- service: climate.set_hvac_mode
data:
entity_id: climate.upstairs
hvac_mode: cool
- service: climate.set_temperature
data:
entity_id: climate.upstairs
temperature: 82
##############################################################################
### AUTOMATIONS - Thermostat schedules, guardrails, and presence/weather logic
### Some shutoff automations are also in the ALARM.yaml package when windows/doors are left open.
### There are also some automations in the POWERWALL.yaml package when the grid is down.
##############################################################################
automation:
- alias: Notify Climate Filter Change Due
description: Notify when HVAC filter runtime exceeds 350h or age reaches 9 months
trigger:
- platform: numeric_state
entity_id: sensor.downstairs_ac_runtime_since_last_filter_change
above: 350
id: downstairs_runtime
- platform: time
at: "09:00:00"
id: downstairs_daily
- platform: numeric_state
entity_id: sensor.upstairs_ac_runtime_since_last_filter_change
above: 350
id: upstairs_runtime
- platform: time
at: "09:10:00"
id: upstairs_daily
variables:
location: "{{ 'upstairs' if trigger.id.startswith('upstairs') else 'downstairs' }}"
location_title: "{{ 'Upstairs' if location == 'upstairs' else 'Downstairs' }}"
runtime_sensor: "sensor.{{ location }}_ac_runtime_since_last_filter_change"
last_filter_entity: "input_datetime.{{ location }}_last_filter_change"
snooze_entity: "input_datetime.{{ location }}_filter_snooze_until"
runtime_hours: "{{ states(runtime_sensor) | float(0) }}"
last_filter_ts: "{{ as_timestamp(states(last_filter_entity), 0) }}"
filter_age_days: "{{ ((as_timestamp(now()) - (last_filter_ts | float(0))) / 86400) | round(0) }}"
due_reason: >-
{% set runtime_due = runtime_hours | float(0) > 350 %}
{% set age_due = (last_filter_ts | float(0)) == 0 or (filter_age_days | int(0)) >= 274 %}
{% if runtime_due and age_due %}
Runtime has exceeded 350h and the filter is {{ filter_age_days }} days old.
{% elif runtime_due %}
Runtime has exceeded 350h.
{% else %}
Filter age has reached the 9-month hard limit ({{ filter_age_days }} days).
{% endif %}
condition:
- condition: template
value_template: >-
{{
(runtime_hours | float(0) > 350)
or (last_filter_ts | float(0)) == 0
or (filter_age_days | int(0)) >= 274
}}
- condition: template
value_template: >-
{% set snooze_until = as_timestamp(states(snooze_entity), 0) %}
{{ snooze_until <= as_timestamp(now()) }}
action:
- service: script.send_to_logbook
data:
topic: "MAINTENANCE"
message: >-
{{ location_title }} AC filter due. {{ due_reason }} Runtime is {{ runtime_hours | round(1) }}h.
- service: script.notify_engine_two_button
data:
title: "Home Maintenance Reminder"
value1: "It's time to change your {{ location_title }} AC filter."
value2: >
{{ due_reason }} Runtime is {{ runtime_hours | round(1) }}h.
title1: "Snooze 3d"
action1: "{{ 'SNOOZE_UPSTAIRS_FILTER_3D' if location == 'upstairs' else 'SNOOZE_DOWNSTAIRS_FILTER_3D' }}"
icon1: "sfsymbols:clock"
title2: "Filter Changed"
action2: "{{ 'RESET_UPSTAIRS_FILTER' if location == 'upstairs' else 'RESET_DOWNSTAIRS_FILTER' }}"
icon2: "sfsymbols:checkmark.circle"
who: "carlo"
group: "maintenance"
level: "active"
- alias: Climate Filter Reminder Actions
id: 6d7056d0-90ce-4c4f-b8b1-fd32a7e58311
mode: queued
trigger:
- platform: event
event_type: mobile_app_notification_action
event_data:
action: SNOOZE_DOWNSTAIRS_FILTER_3D
id: downstairs_snooze
- platform: event
event_type: mobile_app_notification_action
event_data:
action: SNOOZE_UPSTAIRS_FILTER_3D
id: upstairs_snooze
- platform: event
event_type: mobile_app_notification_action
event_data:
action: RESET_DOWNSTAIRS_FILTER
id: downstairs_reset
- platform: event
event_type: mobile_app_notification_action
event_data:
action: RESET_UPSTAIRS_FILTER
id: upstairs_reset
variables:
location: "{{ 'upstairs' if trigger.id.startswith('upstairs') else 'downstairs' }}"
location_title: "{{ 'Upstairs' if location == 'upstairs' else 'Downstairs' }}"
snooze_entity: "input_datetime.{{ location }}_filter_snooze_until"
reset_script: "script.reset_{{ location }}_filter"
action:
- choose:
- conditions: "{{ trigger.id.endswith('_snooze') }}"
sequence:
- variables:
snooze_until: "{{ (now() + timedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S') }}"
- service: input_datetime.set_datetime
target:
entity_id: "{{ snooze_entity }}"
data:
datetime: "{{ snooze_until }}"
- service: script.send_to_logbook
data:
topic: "MAINTENANCE"
message: "{{ location_title }} AC filter reminder snoozed until {{ snooze_until }}."
- conditions: "{{ trigger.id.endswith('_reset') }}"
sequence:
- service: "{{ reset_script }}"
- service: script.send_to_logbook
data:
topic: "MAINTENANCE"
message: "{{ location_title }} AC filter reset from notification action."
- alias: Notify HVAC Condenser Lines Cleaning Due
id: 4dafac96-3163-4d63-847a-76727005f75b
description: Notify every 360 days when the outdoor condenser lines should be cleaned
trigger:
- platform: time
at: "09:20:00"
variables:
cleaned: "{{ states('input_datetime.hvac_condenser_lines_last_cleaned') }}"
cleaned_ts: >-
{% if cleaned in ['unknown', 'unavailable', 'none', ''] %}
0
{% else %}
{{ as_timestamp(strptime(cleaned, '%Y-%m-%d'), 0) }}
{% endif %}
days_since_cleaned: "{{ ((as_timestamp(now()) - (cleaned_ts | float(0))) / 86400) | round(0) }}"
condition:
- condition: template
value_template: "{{ (cleaned_ts | float(0)) == 0 or (days_since_cleaned | int(0)) >= 360 }}"
- condition: template
value_template: >-
{% set snooze_until = as_timestamp(states('input_datetime.hvac_condenser_lines_cleaning_snooze_until'), 0) %}
{{ snooze_until <= as_timestamp(now()) }}
action:
- service: script.send_to_logbook
data:
topic: "MAINTENANCE"
message: "HVAC condenser lines cleanout due; last cleaned {{ days_since_cleaned }} days ago."
- service: script.notify_engine_two_button
data:
title: "Home Maintenance Reminder"
value1: "Clean the HVAC condenser lines."
value2: "360-day cleanout is due; last cleaned {{ days_since_cleaned }} days ago."
title1: "Snooze 3d"
action1: "SNOOZE_HVAC_CONDENSER_LINES_3D"
icon1: "sfsymbols:clock"
title2: "Cleaned"
action2: "RESET_HVAC_CONDENSER_LINES_CLEANED"
icon2: "sfsymbols:checkmark.circle"
who: "carlo"
group: "maintenance"
level: "active"
- alias: Climate Condenser Lines Reminder Actions
id: dfe89869-4bd3-47f6-bf0d-612bf7ee949f
mode: queued
trigger:
- platform: event
event_type: mobile_app_notification_action
event_data:
action: SNOOZE_HVAC_CONDENSER_LINES_3D
id: snooze
- platform: event
event_type: mobile_app_notification_action
event_data:
action: RESET_HVAC_CONDENSER_LINES_CLEANED
id: reset
action:
- choose:
- conditions: "{{ trigger.id == 'snooze' }}"
sequence:
- variables:
snooze_until: "{{ (now() + timedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S') }}"
- service: input_datetime.set_datetime
target:
entity_id: input_datetime.hvac_condenser_lines_cleaning_snooze_until
data:
datetime: "{{ snooze_until }}"
- service: script.send_to_logbook
data:
topic: "MAINTENANCE"
message: "HVAC condenser lines cleanout reminder snoozed until {{ snooze_until }}."
- conditions: "{{ trigger.id == 'reset' }}"
sequence:
- service: script.reset_hvac_condenser_lines_cleaned
- service: script.send_to_logbook
data:
topic: "MAINTENANCE"
message: "HVAC condenser lines cleanout reset from notification action."
- alias: 'AC Status Announcement'
id: 7812fdaf-a3f8-498b-8f07-28e977e528fe
trigger:
- platform: state
entity_id:
- climate.downstairs
- climate.upstairs
from: 'off'
- platform: state
entity_id:
- climate.downstairs
- climate.upstairs
to: 'off'
action:
- service: script.speech_engine
data:
NestStatus: "The {{ trigger.entity_id.split('.')[1].split('_')[0]}} {{ trigger.entity_id.split('.')[1].split('_')[1]}} has now been turned on for {{(trigger.to_state.state)}}."
call_window_check: 1
- alias: Prevent Upstairs Nest Below 76
id: 7812fdaf-a3f8-498b-8f07-28e977e528ff
trigger:
- platform: numeric_state
entity_id: climate.upstairs
attribute: temperature
below: 76
condition:
- condition: template
value_template: "{{ is_state('binary_sensor.powerwall_grid_status', 'on') }}"
action:
- delay: "00:03:00"
- service: climate.set_temperature
data:
entity_id: climate.upstairs
temperature: 78
- service: script.send_to_logbook
data:
topic: "CLIMATE"
message: "Upstairs target temp corrected to 78F (guardrail)."
- alias: Set Downstairs Bedtime Target When Both in Bed
id: set_downstairs_nest_77_both_in_bed
trigger:
- platform: state
entity_id: group.bed
to: 'on'
condition:
- condition: and
conditions:
- condition: state
entity_id: binary_sensor.sleepnumber_carlo_carlo_is_in_bed
state: 'on'
- condition: state
entity_id: binary_sensor.sleepnumber_carlo_stacey_is_in_bed
state: 'on'
- condition: state
entity_id: binary_sensor.powerwall_grid_status
state: 'on'
- condition: state
entity_id: sun.sun
state: 'below_horizon'
action:
- choose:
- conditions:
- condition: numeric_state
entity_id: sensor.pirateweather_temperature
above: 80
sequence:
- service: climate.set_temperature
data:
entity_id: climate.downstairs
temperature: 77
- conditions:
- condition: numeric_state
entity_id: sensor.pirateweather_temperature
above: 75
below: 80
sequence:
- service: climate.set_temperature
data:
entity_id: climate.downstairs
temperature: 79
- alias: 'Set Downstairs Thermostat Daytime Target'
id: set_downstairs_thermostat_daytime_target
mode: single
trigger:
- platform: state
entity_id: sun.sun
to: 'above_horizon'
- platform: time
at: "08:00:00"
- platform: numeric_state
entity_id: sensor.pirateweather_temperature
above: 92
condition:
- condition: and
conditions:
- condition: state
entity_id: sun.sun
state: 'above_horizon'
- condition: state
entity_id: input_boolean.guest_mode
state: 'off'
- condition: state
entity_id: binary_sensor.powerwall_grid_status
state: 'on'
action:
- service: script.set_downstairs_daytime_target
# Set thermostats to eco mode when everyone is away
- alias: 'Set Thermostats to Eco When Away'
id: 1e2d3c4b-eco-thermostat-away
mode: single
trigger:
- platform: state
entity_id: group.family
to: 'not_home'
condition:
- condition: state
entity_id: group.family
state: 'not_home'
- condition: state
entity_id: input_boolean.guest_mode
state: 'off'
action:
- service: climate.set_preset_mode
data:
entity_id: climate.downstairs
preset_mode: 'eco'
- choose:
- conditions:
- condition: numeric_state
entity_id: sensor.pirateweather_temperature
below: 75
sequence:
- service: script.send_to_logbook
data:
topic: "CLIMATE"
message: "Skipping downstairs cool mode (outside temp <75F)."
default:
- service: script.set_downstairs_hvac_cool
- service: climate.set_temperature
data:
entity_id: climate.upstairs
temperature: 83
# Automation: Set Thermostats Back to Cool Mode
- alias: 'Set Thermostats to Cool When Home'
id: 1e2d3c4b-cool-thermostat-home
mode: single
trigger:
- platform: state
entity_id: group.family
to: 'home'
condition:
- condition: or
conditions:
- condition: state
entity_id: group.family
state: 'home'
- condition: template
value_template: "{{ is_state('input_boolean.guest_mode', 'off') }}"
- condition: state
entity_id: binary_sensor.powerwall_grid_status
state: 'on'
action:
- service: climate.set_preset_mode
data:
entity_id: climate.downstairs
preset_mode: 'none'
- service: climate.set_hvac_mode
data:
entity_id: climate.upstairs
hvac_mode: cool
- service: script.set_downstairs_target_temp_based_on_conditions
- service: climate.set_temperature
data:
entity_id: climate.upstairs
temperature: 82
- alias: 'Reset Upstairs to 82 Each Night'
id: reset_upstairs_thermostat_nightly_82
mode: single
trigger:
- platform: time
at: "03:00:00"
condition:
- condition: template
value_template: "{{ states('binary_sensor.powerwall_grid_status') == 'on' }}"
action:
- service: script.set_upstairs_cool_82
- alias: 'Humidity Control'
id: AC_Humidity_Control
mode: single
trigger:
- platform: numeric_state
entity_id: sensor.downstairs_humidity
above: 60
- platform: time
at: "02:00:00"
condition:
- condition: time
after: "01:00:00"
before: "08:00:00"
- condition: numeric_state
entity_id: sensor.downstairs_humidity
above: 60
- condition: template #Only needed in the winter months
value_template: "{{ now().month in [10, 11, 12, 1, 2, 3] }}"
- condition: template # Only run if AC is idle (prevents fighting other automations)
value_template: "{{ state_attr('climate.downstairs', 'hvac_action') == 'idle' }}"
- condition: template # Never run if the grid is down and running on powerwall.
value_template: "{{ states('binary_sensor.powerwall_grid_status')|lower == 'on' }}"
action:
- choose:
- conditions:
- condition: numeric_state
entity_id: sensor.pirateweather_temperature
below: 75
sequence:
- service: script.send_to_logbook
data:
topic: "CLIMATE"
message: "Skipping downstairs cool mode (outside temp <75F)."
default:
- service: climate.set_preset_mode
data:
entity_id: climate.downstairs
preset_mode: 'none'
- service: climate.set_hvac_mode
data:
entity_id: climate.downstairs
hvac_mode: cool
- service: climate.set_temperature
data:
entity_id: climate.downstairs
temperature: 77
- delay: "00:20:00"
- service: script.set_downstairs_target_temp_based_on_conditions
- delay: "02:00:00" # stops it from being triggered again too soon.