ESPHome update notifications

Great work! However, I can’t get the esphome_version-sensor to work.
I’ve copied the command_line sensor to my config file, but it never gets a sensor state.

In the log I can se that the command is running:

2024-01-18 08:01:39.250 DEBUG (SyncWorker_6) [homeassistant.components.command_line] Running command: curl -s -X GET https://hub.docker.com/v2/namespaces/esphome/repositories/esphome/tags | jq -r '.results|.[]|.name' | egrep -i "^$(date +'%Y')\.[0-9]{1,2}\.[0-9]{1,2}$" | sort -rn | head -1

but it never seems to get a get a reply and the esphome_sensor never gets a value.

Do you have any clue what the problem might be?

Thanks for your feedback @johan.borgman!

Yes, I just figured out what happened :slight_smile:
The current releases are the following:

home-assistant:/config# curl -s -X GET https://hub.docker.com/v2/namespaces/esphome/repositories/esphome/tags | jq -r '.results|.[]|.name'
latest
2024.1.0-dev20240118
dev
2024.1.0-dev20240117
2023.12.7
2023.12
beta
stable
2024.1.0-dev20240116
2023.12.6

As the command line tries to find the latest stable release of this year - and there is no release for this year yet - it fails and returns nothing.
The fix is to replace the command line:

curl -s -X GET https://hub.docker.com/v2/namespaces/esphome/repositories/esphome/tags | jq -r '.results|.[]|.name' | egrep -i "^$(date +'%Y')\.[0-9]{1,2}\.[0-9]{1,2}$" | sort -rn | head -1

with the following command line:

curl -s -X GET https://hub.docker.com/v2/namespaces/esphome/repositories/esphome/tags | jq -r '.results|.[]|.name' | egrep -i "^[0-9]{4}\.[0-9]{1,2}\.[0-9]{1,2}$" | sort -rn | head -1

I’ll update this in my first post as well

Thank you @dominik4545 ! The new command line works perfectly.

1 Like

Dear community,

as my ESPHome-version sensor showed ‘unknown’, I started research again and figured out, by default docker just returned the latest 10 tags - which is not sufficient for now as there is no stable release of ESPHome within the lastest 10 tags.

Therefore, I added the flag for page_size to the request.
If you notice the same issues on your end, please replace your command line by this one:

curl -s -X GET https://hub.docker.com/v2/namespaces/esphome/repositories/esphome/tags?page_size=50 | jq -r '.results|.[]|.name' | egrep -i "^[0-9]{4}\.[0-9]{1,2}\.[0-9]{1,2}$" | sort -rn | head -1

The initial post was updated accordingly.

Happy sunday! :slight_smile:

I’m having a problem with the template script. My devices are at the current version, however the template sensor is showing them as being out of date. Any ideas?

Hi cdn4lf,

do you have this issue just sometimes or more often?
What would be the output if you copy the following template to the developer tools in the template section?

{%- set entities =  integration_entities('esphome') -%}
{%- set ns = namespace(outdated = []) -%}
{%- for entity in entities -%}
  {%- set device_name = device_attr(device_id(entity), 'name') -%}
  {%- if 'version' in entity and states(entity) != "unavailable" -%}
    {%- if not states('sensor.esphome_version') in states(entity) -%}
      {%- set ns.outdated = ns.outdated + [ device_name ] -%}
    {%- endif -%}
  {%- endif -%}
{%- endfor -%}
{{- ns.outdated -}}

Best regards,
Dominik

Hi Dominic,

I’ve had this since I set it up. Result when checked in dev tools is the same, showing the same 3 out of 6 devices.

My ratgdo and ble trackers aren’t showing up, and are reporting correctly.

My tuya flashed esphome devices do show up and are not reporting correctly.

All devices are on the correct and current version.

Thanks.

Okay, then let’s start debugging. Could you please add the following snippet to the template developer tools to check which condition fails? After that we should be able to find the reason for that :slight_smile:

{{- "ESPHome-Version-Sensor: '" + states('sensor.esphome_version') + "'\n" }}
{{- "----------------------\n" }}
{%- set entities =  integration_entities('esphome') -%}
{%- set ns = namespace(outdated = []) -%}
{%- for entity in entities -%}
  {%- set device_name = device_attr(device_id(entity), 'name') -%}
  {%- if 'version' in entity and states(entity) != "unavailable" -%}
    {{- "Valid version entity found: '" + entity + "'\n" }}
    {%- if not states('sensor.esphome_version') in states(entity) -%}
      {{- "OUTDATED - Version entity ('" + entity + "') has value: '" + states(entity) + "'\n" }}
      {%- set ns.outdated = ns.outdated + [ device_name ] -%}
    {%- else %}
      {{- "UP2DATE - Version entity ('" + entity + "') has value: '" + states(entity) + "'\n" }}
    {%- endif -%}
  {%- else %}
    {{- "Skipping entity '" + entity + "'\n" }}
  {%- endif -%}
{%- endfor -%}
{{- ns.outdated -}}

Looks like the code is picking up all versions. Libretiny is being identified as out of date.

Result type: string
ESPHome-Version-Sensor: '2024.2.2'
----------------------
Skipping entity 'button.esp32_bluetooth_proxy_9a50a0_safe_mode_boot'
Valid version entity found: 'sensor.living_room_dimmer_libretiny_version'
OUTDATED - Version entity ('sensor.living_room_dimmer_libretiny_version') has value: 'v1.4.1 on generic-bk7231n-qfn32-tuya, compiled at Mar  9 2024 09:25:07, GCC 10.3.1 (-O1)'
Skipping entity 'sensor.living_room_dimmer_esp_ip_address'
Skipping entity 'sensor.living_room_dimmer_esp_mac_wifi_address'
Valid version entity found: 'sensor.living_room_dimmer_livingroomdimmer_version'
UP2DATE - Version entity ('sensor.living_room_dimmer_livingroomdimmer_version') has value: '2024.2.2 Mar  9 2024, 19:04:36'
Skipping entity 'switch.living_room_dimmer_living_room_restart'
Skipping entity 'light.living_room_dimmer_feit_dimmer_light'
Skipping entity 'switch.ratgdov25i_fada22_learn'
Skipping entity 'cover.ratgdov25i_fada22_door'
Skipping entity 'light.ratgdov25i_fada22_light'
Skipping entity 'lock.ratgdov25i_fada22_lock_remotes'
Skipping entity 'binary_sensor.ratgdov25i_fada22_motion'
Skipping entity 'binary_sensor.ratgdov25i_fada22_obstruction'
Skipping entity 'binary_sensor.ratgdov25i_fada22_button'
Skipping entity 'binary_sensor.ratgdov25i_fada22_motor'
Skipping entity 'binary_sensor.ratgdov25i_fada22_dry_contact_open'
Skipping entity 'binary_sensor.ratgdov25i_fada22_dry_contact_close'
Skipping entity 'binary_sensor.ratgdov25i_fada22_dry_contact_light'
Skipping entity 'sensor.ratgdov25i_fada22_openings'
Skipping entity 'sensor.ratgdov25i_fada22_paired_devices'
Valid version entity found: 'sensor.ratgdov25i_fada22_firmware_version'
UP2DATE - Version entity ('sensor.ratgdov25i_fada22_firmware_version') has value: '2024.2.2 Mar  9 2024, 11:43:55'
Skipping entity 'sensor.ratgdov25i_fada22_esp_ip_address'
Skipping entity 'sensor.ratgdov25i_fada22_esp_mac_wifi_address'
Skipping entity 'number.ratgdov25i_fada22_rolling_code_counter'
Skipping entity 'number.ratgdov25i_fada22_opening_duration'
Skipping entity 'number.ratgdov25i_fada22_closing_duration'
Skipping entity 'number.ratgdov25i_fada22_client_id'
Skipping entity 'button.ratgdov25i_fada22_restart'
Skipping entity 'button.ratgdov25i_fada22_safe_mode_boot'
Skipping entity 'button.ratgdov25i_fada22_query_status'
Skipping entity 'button.ratgdov25i_fada22_query_openings'
Skipping entity 'button.ratgdov25i_fada22_sync'
Skipping entity 'button.ratgdov25i_fada22_toggle_door'
Valid version entity found: 'sensor.libretiny_version'
OUTDATED - Version entity ('sensor.libretiny_version') has value: 'v1.4.1 on generic-bk7231t-qfn32-tuya, compiled at Mar  9 2024 11:39:32, GCC 10.3.1 (-O1)'
Skipping entity 'sensor.esp_ip_address'
Skipping entity 'sensor.esp_mac_wifi_address'
Valid version entity found: 'sensor.bathroomlight_version'
UP2DATE - Version entity ('sensor.bathroomlight_version') has value: '2024.2.2 Mar  9 2024, 18:49:27'
Skipping entity 'switch.bathroom_light'
Valid version entity found: 'sensor.libretiny_version_2'
OUTDATED - Version entity ('sensor.libretiny_version_2') has value: 'v1.4.1 on generic-bk7231t-qfn32-tuya, compiled at Mar  9 2024 11:46:05, GCC 10.3.1 (-O1)'
Skipping entity 'sensor.esp_ip_address_2'
Skipping entity 'sensor.esp_mac_wifi_address_2'
Valid version entity found: 'sensor.bathroomfan_version'
UP2DATE - Version entity ('sensor.bathroomfan_version') has value: '2024.2.2'
Skipping entity 'switch.bathroom_fan'
Skipping entity 'button.main_floor_proxy_safe_mode_boot'
['Living Room Dimmer', 'Bathroomlight', 'Bathroomfan']
This template listens for the following state changed events:

Entity: sensor.bathroomfan_version
Entity: sensor.bathroomlight_version
Entity: sensor.esphome_version
Entity: sensor.libretiny_version
Entity: sensor.libretiny_version_2
Entity: sensor.living_room_dimmer_libretiny_version
Entity: sensor.living_room_dimmer_livingroomdimmer_version
Entity: sensor.ratgdov25i_fada22_firmware_version

Okay, now it’s clear, why this happens:
The template takes the current ESPHome-version (which is 2024.2.2 in this case) and checks whether this text can be found within the values for the version-entities.

It works e.g. for sensor.ratgdov25i_fada22_firmware_version as there the value 2024.2.2 Mar 9 2024, 11:43:55 contains this text. The version sensor.libretiny_version has the value v1.4.1 on generic-bk7231t-qfn32-tuya, compiled at Mar 9 2024 11:39:32, GCC 10.3.1 (-O1) which does not match the ESPHome-version-scheme (even if it might be managed with ESPHome).
If you can get the latest version of those devices as well, you can follow the same approach as for ESPHome but as long as the version numbers don’t match, the entities/devices will be reported as outdated.

Otherwise you can exclude the entities from the version check:

{%- set entities =  integration_entities('esphome') -%}
{%- set ns = namespace(outdated = [], excluded = ['Your-Device-Name-Goes-Here', 'Second-Device-Name']) -%}
{%- for entity in entities -%}
  {%- set device_name = device_attr(device_id(entity), 'name') -%}
  {%- if not device_name in ns.excluded -%}
    {%- if 'version' in entity and states(entity) != "unavailable" -%}
      {%- if not states('sensor.esphome_version') in states(entity) -%}
        {%- set ns.outdated = ns.outdated + [ device_name ] -%}
      {%- endif -%}
    {%- endif -%}
  {%- endif -%}
{%- endfor -%}
{{- ns.outdated -}}

Thanks for your help with this. I updated the code you sent to exclude by entity instead of by device, this way the code will still check the device for the correct Esphome version. Code is below for reference if anyone else wants it.

{%- set entities =  integration_entities('esphome') -%}
{%- set ns = namespace(outdated = [], excluded = ['sensor.living_room_dimmer_libretiny_version', 'sensor.libretiny_version_2', 'sensor.libretiny_version']) -%}
{%- for entity in entities -%}
  {%- set device_name = device_attr(device_id(entity), 'name') -%}
  {%- if not entity in ns.excluded -%}
    {%- if 'version' in entity and states(entity) != "unavailable" -%}
      {%- if not states('sensor.esphome_version') in states(entity) -%}
        {%- set ns.outdated = ns.outdated + [ device_name ] -%}
      {%- endif -%}
    {%- endif -%}
  {%- endif -%}
{%- endfor -%}
{{- ns.outdated -}}
1 Like

Would it be worth retooling to use the existing sw_version in the esphome integration rather than a new sensor? A quick change to use it instead below. Note it still goes through every sensor rather than just the device (too late at night to figure that one out yet).

sensor:
  - platform: template
    sensors:
      # Sensor for ESPHome updates
      calculated_esphome_updates:
        friendly_name: ESPHome Updates
        attribute_templates: 
          outdated_devices: >-
            {%- set entities =  integration_entities('esphome') -%}
            {%- set ns = namespace(outdated = []) -%}
            {%- for entity in entities -%}
              {%- set device_version = device_attr(device_id(entity), 'sw_version') | regex_findall_index('(\d+\.\d+\.\d+)') | join -%}
              {%- if device_version is defined and states(entity) != "unavailable" -%}
                {%- if not states('sensor.esphome_version') in device_version -%}
                  {%- set ns.outdated = ns.outdated + [ device_version ] -%}
                {%- endif -%}
              {%- endif -%}
            {%- endfor -%}
            {{- ns.outdated -}}
          outdated_devices_number: >-
            {%- set entities =  integration_entities('esphome') -%}
            {%- set ns = namespace(outdated = []) -%}
            {%- for entity in entities -%}
              {%- set device_version = device_attr(device_id(entity), 'sw_version') | regex_findall_index('(\d+\.\d+\.\d+)') | join -%}
              {%- if device_version is defined and states(entity) != "unavailable" -%}
                {%- if not states('sensor.esphome_version') in device_version -%}
                  {%- set ns.outdated = ns.outdated + [ device_version ] -%}
                {%- endif -%}
              {%- endif -%}
            {%- endfor -%}
            {{- ns.outdated | count | int -}}
        icon_template: >-
          {%- set entities =  integration_entities('esphome') -%}
          {%- set ns = namespace(up2date = [], outdated = []) -%}
          {%- for entity in entities -%}
            {%- set device_version = device_attr(device_id(entity), 'sw_version') | regex_findall_index('(\d+\.\d+\.\d+)') | join -%}
            {%- if device_version is defined and states(entity) != "unavailable" -%}
              {%- if states('sensor.esphome_version') in device_version -%}
                {%- set ns.up2date = ns.up2date + [ entity ] -%}
              {%- else -%}
                {%- set ns.outdated = ns.outdated + [ entity ] -%}
              {%- endif -%}
            {%- endif -%}
          {%- endfor -%}
          {%- if ns.outdated | count >= 1 -%}
            mdi:package-up
          {%- elif ns.up2date | count >= 1 -%}
            mdi:package-check
          {%- else -%}
            mdi:help-box
          {%- endif -%}
        value_template: >-
          {%- set entities =  integration_entities('esphome') -%}
          {%- set ns = namespace(up2date = [], outdated = []) -%}
          {%- for entity in entities -%}
            {%- set device_version = device_attr(device_id(entity), 'sw_version') | regex_findall_index('(\d+\.\d+\.\d+)') | join -%}
            {%- if device_version is defined and states(entity) != "unavailable" -%}
              {%- if states('sensor.esphome_version') in device_version -%}
                {%- set ns.up2date = ns.up2date + [ entity ] -%}
              {%- else -%}
                {%- set ns.outdated = ns.outdated + [ entity ] -%}
              {%- endif -%}
            {%- endif -%}
          {%- endfor -%}
          {%- if ns.outdated | count == 0 and ns.up2date | count >= 1 -%}
            No update available
          {%- elif ns.outdated | count == 1 -%}
            Update available for one device
          {%- elif ns.outdated | count >= 2 -%}
            {{- "Updates available for " + ns.outdated | count | string + " devices"-}}
          {%- else -%}
            No ESPHome-device found
          {%- endif -%}

This will make it a single entity per device, given there is no preset entity on ESPhome devices that I am aware of. I would image there is a cleaner way.

sensor:
  - platform: template
    sensors:
      # Sensor for ESPHome updates
      calculated_esphome_updates:
        friendly_name: ESPHome Updates
        attribute_templates: 
          outdated_devices: >-
            {%- set entities =  integration_entities('esphome') | map('device_id') | unique | list -%}
            {%- set ns = namespace(outdated = []) -%}
            {%- for entity in entities -%}
              {%- set device_version = device_attr((entity), 'sw_version') | regex_findall_index('(\d+\.\d+\.\d+)') | join -%}
              {%- if device_version is defined and states(entity) != "unavailable" -%}
                {%- if not states('sensor.esphome_version') in device_version -%}
                  {%- set ns.outdated = ns.outdated + [ device_version ] -%}
                {%- endif -%}
              {%- endif -%}
            {%- endfor -%}
            {{- ns.outdated -}}
          outdated_devices_number: >-
            {%- set entities =  integration_entities('esphome') | map('device_id') | unique | list -%}
            {%- set ns = namespace(outdated = []) -%}
            {%- for entity in entities -%}
              {%- set device_version = device_attr((entity), 'sw_version') | regex_findall_index('(\d+\.\d+\.\d+)') | join -%}
              {%- if device_version is defined and states(entity) != "unavailable" -%}
                {%- if not states('sensor.esphome_version') in device_version -%}
                  {%- set ns.outdated = ns.outdated + [ device_version ] -%}
                {%- endif -%}
              {%- endif -%}
            {%- endfor -%}
            {{- ns.outdated | count | int -}}
        icon_template: >-
          {%- set entities =  integration_entities('esphome') | map('device_id') | unique | list -%}
          {%- set ns = namespace(up2date = [], outdated = []) -%}
          {%- for entity in entities -%}
            {%- set device_version = device_attr((entity), 'sw_version') | regex_findall_index('(\d+\.\d+\.\d+)') | join -%}
            {%- if device_version is defined and states(entity) != "unavailable" -%}
              {%- if states('sensor.esphome_version') in device_version -%}
                {%- set ns.up2date = ns.up2date + [ entity ] -%}
              {%- else -%}
                {%- set ns.outdated = ns.outdated + [ entity ] -%}
              {%- endif -%}
            {%- endif -%}
          {%- endfor -%}
          {%- if ns.outdated | count >= 1 -%}
            mdi:package-up
          {%- elif ns.up2date | count >= 1 -%}
            mdi:package-check
          {%- else -%}
            mdi:help-box
          {%- endif -%}
        value_template: >-
          {%- set entities =  integration_entities('esphome') | map('device_id') | unique | list -%}
          {%- set ns = namespace(up2date = [], outdated = []) -%}
          {%- for entity in entities -%}
            {%- set device_version = device_attr((entity), 'sw_version') | regex_findall_index('(\d+\.\d+\.\d+)') | join -%}
            {%- if device_version is defined and states(entity) != "unavailable" -%}
              {%- if states('sensor.esphome_version') in device_version -%}
                {%- set ns.up2date = ns.up2date + [ entity ] -%}
              {%- else -%}
                {%- set ns.outdated = ns.outdated + [ entity ] -%}
              {%- endif -%}
            {%- endif -%}
          {%- endfor -%}
          {%- if ns.outdated | count == 0 and ns.up2date | count >= 1 -%}
            No update available
          {%- elif ns.outdated | count == 1 -%}
            Update available for one device
          {%- elif ns.outdated | count >= 2 -%}
            {{- "Updates available for " + ns.outdated | count | string + " devices"-}}
          {%- else -%}
            No ESPHome-device found
          {%- endif -%}

Thanks @junkman690 for your contribution. The code snippet is primarily usefull for users not using Home Assistant OS but just for instance the Docker installation.

For sure, you can use the integrated version information if available instead of another sensor

Hi @dominik4545, I am running separate docker versions and the firmware info is listed on the device, however it is not integrated into notifications/upgrades like with HAOS. The templated code above for the sw_version attribute works in the docker version (I haven’t test in HAOS as I don’t run it).
Screenshot 2024-04-10 201720
Screenshot 2024-04-10 201838

@dominik4545

Good morning,
today esphome 2024.10.0 is out but the code

curl -s -X GET https://hub.docker.com/v2/namespaces/esphome/repositories/esphome/tags?page_size=50 | jq -r '.results|.[]|.name' | egrep -i "^[0-9]{4}\.[0-9]{1,2}\.[0-9]{1,2}$" | sort -rn | head -1

still shows the previous version
If I decrease page_size to a value less or equal 24 or I remove it, the new version appears.