Auto-update ESPHome Devices via Automation

Hi all,

with the recent implementation of updating ESPHome devices within HA, this is a welcomed feature for me as I have 8 devices, and sometimes it can take about 1.5 hrs for it to finish.

Making an automation for this would be a great solution.

I created a basic automation that checks every mid-night if there is an update available for each of my BT Proxies, send a message that there is and then another when its complete.

However, there is a problem with this below, because updating should happen 1 after the other, and not all at once.

UPDATE: this is now updated to include the repeat until done. Thanks @TheFes

UPDATE2: adding the global condition, will prevent this from sending you a message every day

alias: Automatically update ESPHome Devices
description: >-
  Automatically update ESPHome Devices at midnight, and notify when they are
  updated.
trigger:
  - platform: time
    at: "00:00:00"
    id: update_check
  - platform: state
    entity_id:
      - update.esp32_bluetooth_proxy_299fcc_firmware
      - update.esp32_bluetooth_proxy_4ca0c4_firmware
      - update.esp32_bluetooth_proxy_5a63a8_firmware
      - update.esp32_bluetooth_proxy_5f189c_firmware
      - update.esp32_bluetooth_proxy_4ca434_firmware
      - update.esp32_bluetooth_proxy_4cae58_firmware
      - update.esp32_bluetooth_proxy_5e9b38_firmware
      - update.esphome_web_5bdf28_firmware
    to: "off"
    id: up-to-date
  - platform: event
    event_type: timer.finished
    event_data:
      entity_id: timer.esphome_updates_timer
    id: esphome_timer_updates_finished
condition:
  - condition: state
    entity_id:
      - update.esp32_bluetooth_proxy_299fcc_firmware
      - update.esp32_bluetooth_proxy_4ca0c4_firmware
      - update.esp32_bluetooth_proxy_5a63a8_firmware
      - update.esp32_bluetooth_proxy_5f189c_firmware
      - update.esp32_bluetooth_proxy_4ca434_firmware
      - update.esp32_bluetooth_proxy_4cae58_firmware
      - update.esp32_bluetooth_proxy_5e9b38_firmware
      - update.esphome_web_5bdf28_firmware
    match: any
    state: "on"
action:
  - choose:
      - conditions:
          - condition: trigger
            id: update_check
        sequence:
          # Start a watch-dog timer to make sure things get done.
          - service: timer.start
            data:
              duration: 0
            target:
              entity_id: timer.esphome_updates_timer
          - service: notify.mobile_app_sony_xperia_zx1
            data:
              title: ESPHome Devices
              # Pick any ESPHome device below to get the version numbers in your message.
              message: >-
                Your ESPHome devices will now be updated from {{
                state_attr("update.esp32_bluetooth_proxy_299fcc_firmware",
                "installed_version") }} to {{
                state_attr("update.esp32_bluetooth_proxy_299fcc_firmware",
                "latest_version") }}.
          - alias: Update all ESPHome devices in sequence
            repeat:
              for_each: >-
                {{ states.update | selectattr('state', 'eq', 'on') |
                map(attribute='entity_id') | select('in',
                integration_entities('esphome')) | list }}
              sequence:
                - service: update.install
                  target:
                    entity_id: "{{ repeat.item }}"
                - wait_template: "{{ is_state(repeat.item, 'off') }}"
      - conditions:
          - condition: trigger
            id: up-to-date
        sequence:
          - service: timer.cancel
            data: {}
            target:
              entity_id: timer.esphome_updates_timer
          - service: notify.mobile_app_sony_xperia_zx1
            data:
              title: ESPHome Devices
              message: >-
                Your ESPHome devices are now all updated to {{
                state_attr("update.esp32_bluetooth_proxy_299fcc_firmware",
                "installed_version") }}.
      - conditions:
         # In-case the update failed
          - condition: trigger
            id: esphome_timer_updates_finished
        sequence:
          - service: notify.mobile_app_sony_xperia_zx1
            data:
              title: ESPHome Devices
              message: >-
                There was a problem updating to  {{
                state_attr("update.esp32_bluetooth_proxy_299fcc_firmware",
                "latest_version") }}. Please check your ESPHome Addon.
mode: single
5 Likes

Wow man, thanks for that. Will fire it tonight the first time. Just saw the new mechanism to update the devices and looked for a solution.

I´ve also the problem that my Dell/ Wyse 5040 needs ages for compiling.

Thanks again!

Yes it’s very useful.

Make sure you have auto update on the ESPHome add-on, and you’ll also have to make a timer via the helpers if you want the Watchdog timer to work. I would set it up to however long it takes to update all your boards plus 40 minutes or however way you like it.

Cheers

I get a message that in need repair my automation because this service is invalid.

Hmm, the service itself is native to HA core. Try in developer tools.

You did make a timer in the helpers, right?

I created the timer:
With a different name :slight_smile: and updated the automation.

timer.helpers_esphome_updater
Home Assistant 2023.2.5
Supervisor 2023.01.1
Operating System 9.5
Frontend 20230202.0 - latest

ahh, ok, got it.

Interesting how I don’t have this repair warning helper but, it should be:

timer.cancel

I’ve updated the above automation.

service: timer.cancel
data: {}
target:
  entity_id: timer.esphome_updates_timer
1 Like

This Blueprint will handle ESPHome updates:

1 Like

Could the entities be made dynamic? The following template lists the relevant entities (very rough)

{{ states.update 
  | selectattr('entity_id', 'match', '\w+.\w+_firmware')
  | selectattr('state','eq','on') 
  | map(attribute ='entity_id')
  | list
}}

That is feasible, but why don’t you want to update other entities automatically?
If you take a look at this most recent version of this Blueprint, you can filter out Core updates… isn’t that enough?
Maybe I can create a session specifically for devices’s firmwares (I would like more to use device_class: firmware instead of text searching, as that can be changed by users), with similar options like the Core items… I’m just afraid that too many settings (add-ons, HACS, etc.) will make it cumbersome to setup for entry level users.

How about this point of view to the automation?
I created a group with all my esphome entities in it.
When a esphome entity have an update the group goes ON. Then the repeat start.
When all entities are updated and go off the group goes off… Then it will send a done notification.

I have automation updated. I think this will work.

Here is my automation:

###########################################################################################
# ESPHOME UPDATER
###########################################################################################
- id: "793aebec-d390-4a9c-8f2c-725283c2853f"
  alias: "System - Automatically update ESPHome Devices"
  mode: single
  trigger:
    - platform: time
      at: 
        - "09:00:00"
        - "12:00:00"
        - "15:00:00"

    - platform: state
      entity_id: group.helpers_esphome_updates
      to: "on"
      id: up-date

    - platform: state
      entity_id: group.helpers_esphome_updates
      to: "off"
      id: up-to-date

  condition: []

  action:
    - choose:
          ###########################################################################################
          # When platform time is fires
          ###########################################################################################
        - conditions:
            - condition: trigger
              id: up-date

          sequence:
            - service: script.mobile_notify_no_actionable
              data:
                title: "System - ESPHome Devices"
                message: >-
                  Your ESPHome devices will now be updated from {{
                  state_attr("update.meek_toilet_firmware",
                  "installed_version") }} to {{
                  state_attr("update.meek_toilet_firmware",
                  "latest_version") }}.
                thread_id: "esphome_updater"

            - repeat:
                for_each: >-
                  {{ states.update | selectattr('state', 'eq', 'on') |
                  map(attribute='entity_id') | select('in',
                  integration_entities('esphome')) | list }}
                sequence:
                  - service: update.install
                    target:
                      entity_id: "{{ repeat.item }}"
                  - wait_template: "{{ is_state(repeat.item, 'off') }}"

          ###########################################################################################
          # When devices are updated
          ###########################################################################################
        - conditions:
            - condition: trigger
              id: up-to-date

          sequence:
            - service: timer.cancel
              data: {}
              target:
                entity_id: timer.helpers_esphome_updater

            - service: script.mobile_notify_no_actionable
              data:
                title: "System - ESPHome Devices"
                message: >-
                  Your ESPHome devices are now all updated to {{
                  state_attr("update.meek_toilet_firmware",
                  "installed_version") }}.
                thread_id: "esphome_updater"

          ###########################################################################################
          # Will fire when time_pattern start and group is still on
          ###########################################################################################
        - conditions:
            - "{{ is_state('group.helpers_esphome_updates', 'on') }}"

          sequence:
            - service: script.mobile_notify_no_actionable
              data:
                title: "System - ESPHome Devices"
                message: "If one or more devices didnt update well, now they are"
                thread_id: "esphome_updater"

            - repeat:
                for_each: >-
                  {{ states.update | selectattr('state', 'eq', 'on') |
                  map(attribute='entity_id') | select('in',
                  integration_entities('esphome')) | list }}
                sequence:
                  - service: update.install
                    target:
                      entity_id: "{{ repeat.item }}"
                  - wait_template: "{{ is_state(repeat.item, 'off') }}"
1 Like

That works well also!

I’m actually experiencing an issue at the moment with the ESPHome firmware update entities becoming unavailable

So my automation is not working right now because these entities are the triggers.

This is issue has been reported already by others and myself. If you are experiencing the same problem, check out the issue here and feel free to add something if it was not already mentioned.

I had same but only when HA was restart and ESPhome devices not connected yet via API
I use many ESPhgome switches. So when they become online they trigger some automations.

For those automations that fit on ESPhome states I add this condition to all of the automations:

  condition:
    - "{{ not (to_state == 'on' and from_state == 'unavailable') }}"
    - "{{ not (to_state == 'off' and from_state == 'unavailable') }}"
    - "{{ not (to_state == 'unavailable') }}"

This did the job and everything works perfect

So, I’ve added support for selecting between “All”, “Patches only” or “Don’t update” for Core items, devices firmware and everything else, so now you can select exactly what you want to update.¨

Please let me know if you have any issues.

Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.

The code is on Github:

My take on using jinja filters on the updater.state and in_progress attributes. All my nodes are named node** (i.e. node01, node12, etc.) and have entity ids updater.node**_firmware. First time doing this, and it looks a little too short! :sweat_smile: Can anyone point out some missed considerations?

- alias: Update ESPHome nodes
  trigger:
  - platform: time_pattern
    hours: '3'
    minutes: '*'
  condition:
  - "{{ states.update | selectattr('entity_id', 'match', 'update\.node\d+_firmware')
    | selectattr('state', 'eq', 'on') | list | count > 0 }}"
  - "{{ states.update | selectattr('entity_id', 'match', 'update\.node\d+_firmware')
    | selectattr('attributes.in_progress') | list | count == 0 }}"
  action:
  - service: update.install
    target:
      entity_id: "{{ (states.update | selectattr('entity_id', 'match', 'update\.node\d+_firmware')
        | selectattr('state', 'eq', 'on') | first).entity_id }}"
  mode: single
1 Like

Here is my take :grinning:
The script takes two parameters:

integration: Integration for which update-entities should be filtered like hassio, hacs, fritz, shelly, esphome

action: either install or skip

  sys_update:
# platforms: shelly, esphome, hacs, fritz, hassio
    sequence:
      - variables:  
          updates: |
            {{ expand(integration_entities(integration) 
                | select('match','update.'))
               | selectattr('state','eq','on')
               | selectattr('attributes.in_progress','eq', false) 
               | selectattr('attributes.latest_version','ne','attributes.skipped_version') 
               | map(attribute='entity_id')
               | list 
            }}
      - service: "update.{{action}}"
        data:
          entity_id: "{{ updates }}"
      - wait_template: "{{ expand(updates) | selectattr('attributes.in_progress','eq', true) | list | count == 0 }}"
      - service: script.notify
        data:
            type: "text"
            severity: "info"
            target: "sys"
            notification_id: "update"
            title: "Updates {{action}}ed"
            message: |
              (*{{ states('sensor.date_time') }}*)
              {% if updates | count > 0 -%}
                Die Updates für **{{integration}}** wurden auf den folgenden Geräten **{{ iif( action == 'skip', 'übersprungen', 'installiert') }}**:
                 - {{ updates | join('\n - ') }}
              {% else -%}
                Keine Updates für **{{integration}}** gefunden
              {% endif %}

Of course you can call this script with whatever trigger in an automation:

service: script.sys_update
data:
  integration: shelly
  action: skip

1 Like

My only consideration is that you are calling all the updates at the same time. This is not wrong, but you should take a look on how will be your system’s resources (CPU, memory, CPU temperature, etc) when you call dozens of updates at the same time. Maybe not a big problem when you are just updating firmware of a device where HA is not very much involved, the device is handling most of the load, then it’s probably ok, but if HA host machine have something to do (ESPHome?) then it could be an issue.
Or maybe you create a repeat loop and call the updates one by one. It might take a bit longer, but that is probably ok in most cases.

The ESPhome updates are installed sequentially, even if you call the update service with a list of entities, just how the “update all” on the ESPHome interface works.

I understand that is the case when you click in the “Update all” button, but are you sure this is the case when you all the update service and add all the ESPHome in the entity list? In my understanding most of the services runs almost a parallel call to multiple entities, so are you sure there is an exception for the update service when addressing ESPHome updates?
If they are sequential, then you are probably ok, but if they try to update in parallel, then it can be an issue.

Yes, I am sure, had plenty of opportunity lately :smiley:
I get notified, when a device reboots, and they do one after the other with ample time between them.

1 Like