neon.ninja

Sharing enlightening moments of home automation

Regain Local Control to Smart Panel Heater

It’s time for an update on my Arlec panel heater (Model PEH223HA) after I partially lost control of the device in Home Assistant. So, here are the steps that I took to regain access.

Symptoms

  • The climate device in Home Assistant was still showing up, so the MQTT integration was still working fine in principal.
  • Changing the Mode or Fan Mode did not work anymore; I could still select a different option, but it would change back to the previous value after a couple of seconds, and there was clearly no change on the device itself.
  • I was not able to change the target temperature anymore.

Tasmota update

The first thing I did is update the device itself to the latest Tasmota version. I originally intalled version 9.4.0 and shortly after updated to 9.5.0 but that is where I stayed for the last two years.

  1. Backup your configuration. In the device’s web UI click Configuration -> Backup Configuration
  2. Download and install minimal Tasmota binary. This step is required due to the limited memory on the device itself. After installing the minimal binary the only next step you can take is to install another binary.
  3. Download and install standard (without any customisations) Tasmota binary.

After the update I just quickly checked that all the configuration was still there, and indeed the device was still configured just as before the update, but I still had not regained full control in Home Assistant.

Trouble-shooting

To trouble-shoot what was going on I looked at three different things:

  1. I looked into the device log. Click Console button in the device’s web UI, and messages show up. What I immediately noticed here was that the serial messages received from the MCU (that controls the thermostat) where incomplete (Example: {"SSerialReceived":"). So that was a first hint that something was wrong in the serial communication.
  2. I then used mosquitto_sub to listen to any messages exchanged on my MQTT broker. And here those same serial messages appeared wrong, too (Example: {"SSerialReceived":"A~"}). All messages received clearly were not HEX encoded.
  3. I also closely observed the Home Assistant UI when making changes to the climate entity. Toast messages appeared in the bottom left indicating that something may be configured incorrectly in the entity itself. Some messages were clearly related to the above issue where I was expecting a HEX encoded value which Home Assistant was attempting to decode but of course failed. Other messages were indicating that I may have missed a update or breaking change message in one of the many releases since my original implementation.

Fixing serial communication

After reading through issue reports in Tasmota’s GitHub repository I found hints that some time in early 2022 a setting related to serial communication was changed that may have broken my configuration. All I had to do is define a serial delimiter to fix this issue; the following command needs to be execute on the device’s Console:

SerialDelimiter 254

After making this configuration change, the serial messages were again issued in HEX format.

Fixing Home Assistant configuration

I made some modifications to my climate entity configuration:

  1. Modernised the YAML configuration: Now, climate goes underneath the mqtt integration. This is actually something I had already done some time after the 2022.06 release where the old way was deprecated.
  2. Added mode_command_topic and mode_command_template

Here is the complete YAML configuration of the climate entity for locally controlling the panel heater.

mqtt:
  climate:
    - name: "Panel Heater Office"
      unique_id: "esp_panel_heater_office_climate"
      availability_topic: "esp_panel_heater_office/tele/LWT"
      payload_available: "Online"
      payload_not_available: "Offline"
      modes:
        - "off"
        - "heat"
      mode_state_topic: "esp_panel_heater_office/tele/RESULT"
      mode_state_template: "{% if value_json['SSerialReceived'][20:22] == '00' %}off{% else %}heat{% endif %}"
      mode_command_topic: "esp_panel_heater_office/cmnd/sserialsend5"
      mode_command_template: >
        {% set modes = { 'off': '02', 'heat': '01' } %}
        {% set fan_modes = { 'off': '00', 'low': '02', 'high': '03', 'anti-frost': '04'} %}
        {{ ["F1F10210", modes[value], "0000000000", fan_modes[state_attr('climate.panel_heater_office', 'fan_mode')], ('%#x' % (state_attr('climate.panel_heater_office', 'temperature') | int))[2:], "01000001000001", (('%#x' % ( 21 + (modes[value] | int) + (fan_modes[state_attr('climate.panel_heater_office', 'fan_mode')] | int) + (state_attr('climate.panel_heater_office', 'temperature') | int)))[2:] | upper), "7E"] | join }}
      power_command_topic: "esp_panel_heater_office/cmnd/sserialsend5"
      payload_on: "F1F10210010000000000000000000001000001157E"
      payload_off: "F1F10210020000000000000000000001000001167E"
      fan_modes:
        - "off"
        - "low"
        - "high"
        - "anti-frost"
      fan_mode_state_topic: "esp_panel_heater_office/tele/RESULT"
      fan_mode_state_template: "{% if value_json['SSerialReceived'][20:22] == '02' %}low{% elif value_json['SSerialReceived'][20:22] == '03' %}high{% elif value_json['SSerialReceived'][20:22] == '04' %}anti-frost{% else %}off{% endif %}"
      fan_mode_command_topic: "esp_panel_heater_office/cmnd/sserialsend5"
      fan_mode_command_template: >
        {% set modes = { 'off': '02', 'heat': '01' } %}
        {% set fan_modes = { 'off': '00', 'low': '02', 'high': '03', 'anti-frost': '04'} %}
        {{ ["F1F10210", modes[states('climate.panel_heater_office')], "0000000000", fan_modes[value], ('%#x' % (state_attr('climate.panel_heater_office', 'temperature') | int))[2:], "01000001000001", (('%#x' % ( 21 + (modes[states('climate.panel_heater_office')] | int) + (fan_modes[value] | int) + (state_attr('climate.panel_heater_office', 'temperature') | int)))[2:] | upper), "7E"] | join }}
      temperature_state_topic: "esp_panel_heater_office/tele/RESULT"
      temperature_state_template: "{{ value_json['SSerialReceived'][22:24] | int(base=16) }}"
      temperature_unit: "C"
      min_temp: 18.0
      max_temp: 30.0
      temperature_command_topic: "esp_panel_heater_office/cmnd/sserialsend5"
      temperature_command_template: >
        {% set modes = { 'off': '02', 'heat': '01' } %}
        {% set fan_modes = { 'low': '02', 'high': '03', 'anti-frost': '04'} %}
        {{ ["F1F10210", modes[states('climate.panel_heater_office')], "0000000000", fan_modes[state_attr('climate.panel_heater_office', 'fan_mode')], ('%#x' % (value | int))[2:], "01000001000001", (('%#x' % ( 21 + (modes[states('climate.panel_heater_office')] | int) + (fan_modes[state_attr('climate.panel_heater_office', 'fan_mode')] | int) + (value | int)))[2:] | upper), "7E"] | join }}
      precision: 1.0
      current_temperature_topic: "esp_panel_heater_office/tele/RESULT"
      current_temperature_template: "{{ value_json['SSerialReceived'][28:30] | int(base=16) }}"

Conclusion

So, after all this, at its core it was probably just the two missing lines in the YAML configuration that prevented me from full local access of the panel heater. The other issue I experienced was probably just due to the decision to update to the latest Tasmota version which required me to add that new serial delimiter.

Either way, I just wanted to share my journey and the configuration that keeps working in my ecosystem with the current Tasmota version and Home Assistant version.

Compatibility
At the time of writing this post, I used:
* Home Assistant 2023.11.2 with Python 3.11.6 in Docker
* Tasmota 13.2.0

Comments

14 responses to “Regain Local Control to Smart Panel Heater”

  1. Will Avatar
    Will

    Followed the steps but getting this error

    Failed to call service climate/set_fan_mode. ValueError: Template error: int got invalid input ‘None’ when rendering template ‘{% set modes = { ‘off’: ’02’, ‘heat’: ’01’ } %} {% set fan_modes = { ‘off’: ’00’, ‘low’: ’02’, ‘high’: ’03’, ‘anti-frost’: ’04’} %} {{ [“F1F10210”, modes[states(‘climate.panel_heater_office’)], “0000000000”, fan_modes[value], (‘%#x’ % (state_attr(‘climate.panel_heater_office’, ‘temperature’) | int))[2:], “01000001000001”, ((‘%#x’ % ( 21 + (modes[states(‘climate.panel_heater_office’)] | int) + (fan_modes[value] | int) + (state_attr(‘climate.panel_heater_office’, ‘temperature’) | int)))[2:] | upper), “7E”] | join }}’ but no default was specified

    * Home Assistant 2023.12.3 with Python 3.11.6 in Docker
    * Tasmota 13.3.0

    1. malte Avatar
      malte

      Just a quick confirmation: Is your panel heater’s climate entity indeed called `climate.panel_heater_office`? If not, you will need to adopt the template and replace all instances of this reference with your actual entity name. If it is called that way, does it have an attribute `temperature` (you can see all attributes via Developer Tools -> States)?

  2. Will Avatar
    Will

    Hi Malte, It is indeed called `climate.panel_heater_office` but I don’t see the attribute for temperature.

    1. malte Avatar
      malte

      OK, I just tried to reproduce your issue by copy&pasting the `fan_mode_command_template` template code into HA’s template editor, and one way to do reproduce the error message “int got invalid input ‘None’…” is to either use a wrong entity name or wrong attribute name in the `state_attr(‘climate.panel_heater_office’, ‘temperature’)` part of the template.

      This and the fact that you can’t see the `temperature` attribute makes me wonder how this can happen? I could imagine that the temperature attribute only appears once a value is posted to the MQTT topic defined in `temperature_state_topic` and successfully parsing the temperature value using the template defined in `temperature_state_template`.

      So, this is where I would go next: Monitor MQTT messages and see if the data you see on the `temperature_state_topic` could be parse by the template to a temperature value.

  3. Will Avatar
    Will

    Would you mind sharing your entity card configuration? I’m wondering if that is where it’s going wrong.

    1. malte Avatar
      malte

      The error message that you are seeing there looks as if it is independent of how the entity is displayed. Anyway, in the screenshot at the top I used a Thermostat card with the following configuration:

      type: thermostat
      entity: climate.panel_heater_office
      show_current_as_primary: true
      features:
        - type: climate-hvac-modes
          hvac_modes:
            - 'off'
            - heat
      
  4. Will Avatar
    Will

    Now that winter is here, I am finally back to trying to resolve this issue. The latest I am still getting the same error message. I am completely lost at this point 🙁
    Failed to call service climate/set_hvac_mode. ValueError: Template error: int got invalid input ‘None’ when rendering template ‘{% set modes = { ‘off’: ’02’, ‘heat’: ’01’ } %} {% set fan_modes = { ‘off’: ’00’, ‘low’: ’02’, ‘high’: ’03’, ‘anti-frost’: ’04’} %} {{ [“F1F10210”, modes[value], “0000000000”, fan_modes[state_attr(‘climate.panel_heater_office’, ‘fan_mode’)], (‘%#x’ % (state_attr(‘climate.panel_heater_office’, ‘temperature’) | int))[2:], “01000001000001”, ((‘%#x’ % ( 21 + (modes[value] | int) + (fan_modes[state_attr(‘climate.panel_heater_office’, ‘fan_mode’)] | int) + (state_attr(‘climate.panel_heater_office’, ‘temperature’) | int)))[2:] | upper), “7E”] | join }}’ but no default was specified

  5. Will Avatar
    Will

    Aha I think I worked it out. After a restart of home assistant I received the above error message. After I sent “esp_panel_heater_office/cmnd/sserialsend5” with payload “F1F10210010000000000000000000001000001157E” I could then control it via home assistant.

    1. malte Avatar
      malte

      That’s good news, I’m glad that it works for you now.

  6. ziptie Avatar
    ziptie

    Awww Stink. Just bought 4 of these heaters from bunnings

    they all now have the stupid XA-DAVOS chip

    🙁

    May try to find a esp unit to swap in.

    1. malte Avatar
      malte

      That is very unfortunate. I have had mixed results with replacing chips (in other devices), but maybe that just comes down to soldering skills. The only alternative I can think of here is the Local Tuya integration (available via HACS), but of course this is still far from ideal and often fiddly to set up.

  7. Falz Avatar
    Falz

    Thanks for the great posts!
    Not sure where I might have gone wrong.
    I flashed my unit years ago and never got around to getting it working, just found your posts about it and thought I should give it a go.
    I updated from 7 to 8 then 9.1.0 (Possibly I need to upgrade further?)

    My Config:

    Arlec Heater

    Template
    {“NAME”:”Arlec Heater”,”GPIO”:[0,0,0,0,0,1,0,0,0,1824,0,1792,0,0],”FLAG”:0,”BASE”:18}

    MQTT
    Topic = esp_panel_heater_office
    Full Toipic = %topic%/%prefix%/

    Home Assistant:

    climate:
    – name: “Panel Heater Office”
    unique_id: “esp_panel_heater_office_climate”
    availability_topic: “esp_panel_heater_office/tele/LWT”
    payload_available: “Online”
    payload_not_available: “Offline”
    modes:
    – “off”
    – “heat”
    mode_state_topic: “esp_panel_heater_office/tele/RESULT”
    mode_state_template: “{% if value_json[‘SSerialReceived’][20:22] == ’00’ %}off{% else %}heat{% endif %}”
    mode_command_topic: “esp_panel_heater_office/cmnd/sserialsend5”
    mode_command_template: >
    {% set modes = { ‘off’: ’02’, ‘heat’: ’01’ } %}
    {% set fan_modes = { ‘off’: ’00’, ‘low’: ’02’, ‘high’: ’03’, ‘anti-frost’: ’04’} %}
    {{ [“F1F10210”, modes[value], “0000000000”, fan_modes[state_attr(‘climate.panel_heater_office’, ‘fan_mode’)], (‘%#x’ % (state_attr(‘climate.panel_heater_office’, ‘temperature’) | int))[2:], “01000001000001”, ((‘%#x’ % ( 21 + (modes[value] | int) + (fan_modes[state_attr(‘climate.panel_heater_office’, ‘fan_mode’)] | int) + (state_attr(‘climate.panel_heater_office’, ‘temperature’) | int)))[2:] | upper), “7E”] | join }}
    power_command_topic: “esp_panel_heater_office/cmnd/sserialsend5”
    payload_on: “F1F10210010000000000000000000001000001157E”
    payload_off: “F1F10210020000000000000000000001000001167E”
    fan_modes:
    – “off”
    – “low”
    – “high”
    – “anti-frost”
    fan_mode_state_topic: “esp_panel_heater_office/tele/RESULT”
    fan_mode_state_template: “{% if value_json[‘SSerialReceived’][20:22] == ’02’ %}low{% elif value_json[‘SSerialReceived’][20:22] == ’03’ %}high{% elif value_json[‘SSerialReceived’][20:22] == ’04’ %}anti-frost{% else %}off{% endif %}”
    fan_mode_command_topic: “esp_panel_heater_office/cmnd/sserialsend5”
    fan_mode_command_template: >
    {% set modes = { ‘off’: ’02’, ‘heat’: ’01’ } %}
    {% set fan_modes = { ‘off’: ’00’, ‘low’: ’02’, ‘high’: ’03’, ‘anti-frost’: ’04’} %}
    {{ [“F1F10210”, modes[states(‘climate.panel_heater_office’)], “0000000000”, fan_modes[value], (‘%#x’ % (state_attr(‘climate.panel_heater_office’, ‘temperature’) | int))[2:], “01000001000001”, ((‘%#x’ % ( 21 + (modes[states(‘climate.panel_heater_office’)] | int) + (fan_modes[value] | int) + (state_attr(‘climate.panel_heater_office’, ‘temperature’) | int)))[2:] | upper), “7E”] | join }}
    temperature_state_topic: “esp_panel_heater_office/tele/RESULT”
    temperature_state_template: “{{ value_json[‘SSerialReceived’][22:24] | int(base=16) }}”
    temperature_unit: “C”
    min_temp: 18.0
    max_temp: 30.0
    temperature_command_topic: “esp_panel_heater_office/cmnd/sserialsend5”
    temperature_command_template: >
    {% set modes = { ‘off’: ’02’, ‘heat’: ’01’ } %}
    {% set fan_modes = { ‘low’: ’02’, ‘high’: ’03’, ‘anti-frost’: ’04’} %}
    {{ [“F1F10210”, modes[states(‘climate.panel_heater_office’)], “0000000000”, fan_modes[state_attr(‘climate.panel_heater_office’, ‘fan_mode’)], (‘%#x’ % (value | int))[2:], “01000001000001”, ((‘%#x’ % ( 21 + (modes[states(‘climate.panel_heater_office’)] | int) + (fan_modes[state_attr(‘climate.panel_heater_office’, ‘fan_mode’)] | int) + (value | int)))[2:] | upper), “7E”] | join }}
    precision: 1.0
    current_temperature_topic: “esp_panel_heater_office/tele/RESULT”
    current_temperature_template: “{{ value_json[‘SSerialReceived’][28:30] | int(base=16) }}”

    Issue:
    1. My Card says “Unknown”
    2. No buttons work on the card
    3. I get this error when i press a button:

    Parsing template `{% set modes = { ‘off’: ’02’, ‘heat’: ’01’ } %} {% set fan_modes = { ‘off’: ’00’, ‘low’: ’02’, ‘high’: ’03’, ‘anti-frost’: ’04’} %} {{ [“F1F10210”, modes[value], “0000000000”, fan_modes[state_attr(‘climate.panel_heater_office’, ‘fan_mode’)], (‘%#x’ % (state_attr(‘climate.panel_heater_office’, ‘temperature’) | int))[2:], “01000001000001”, ((‘%#x’ % ( 21 + (modes[value] | int) + (fan_modes[state_attr(‘climate.panel_heater_office’, ‘fan_mode’)] | int) + (state_attr(‘climate.panel_heater_office’, ‘temperature’) | int)))[2:] | upper), “7E”] | join }}` for entity `climate.panel_heater_office` failed with error: ValueError: Template error: int got invalid input ‘None’ when rendering template ‘{% set modes = { ‘off’: ’02’, ‘heat’: ’01’ } %} {% set fan_modes = { ‘off’: ’00’, ‘low’: ’02’, ‘high’: ’03’, ‘anti-frost’: ’04’} %} {{ [“F1F10210”, modes[value], “0000000000”, fan_modes[state_attr(‘climate.panel_heater_office’, ‘fan_mode’)], (‘%#x’ % (state_attr(‘climate.panel_heater_office’, ‘temperature’) | int))[2:], “01000001000001”, ((‘%#x’ % ( 21 + (modes[value] | int) + (fan_modes[state_attr(‘climate.panel_heater_office’, ‘fan_mode’)] | int) + (state_attr(‘climate.panel_heater_office’, ‘temperature’) | int)))[2:] | upper), “7E”] | join }}’ but no default was specified.

  8. will Avatar
    will

    Falz, I had the same issue until I sent the below using MQTT explorer

    Publish “esp_panel_heater_office/cmnd/sserialsend5” with JSON payload “F1F10210010000000000000000000001000001157E”

    1. malte Avatar
      malte

      Thanks very much for sharing this, Will. And, Falz, please let us know if this helped. Other than that, I would assume that further Tasmota upgrades may be needed – version 9.1 is already about 4 years old.

Leave a Reply

Your email address will not be published. Required fields are marked *