Aqara E1 External Temperature Calibration

Aqara E1 External Temperature Calibration

This blueprint synchronizes an external temperature sensor with the Aqara Smart Radiator Thermostat E1 via Zigbee2MQTT.

Import Blueprint

Why use this?

Unlike software-based solutions (like Better Thermostat), this blueprint feeds the temperature directly into the Aqara hardware. This allows the TRV to use its own internal Proportional-Integral (PI) logic while benefiting from a more accurate sensor placement away from the radiator.

Prerequisites

  • Zigbee2MQTT: You must set the sensor setting to external in the “Exposes” tab of your device in Z2M.
  • MQTT: Home Assistant must be connected to the same broker as Z2M.

Features

  • Integer Rounding: Automatically handles the Aqara E1 requirement for whole numbers.
  • Keep-Alive Heartbeat: Sends a periodic update to prevent the TRV from reverting to its internal sensor.
  • Change Limiting: Prevents sudden spikes that might trigger false window-open detection.
  • Robust Logic: Handles “unavailable” or “unknown” states gracefully without crashing.

Installation

Click the “Import Blueprint” button above and paste your Gist URL, or manually add the YAML to your blueprints/automation folder.

Crucial Zigbee2MQTT users: force_update

By default, Zigbee2MQTT only tells Home Assistant when a temperature changes. If your room stays at exactly 21°C for two hours, Z2M sends nothing. After 120 minutes of silence, the Aqara E1 will think the connection is lost and revert to its internal sensor.

To fix this, you must tell Z2M to report the temperature even if it hasn’t changed:

  1. Open your Zigbee2MQTT configuration folder (usually via the File Editor or Samba).
  2. Open devices.yaml.
  3. Find your External Temperature Sensor (not the TRV) and add force_update: true to its entry.

Example:

'0x00158d0001234567':
  friendly_name: Living Room T-RH
  force_update: true

The Code

Click to view the Blueprint YAML
blueprint:
  domain: automation
  name: Aqara E1 External Temperature Sensor Calibration
  author: Proxo Nox
  description: |
    # Aqara Smart Radiator Thermostat E1 External Temperature Sensor Calibration
    
    **Inspired by and based on** the "Sonoff TRVZB External Temperature Sensor Calibration" blueprint by photomoose (Tom Davis).
    
    ## Important Distinction: What This Blueprint Does vs. Better Thermostat
    
    This blueprint is **not** a replacement for Better Thermostat. They serve different purposes:
    
    | **This Blueprint** | **Better Thermostat** |
    |-------------------|----------------------|
    | Feeds external temperature **directly to the thermostat hardware** | Creates a virtual thermostat in software |
    | Thermostat uses its own algorithms and schedules | HA handles all control logic |
    | Works even if Home Assistant is down (once configured) | Requires HA to be running constantly |
    | Native integration with the device's features | More flexible control options |
    
    **Use this blueprint when:** You want the Aqara E1 to use its own scheduling and algorithms but with a more accurately placed temperature sensor.
    
    **Use Better Thermostat when:** You want Home Assistant to handle all temperature control logic across multiple rooms/zones.
    
    ## Description
    
    This blueprint synchronises the temperature reading of an Aqara Smart Radiator Thermostat E1 with an external temperature sensor. When configured in "external sensor" mode in Zigbee2MQTT, the Aqara E1 completely bypasses its internal temperature sensor and relies entirely on values sent to its `external_temperature_input`.
    
    ## How It Works
    
    Whenever your external temperature sensor reports a new value, this automation:
    1. Reads the temperature from your external sensor
    2. Validates it's a proper number (ignores unavailable/unknown states)
    3. Rounds it to the nearest whole number (the Aqara E1 accepts only integer values)
    4. Optionally limits how much the temperature can change at once (prevents false window detection)
    5. Sends the value directly to the thermostat via MQTT
    
    Additionally, a keep-alive timer ensures the thermostat never reverts to its internal sensor during periods of constant temperature.
    
    ## Important for Zigbee2MQTT Users:
    To prevent the thermostat from reverting to the internal sensor during stable temperatures, 
    enable `force_update: true` for your external temperature sensor in the Zigbee2MQTT 
    `devices.yaml` file. This ensures the "Keep-alive" heartbeat always has a value to send.
    
    ## Requirements
    
    - **Hardware**: Aqara Smart Radiator Thermostat E1 (firmware latest)
    - **Integration**: Zigbee2MQTT (latest version) with MQTT configured in Home Assistant
    - **Configuration**: Thermostat must be set to `sensor: external` in Zigbee2MQTT device settings
    - **External Sensor**: Any temperature sensor that reports to Home Assistant (Zigbee, WiFi, etc.)
    
    ## Features
    
    - **Integer rounding**: Automatically rounds temperatures to nearest whole number (required by device)
    - **Change limiting**: Prevents sudden temperature changes that could trigger false window detection
    - **Timeout prevention**: Regular updates ensure thermostat never reverts to internal sensor
    - **Flexible naming**: You provide the exact Zigbee2MQTT device name for reliable MQTT communication
    - **Validation**: Ensures values stay within valid range (0-55°C)
    - **Defensive programming**: Handles network hiccups, unavailable entities, and missing attributes gracefully
    - **Debug logging**: Optional logging to help troubleshoot
    
    ## Configuration in Zigbee2MQTT
    
    Before using this blueprint, you must configure your Aqara E1 in Zigbee2MQTT:
    
    1. Go to Zigbee2MQTT → Devices → Your Aqara E1
    2. Find the `sensor` setting (under "Exposes")
    3. Change it from `internal` to `external`
    4. The device will now listen to the `external_temperature_input` for temperature values
    
    ## Blueprint Inputs
    
    | Input | Description |
    |-------|-------------|
    | External Temperature Sensor | The sensor entity that provides the room temperature |
    | Aqara E1 Climate Entity | The climate entity of your Aqara E1 thermostat |
    | Zigbee2MQTT Device Name | The exact device name as shown in Zigbee2MQTT (e.g., "living_room_valve" or "0x54ef4410009f546f") |
    | Maximum Temperature Change | Optional limit to prevent window detection (set 0 to disable) |
    | Update Frequency | How often to send updates during constant temperatures |
    | Enable Debug Logging | Turn on/off system log entries |
    
    ## Version History
    - v1.7 - Final stable release with robust datetime and attribute handling.

    ---
    *Based on the original Sonoff TRVZB blueprint by photomoose (Tom Davis). Adapted for Aqara E1 with community contributions.*

  input:
    external_temperature_sensor:
      name: External Temperature Sensor
      description: "The sensor entity that provides the accurate room temperature"
      selector:
        entity:
          filter:
            - domain: sensor
              device_class: temperature
          multiple: false

    aqara_climate_entity:
      name: Aqara E1 Climate Entity
      description: "The climate entity of your Aqara E1 thermostat"
      selector:
        entity:
          filter:
            - domain: climate
          multiple: false

    zigbee2mqtt_device_name:
      name: Zigbee2MQTT Device Name
      description: "The exact friendly name or IEEE address in Zigbee2MQTT."
      selector:
        text:

    max_temperature_change:
      name: Maximum Temperature Change (per update)
      description: "Limits rapid changes. Set to 0 to disable."
      default: 3
      selector:
        number:
          min: 0
          max: 10
          step: 1
          unit_of_measurement: "°C"
          mode: slider

    update_frequency:
      name: Update Frequency (minutes)
      description: "Keep-alive interval to prevent reversion to internal sensor."
      default: 30
      selector:
        number:
          min: 5
          max: 120
          step: 5
          unit_of_measurement: "minutes"
          mode: slider

    enable_debug:
      name: Enable Debug Logging
      default: false
      selector:
        boolean:

trigger_variables:
  external_sensor: !input external_temperature_sensor
  climate_entity: !input aqara_climate_entity
  device_name: !input zigbee2mqtt_device_name
  max_change: !input max_temperature_change
  update_mins: !input update_frequency
  debug: !input enable_debug

triggers:
  - trigger: state
    entity_id: !input external_temperature_sensor
  - trigger: time_pattern
    minutes: "/5"

variables:
  climate_state_raw: "{{ states[climate_entity] }}"
  
  current_thermostat_temp: >-
    {% set temp = state_attr(climate_entity, 'current_temperature') %}
    {{ temp | float(20) if temp is not none else 20 }}

  last_changed_datetime: >-
    {% if climate_state_raw is not none and climate_state_raw is not string %}
      {{ climate_state_raw.last_changed }}
    {% else %}
      {{ now() }}
    {% endif %}

  minutes_since_last: >-
    {% set last = last_changed_datetime | as_datetime(now()) %}
    {{ ((now() - last).total_seconds() / 60) | int }}
  
  should_run: >-
    {% if trigger.platform == 'state' %}
      true
    {% elif trigger.platform == 'time_pattern' %}
      {{ minutes_since_last >= update_mins }}
    {% else %}
      false
    {% endif %}
  
  raw_state: "{{ states(external_sensor) }}"
  
  valid_temperature: >-
    {% if raw_state not in ['unavailable', 'unknown', 'none'] and raw_state is not none and raw_state != '' %}
      {% if raw_state | float(none) is not none %}
        true
      {% else %}
        false
      {% endif %}
    {% else %}
      false
    {% endif %}
  
  base_temperature: >-
    {% if valid_temperature %}
      {{ raw_state | float(0) }}
    {% else %}
      {{ current_thermostat_temp }}
    {% endif %}
  
  rounded_temperature: "{{ base_temperature | round(0) | int }}"
  
  limited_temperature: >-
    {% if max_change | int > 0 and current_thermostat_temp > 0 %}
      {% set diff = (rounded_temperature - current_thermostat_temp) | abs %}
      {% if diff > max_change %}
        {% if rounded_temperature > current_thermostat_temp %}
          {{ (current_thermostat_temp + max_change) | int }}
        {% else %}
          {{ (current_thermostat_temp - max_change) | int }}
        {% endif %}
      {% else %}
        {{ rounded_temperature | int }}
      {% endif %}
    {% else %}
      {{ rounded_temperature | int }}
    {% endif %}
  
  safe_temperature: >-
    {% if limited_temperature < 0 %}
      {{ 0 }}
    {% elif limited_temperature > 55 %}
      {{ 55 }}
    {% else %}
      {{ limited_temperature }}
    {% endif %}
  
  temperature_changed: "{{ safe_temperature != current_thermostat_temp }}"

actions:
  - condition: template
    value_template: "{{ should_run }}"
  
  - condition: template
    value_template: "{{ valid_temperature or trigger.platform == 'time_pattern' }}"
  
  - choose:
      - conditions:
          - "{{ temperature_changed or debug or trigger.platform == 'time_pattern' }}"
        sequence:
          - action: mqtt.publish
            data:
              topic: "zigbee2mqtt/{{ device_name }}/set"
              payload: "{\"external_temperature_input\": {{ safe_temperature }}}"
              qos: 1
              retain: false
          
          - if:
              - "{{ debug }}"
            then:
              - action: system_log.write
                data:
                  level: info
                  message: >-
                    Aqara E1 Update - Sent: {{ safe_temperature }}°C | Prev: {{ current_thermostat_temp }}°C | Trigger: {{ trigger.platform }}
mode: single
max_exceeded: silent