I’ve spent a few months iterating on a fairly elaborate climate automation for a Gree/EWPE-protocol split AC (branded as Sinclair here, but the protocol is shared by many rebrands — Cooper&Hunter, etc.). Sharing the design + full YAML in case it’s useful to anyone with a similar unit or a similar “which mode am I in” presence-based cooling problem.
What it does
- Three “presence scenarios” via an
input_select(Home / Away / Vacation), each with its own cooling logic - Home: different day/evening/night temperature targets, AC off overnight
- Away: keep the room from overheating, but more aggressively (higher setpoint, always-on gate)
- Vacation: only intervene on real overheating (higher threshold), don’t fight for comfort
- A “free cooling” gate: don’t turn the AC on if it’s already cooler outside than inside — but an explicit overheat override always wins over that gate, so comfort/safety beats energy-saving when it actually matters
- Hysteresis on the free-cooling gate via a 30-minute
for:on a template trigger, to avoid flapping on short outdoor temperature blips - A shared “set cooling” and “turn off” script with retry (3x, 15s apart) + a push notification if it still didn’t take after 3 tries
- A known Gree/EWPE integration quirk fix (see below) baked into both scripts
The Gree/EWPE “Pow bit” bug
This is probably the most broadly useful part if you’re on this integration (core gree or common forks). The integration sends only the fields that changed relative to its last cached state (a diff, not a full state push: Pow, Mod, SetTem, etc.).
If you call climate.set_temperature with both hvac_mode and temperature in one call while the unit is off, there’s a real risk the diff drops the Pow field (physical power-on) while SetTem still goes through. Symptom: the automation fires with no error, but the unit never actually turns on.
Fix: always split it into two calls — climate.set_hvac_mode first (mode only), then after a short delay climate.set_temperature (temperature only). Confirmed this fixed two consecutive failed morning activations for me.
A related gotcha I hit later: if the unit is already in cool mode (e.g. left over from a previous branch) but at the wrong temperature, a retry loop that only checks state == 'cool' will think it already succeeded and never send set_temperature. Check both state == 'cool' AND temperature == target before deciding whether either call is needed.
The YAML
Entity IDs, thresholds, and scenario names are placeholders — swap in your own. The overall shape (multi-branch choose:, shared retry/notify scripts, hysteresis triggers) is what’s meant to be reusable.
# ============================================================
# Main automation: presence-aware AC control
# ============================================================
alias: "AC - main logic (Home/Away/Vacation)"
mode: single
triggers:
- trigger: time
at: "08:00:00"
id: morning
- trigger: time
at: "18:00:00"
id: evening
- trigger: time
at: "22:00:00"
id: night
- trigger: state
entity_id: input_select.presence_scenario
id: scenario
- trigger: numeric_state
entity_id:
- sensor.room_temp_a
- sensor.room_temp_b
above: 30
id: any_above_30
- trigger: numeric_state
entity_id:
- sensor.room_temp_a
- sensor.room_temp_b
below: 28
id: any_below_28
- trigger: numeric_state
entity_id: sensor.room_temp_main
below: 24
id: too_cold
- trigger: numeric_state
entity_id: sensor.room_temp_main
above: 28
id: overheat_night
- trigger: template
# outside colder than inside, sustained 30 min (avoids flapping)
value_template: >
{{ (states('sensor.room_temp_main') | float(-99)) -
(states('sensor.outdoor_temp') | float(99)) > 0 }}
for: "00:30:00"
id: outside_colder
- trigger: template
# outside got warm again, sustained 30 min (re-arms the day/evening branches)
value_template: >
{{ (states('sensor.outdoor_temp') | float(-99)) -
(states('sensor.room_temp_main') | float(99)) > 0 }}
for: "00:30:00"
id: outside_warmer
- trigger: numeric_state
entity_id: sensor.outdoor_temp
below: 30.01
- trigger: numeric_state
entity_id: sensor.room_temp_main
above: 26
id: threshold_day
- trigger: numeric_state
entity_id: sensor.room_temp_main
above: 24
id: threshold_evening
conditions: []
actions:
- choose:
# --- OFF: too cold, always wins regardless of scenario/time ---
- conditions:
- condition: trigger
id: too_cold
sequence:
- action: script.ac_turn_off_verified
data:
entity_id: climate.your_ac
branch_name: "too cold"
# --- ON: night overheat safety net (22:00-08:00, >28°C) ---
- conditions:
- condition: state
entity_id: input_select.presence_scenario
state: Home
- condition: time
after: "22:00:00"
before: "08:00:00"
- condition: numeric_state
entity_id: sensor.room_temp_main
above: 28
sequence:
- action: script.ac_set_cooling_verified
data:
entity_id: climate.your_ac
target_temp: 26
branch_name: "night overheat safety net"
# --- ON: daytime overheat override, ignores the free-cooling gate ---
- conditions:
- condition: state
entity_id: input_select.presence_scenario
state: Home
- condition: time
after: "08:00:00"
before: "18:00:00"
- condition: numeric_state
entity_id: sensor.room_temp_main
above: 28
sequence:
- action: script.ac_set_cooling_verified
data:
entity_id: climate.your_ac
target_temp: 28
branch_name: "daytime overheat override"
# --- OFF: free cooling (outside colder than inside for >=30min) ---
- conditions:
- condition: state
entity_id: input_select.presence_scenario
state: Home
- condition: numeric_state
entity_id: sensor.room_temp_main
below: 28
- condition: template
value_template: >
{{ (states('sensor.room_temp_main') | float(-99)) -
(states('sensor.outdoor_temp') | float(99)) > 0 }}
sequence:
- action: script.ac_turn_off_verified
data:
entity_id: climate.your_ac
branch_name: "free cooling"
# --- OFF: night window / scenario change, 22:00-08:00 ---
- conditions:
- condition: trigger
id: [night, scenario]
- condition: state
entity_id: input_select.presence_scenario
state: Home
- condition: time
after: "22:00:00"
before: "08:00:00"
sequence:
- action: script.ac_turn_off_verified
data:
entity_id: climate.your_ac
branch_name: "night / scenario change"
# --- ON: Home evening 18:00-22:00, >24°C ---
- conditions:
- condition: state
entity_id: input_select.presence_scenario
state: Home
- condition: time
after: "18:00:00"
before: "22:00:00"
- condition: numeric_state
entity_id: sensor.room_temp_main
above: 24
- condition: template
value_template: >
{{ ((states('sensor.room_temp_main') | float(-99)) -
(states('sensor.outdoor_temp') | float(99)) <= 0)
or (states('sensor.room_temp_main') | float(-99) >= 28) }}
sequence:
- action: script.ac_set_cooling_verified
data:
entity_id: climate.your_ac
target_temp: 26
branch_name: "evening 18-22h"
# --- ON: Home day 08:00-18:00, >26°C ---
- conditions:
- condition: state
entity_id: input_select.presence_scenario
state: Home
- condition: time
after: "08:00:00"
before: "18:00:00"
- condition: numeric_state
entity_id: sensor.room_temp_main
above: 26
- condition: template
value_template: >
{{ ((states('sensor.room_temp_main') | float(-99)) -
(states('sensor.outdoor_temp') | float(99)) <= 0)
or (states('sensor.room_temp_main') | float(-99) >= 28) }}
sequence:
- action: script.ac_set_cooling_verified
data:
entity_id: climate.your_ac
target_temp: 28
branch_name: "day 08-18h"
# --- OFF: Away, outside colder, unless room is genuinely overheating ---
- conditions:
- condition: state
entity_id: input_select.presence_scenario
state: Away
- condition: numeric_state
entity_id: sensor.room_temp_main
below: 28
- condition: template
value_template: >
{{ (states('sensor.room_temp_main') | float(-99)) -
(states('sensor.outdoor_temp') | float(99)) > 0 }}
sequence:
- action: script.ac_turn_off_verified
data:
entity_id: climate.your_ac
branch_name: "Away - outside colder"
# --- ON: Away, >26°C regardless of outside temp ---
- conditions:
- condition: state
entity_id: input_select.presence_scenario
state: Away
- condition: numeric_state
entity_id: sensor.room_temp_main
above: 26
sequence:
- action: script.ac_set_cooling_verified
data:
entity_id: climate.your_ac
target_temp: 28
branch_name: "Away"
# --- ON: Vacation, either room >30°C ---
- conditions:
- condition: state
entity_id: input_select.presence_scenario
state: Vacation
- condition: or
conditions:
- condition: numeric_state
entity_id: sensor.room_temp_a
above: 30
- condition: numeric_state
entity_id: sensor.room_temp_b
above: 30
sequence:
- action: script.ac_set_cooling_verified
data:
entity_id: climate.your_ac
target_temp: 28
branch_name: "Vacation"
# --- OFF: Vacation, outside AND both rooms back under 30°C ---
- conditions:
- condition: state
entity_id: input_select.presence_scenario
state: Vacation
- condition: numeric_state
entity_id: sensor.outdoor_temp
below: 30.01
- condition: numeric_state
entity_id: sensor.room_temp_a
below: 30.01
- condition: numeric_state
entity_id: sensor.room_temp_b
below: 30.01
sequence:
- action: script.ac_turn_off_verified
data:
entity_id: climate.your_ac
branch_name: "Vacation"
# ============================================================
# Script: set cooling, with retry + notification on failure
# ============================================================
---
alias: "AC - set cooling (retry + verify)"
fields:
entity_id:
description: "Climate entity to set"
example: climate.your_ac
target_temp:
description: "Target temperature (cool mode)"
example: 28
branch_name:
description: "Name of the calling branch, for the failure notification"
example: "day 08-18h"
sequence:
- repeat:
count: 3
sequence:
- if:
- condition: not
conditions:
- condition: and
conditions:
- condition: template
value_template: "{{ states(entity_id) == 'cool' }}"
- condition: template
value_template: "{{ state_attr(entity_id, 'temperature') == target_temp }}"
then:
# Only send hvac_mode if we're not already in cool - preserves the Pow-bit fix
# while still correcting the temperature if mode is already right but temp is wrong.
- if:
- condition: template
value_template: "{{ states(entity_id) != 'cool' }}"
then:
- action: climate.set_hvac_mode
target:
entity_id: "{{ entity_id }}"
data:
hvac_mode: cool
- delay: "00:00:02"
- action: climate.set_temperature
target:
entity_id: "{{ entity_id }}"
data:
temperature: "{{ target_temp }}"
- delay: "00:00:15"
- if:
- condition: not
conditions:
- condition: and
conditions:
- condition: template
value_template: "{{ states(entity_id) == 'cool' }}"
- condition: template
value_template: "{{ state_attr(entity_id, 'temperature') == target_temp }}"
then:
- action: notify.notify # swap for your own notify service
data:
title: "⚠️ AC - failed to set"
message: "{{ entity_id }} did not reach the target state after 3 tries (branch: {{ branch_name }}, target {{ target_temp }}°C)"
mode: queued
max: 5
# ============================================================
# Script: turn off, with retry + notification on failure
# ============================================================
---
alias: "AC - turn off (retry + verify)"
fields:
entity_id:
description: "Climate entity to turn off"
example: climate.your_ac
branch_name:
description: "Name of the calling branch, for the failure notification"
example: "night"
sequence:
- repeat:
count: 3
sequence:
- if:
- condition: template
value_template: "{{ states(entity_id) != 'off' }}"
then:
- action: climate.set_hvac_mode
target:
entity_id: "{{ entity_id }}"
data:
hvac_mode: "off"
- delay: "00:00:15"
- if:
- condition: template
value_template: "{{ states(entity_id) != 'off' }}"
then:
- action: notify.notify # swap for your own notify service
data:
title: "⚠️ AC - failed to turn off"
message: "{{ entity_id }} did not turn off after 3 tries (branch: {{ branch_name }})"
mode: queued
max: 5
Design notes / gotchas that cost me debugging time
choose:picks the first matching branch — if two branches’ time windows overlap, order matters. I had an “overheat” branch (08:00-22:00) sitting above an “evening” branch (18:00-22:00); any overheat event in the evening window was silently handled by the wrong branch with the wrong target temperature. Fix: keep time windows non-overlapping, and order overheat branches so they only cover the periods where nothing else already handles overheat.- Retry loop must check both mode and temperature, not just mode — otherwise a unit that’s already
coolat the wrong temp looks “already correct” and never gets corrected. - An automation can’t reliably catch its own transition to
on— this is a known Home Assistant core limitation (the entity state flips before the automation’s own trigger listeners are re-attached). If you need “evaluate immediately when this automation gets (re-)enabled,” use a second, always-on watcher automation that listens for the first automation’sstate: onand callsautomation.triggerwithskip_condition: trueon it. - Add explicit trigger IDs for threshold crossings, even if a branch’s conditions don’t need a trigger filter. I initially only had
above: 30/below: 28triggers plus a couple of relative (outside-vs-inside) template triggers; there was a window where the room could slowly cross 26°C or 24°C with nothing to re-trigger evaluation, so the automation just never re-checked.
Happy to answer questions if anyone’s fighting similar integration quirks or building something along these lines.