HA Based Hive Heating alternative

Hi all,

I'm trying to step deeper into HA life. After the initial install/setup, and getting a viable Zigbee network setup, my next goal is to build something like Hive Heating but using my local HA rather than their cloud based setup.

I'm already a Hive user, with the main thermostat & 5x Hive TRVs in the key rooms (there's also a handful of Hive blubs but we can ignore those for this topic.

In addition to this the Zig network consists of a pair of Sonoff Dongle Plus (1x CoOrdinator, 1x Router), Sonoff smart plug, 4x Sonoff room thermostats)
Note that I will swap out the Hive TRVs for Zigbee units at some point, but this doesn't affect the setup as best I can tell as it would just be entity changes to whatever I get setup

After long discussions with OpenAI it's pointing me towards the following:
Scheduler (HA)
HACS > Scheduler Card
HACS > Better Thermostat

What I've done so far is get the Better Thermostats configured for the rooms with TRVs.
I'm using the TRVs as the control, and the Sonoff as the temp reading.
Note that the living room as two rads, but the Better Thermostat is configured with both these & a single Sonoff thermostat as the temp source of truth.

I've also used Climate scheduler to create the time window & target temp per room.

So I now have the core functions similar to Hive.
I'm only testing in 1 room, as I need to keep Hive running until I have this all finalised - so that the rest of the family can still interact with Hive in the interim.

What I'm trying to do now is build something akin to the Hive "boost" functionality.

Initial rabbit hole so far
input_number helpers to save the "pre-boost" temp.
toggle helper to give a boolean for boost ACTIVE or not
timer helper for the boost duration

Then some automation for the boost itself

  1. for the logic of the boost
    I wanted some logic in the boost, so that it checks the current temp, and then boosts to a sensible value. The main idea is that ultimately I will have a dedicated HA dashboard for the family to use, and they can just click/touch the specific room boost card and it will work out the boost target.
  2. For logic to return target temp to whatever it was prior (basically the current schedule target)

This way if it gets to the boost target within the time it will turn the stop the call for heat. Rather than boost to 26c for 1hr - with the potential to turn the room into a hot-box

All the helpers & automations seem overly complex, but this maybe just because I'm new to this (and relying on AI for pointers, help, and YAML coding help/validation)

Is AI steering me down the wrong path. Is there something out there I've not found yet that is more simple, am I thinking its too complex when its just my HA ignorance

TIA

Ad

Sounds quite similar to what I did when my Nest Gen 2 Thermostat was announced to be reaching EoL last year.

I switched to a very basic Tado X setup (Temperature Sensor / Thermostat and link to boiler) as I already had z-wave temperature sensors everywhere and the house is open plan.

We effectively control the heating entirely through HA, but have a fallback schedule set in Tado just in case.

At a very high level:

  • HA heating schedule matching tado schedule with default temperature "mode" and value
    e.g.
    tado_heating_schedule:
      name: "Tado Heating Schedule"
      monday:
        - from: "00:00:00"
          to: "07:00:00"
          data:
            mode: night
            temperature: 18
    
  • input numbers per "mode" offered as sliders on the heating dashboard to allow configuration away from default
  • "comfort" mode toggle on dashboard
  • an input number slider for outdoor temperature to act as a trigger for boost
  • "watchdog" schedule triggers an automation that assesses various factors (occupancy, comfort mode toggle, internal temps, etc) to adjust the set temperature
  • a home/away automation triggered by occupancy and proximity changes to simulate auto away functionality from Nest
  • a morning "boost" automation triggered based on phone alarm times based on outdoor temps etc to bring temps up first thing - think weather compensation functionality from Nest

The above has matured pretty well with extended soak testing - works well for us.

This is nice info - thanks for the details.

I'm using my son's room as the "test" area as he's away at Uni.

I've got the Better Thermostat working, using SNZB-02P as the "temp sensor" & the Hive TRV as "the real thermostat" element.

Alongside this I have used the Scheduler Card from HACS to create a basic daily schedule.

I tried to create a BOOST function, which essentially worked, but then I think I over-engineered it.
Using Custom Card as I wanted to include a timer in the card when clicked.
I've since added Time Bar Card from HACS, and set this to only appear when the boolean "boost" helper is active after clicking the Boost Button.

Button basically does what I want, but its not triggering the actual BetterThermo to take the boost input temp, so need to dig through the code and make sure I don't have any entity typos.

The Better Thermostat has a nice "outside" temp setting so it will block HEAT if its above XXc outside, but I don't have a dedicate outside sensor yet, so using Weather integration for that value at the moment.

@TazUk - would be interested how you've done "Boost". Was it a Tado feature/entity, or home-grown code/automations?

Here's the code for the BOOST so far (note is a redacted entity name...if you didn't guess already)

sequence:
  - action: input_number.set_value
    target:
      entity_id: input_number.<child>_preboost_temp
    data:
      value: "{{ state_attr('climate.jld_bedroom', 'temperature') }}"
  - action: climate.set_temperature
    metadata: {}
    target:
      entity_id: climate.<child>_bedroom_thermostat
    data:
      hvac_mode: heat
      temperature: >
        {% set t =
        state_attr('climate.<child>_bedroom_thermostat','current_temperature') |
        float %} {% if t < 18 %}
          20
        {% elif t < 20 %}
          22
        {% elif t < 22 %}
          25
        {% else %}
          25
        {% endif %}
  - action: timer.start
    metadata: {}
    target:
      entity_id: timer.<child>_boost_timer
    data: {}
alias: <child> - Boost - 1hr
description: ""

Boost mode is implemented as a one of the heating modes, configurable through the dashboard and set in the schedule / overridden by the heating watchdog automation and a morning "boost" based on phone alarms and outdoor temps etc.
The "Comfort Mode" switch controls whether anything can override the heating schedule.

Dashboard items should help explain:

The morning boost is controlled with an automation:

# boost thermostat in the morning based on wakeup time / default 8am
# calculated in a trigger template at 1am each morning
- id: tado_thermostat_boost_morning
  alias: "Thermostat Morning Boost"
  mode: single
  triggers:
    - trigger: time
      at: sensor.thermostat_boost_morning_trigger
      id: "start"
    - trigger: time
      at: sensor.thermostat_boost_morning_end
      id: "end"
  conditions:
    - condition: and
      conditions:
        - condition: state
          entity_id: input_boolean.holiday_mode
          state: "off"
        - condition: state
          entity_id:
            - binary_sensor.tado_thermostat
            - binary_sensor.house_occupied
          state: "on"
        - condition: or
          conditions:
            - condition: and
              conditions:
                - condition: trigger
                  id: "start"
                - condition: state
                  entity_id: input_boolean.heating_comfort_mode
                  state: "on"
                - condition: numeric_state
                  entity_id: sensor.house_temperature_mean
                  value_template: "{{ (state.state | float(21.0)) - 0.5 }}"
                  below: input_number.heating_boost_temp
                - condition: numeric_state
                  entity_id: climate.thermostat
                  attribute: current_temperature
                  value_template: "{{ (state.state | float(21.0)) - 0.5 }}"
                  below: input_number.heating_boost_temp
                - condition: numeric_state
                  entity_id:
                    - sensor.composite_feels_like_temperature
                    - sensor.decking_multisensor_temperature
                  below: input_number.heating_outdoor_trigger_temp
            - condition: and
              conditions:
                - condition: trigger
                  id: "end"
                - condition: state
                  entity_id: binary_sensor.heating_morning_boost_active
                  state: "on"
  actions:
    - variables:
        mode: "{{ 'boost' if trigger.id == 'start' else state_attr('schedule.tado_heating_schedule', 'mode') }}"
        temp_set: "{{ 'input_number.heating_foo_temp' | replace('foo', mode ) }}"
        temp_default: "{{ '21' if trigger.id == 'start' else state_attr('schedule.tado_heating_schedule', 'temperature') }}"
    - action: climate.set_temperature
      target:
        entity_id: climate.thermostat
      data:
        hvac_mode: heat
        temperature: "{{ temp_set | float(temp_default) }}"

and the triggers for that automation are built based on (Android) phone alarms - sensors below (renamed to redact):

# heating boost in the mornings - calculate once per day only
# start based on alarm - 45 minutes
# end based on start + 2 hours
- triggers:
    - trigger: state
      entity_id:
        - sensor.me_next_alarm
        - sensor.so_next_alarm
    - trigger: time
      at:
        - "10:00"
        - "18:00"
    - trigger: event
      event_type: event_template_reloaded
  sensor:
    - name: "Thermostat Boost Morning Trigger"
      unique_id: thermostat_boost_morning_trigger
      device_class: timestamp
      state: >-
        {%- set default_start = '08:00' -%}
        {%- set last_trigger = state_attr('automation.thermostat_boost_morning', 'last_triggered') | as_datetime | as_local -%}
        {%- if last_trigger is none -%}
          {%- set last_trigger = (today_at(default_start) - timedelta(days = 1)) | as_local -%}
        {%- endif -%}
        {%- set target = today_at(default_start) | as_local -%}
        {%- if target <= now() -%}
           {%- set target = (target + timedelta(days = 1)) | as_local -%}
        {%- endif -%}
        {%- if states('sensor.me_next_alarm') | as_datetime(none) is not none -%}
           {%- set me_alarm = states('sensor.me_next_alarm') | as_datetime | as_local -%}
           {%- if (me_alarm.date() == target.date()) and (me_alarm < target) -%}
              {%- set target = me_alarm -%}
           {%- endif -%}
        {%- endif -%}
        {%- if states('sensor.so_next_alarm') | as_datetime(none) is not none -%}
           {%- set so_alarm = states('sensor.so_next_alarm') | as_datetime | as_local -%}
           {%- if (so_alarm.date() == target.date()) and (so_alarm < target) -%}
              {%- set target = so_alarm -%}
           {%- endif -%}
        {%- endif -%}
        {%- if (target - last_trigger) < timedelta(hours = 3) -%}
           {{ last_trigger.isoformat() }}
        {%- else -%}
           {{ ((target - timedelta(minutes = 45)) | as_local).isoformat() }}
        {%- endif -%}

- triggers:
    - trigger: time
      at: sensor.thermostat_boost_morning_trigger
    - trigger: event
      event_type: event_template_reloaded
  sensor:
    - name: "Thermostat Boost Morning End"
      unique_id: thermostat_boost_morning_end
      device_class: timestamp
      state: "{{ states('sensor.thermostat_boost_morning_trigger') | as_datetime(none) | as_local + timedelta(hours = 2) }}"

For completeness:

Watchdog automation (controlled by a homeassistant schedule)

# the tado app controls the heating schedule
# homeassistant overrides when:
# - we are away from the house or on holiday (substitute for auto away)
# - we want a temporary boost
# the tado app is set to resume schedule automatically after temperature adjustments
# the tado app controls the heating schedule
# homeassistant overrides when:
# - we are away from the house or on holiday (substitute for auto away)
# - we have "comfort mode" enabled to allow higher than scheduled temperature based on configuration exposed in the UI.
# the tado app is set to resume the schedule automatically after temperature adjustments
- id: tado_thermostat_heating_watchdog
  alias: "Thermostat Heating Watchdog"
  mode: single
  max_exceeded: silent
  triggers:
    - trigger: time
      at: sensor.thermostat_watchdog_trigger
  conditions:
    - condition: and
      conditions:
        - condition: state
          entity_id: binary_sensor.tado_thermostat
          state: "on"
        - condition: or
          conditions:
            # away conditions: holiday mode / everyone working at office / family proximity + thermostat not in night mode
            - condition: state
              entity_id: input_boolean.holiday_mode
              state: "on"
            - condition: state
              entity_id: binary_sensor.family_wfo
              state: "on"
            - condition: and
              conditions:
                - condition: numeric_state
                  entity_id: sensor.proximity_home_family_miles
                  above: 19
                - condition: not
                  conditions:
                    - condition: state
                      entity_id: schedule.tado_heating_schedule
                      attribute: mode
                      state: "night"
            # comfort conditions: not holiday mode, house_occupied, mode enabled, and house not to temp
            - condition: and
              conditions:
                - condition: state
                  entity_id: input_boolean.holiday_mode
                  state: "off"
                - condition: state
                  entity_id: binary_sensor.house_occupied
                  state: "on"
                - condition: state
                  entity_id: input_boolean.heating_comfort_mode
                  state: "on"
                - condition: not
                  conditions:
                    - condition: state
                      entity_id: schedule.tado_heating_schedule
                      attribute: mode
                      state: "night"
                - condition: numeric_state
                  entity_id: sensor.house_temperature_mean
                  value_template: "{{ (state.state | float(21.0)) - 0.5 }}"
                  below: input_number.heating_boost_temp
                - condition: numeric_state
                  entity_id: climate.thermostat
                  attribute: current_temperature
                  value_template: "{{ (state.state | float(21.0)) - 0.5 }}"
                  below: input_number.heating_boost_temp
                - condition: numeric_state
                  entity_id:
                    - sensor.composite_feels_like_temperature
                    - sensor.decking_multisensor_temperature
                  below: input_number.heating_outdoor_trigger_temp
  actions:
    - variables:
        heating_mode: >-
          {%- if is_state('input_boolean.holiday_mode', 'on') or is_state('binary_sensor.family_wfo', 'on') or (states('sensor.proximity_home_family_miles')|float(0) >= 20) -%}away
          {%- elif is_state('input_boolean.holiday_mode', 'off') and is_state('input_boolean.heating_comfort_mode', 'on') -%}boost
          {%- else -%}home
          {%- endif -%}
        temp_set: "{{ states('input_number.heating_foo_temp' | replace('foo', heating_mode)) }}"
        temp_default: >-
          {%- if heating_mode in ['away','night'] -%}18
          {%- elif heating_mode == 'home' -%}20
          {%- else -%}21
          {%- endif -%}
    - action: climate.set_temperature
      target:
        entity_id: climate.thermostat
      data:
        hvac_mode: heat
        temperature: "{{ temp_set | float(temp_default) }}"

and the "auto away" automation:

# set / clear eco mode on thermostat:
# - when holiday mode is enabled for 30 minutes / disabled (make sure eco mode stays enabled if disabled when on holiday mode)
# - when family is a minimum of 20 miles away for more than 30 minutes then revert when distance drops
# - when family at the office for a workday then revert when first person leaves the office
- id: tado_thermostat_away_toggle
  alias: "Thermostat Away Toggle"
  mode: single
  max_exceeded: silent
  triggers:
    # holiday mode controls
    - trigger: state
      entity_id: input_boolean.holiday_mode
      from: "off"
      to: "on"
      for:
        minutes: 30
      id: "away"
    - trigger: state
      entity_id: input_boolean.holiday_mode
      from: "on"
      to: "off"
      id: "home"
    # distance controls
    - trigger: numeric_state
      entity_id: sensor.proximity_home_family_miles
      above: 20
      for:
        minutes: 30
      id: "away"
    - trigger: numeric_state
      entity_id: sensor.proximity_home_family_miles
      below: 20
      for:
        minutes: 5
      id: "home"
    # workday in office controls
    - trigger: state
      entity_id: binary_sensor.family_wfo
      to: "on"
      for:
        minutes: 30
      id: "away"
    - trigger: state
      entity_id: binary_sensor.family_wfo
      to: "off"
      for:
        minutes: 5
      id: "home"
  conditions:
    - condition: and
      conditions:
        - condition: state
          entity_id: binary_sensor.tado_thermostat
          state: "on"
        - condition: or
          conditions:
            # away conditions
            - condition: and
              conditions:
                - condition: trigger
                  id: "away"
                - condition: numeric_state
                  entity_id: climate.thermostat
                  attribute: temperature
                  above: input_number.heating_away_temp
            # home conditions
            - condition: and
              conditions:
                - condition: trigger
                  id: "home"
                - condition: numeric_state
                  entity_id: climate.thermostat
                  attribute: temperature
                  below: input_number.heating_home_temp
                - condition: time
                  after: "07:00:00"
                  before: "22:00:00"
  actions:
    - variables:
        temp_set: "{{ states('input_number.heating_foo_temp' | replace('foo', trigger.id)) }}"
        temp_default: "{{ 20 if trigger.id == 'home' else 18 }}"
    - action: climate.set_temperature
      target:
        entity_id: climate.thermostat
      data:
        hvac_mode: heat
        temperature: "{{ temp_set | float(temp_default) }}"

Most of this evolved over time - I started with the base scheduling and then gradually rolled out additions as I saw the need (e.g. the morning heating boost was implemented during the first winter with the tado).

Bit of an info dump so apologies for that :smiley: