Auto-update ESPHome Devices via Automation

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
}}
1 Like

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

3 Likes

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

I learned something new in a Saturday morning… :wink:
Thanks for sharing!!

1 Like

Updating the esphome devices one-by-one sequentially is considered, as this script runs every minute from 3.00-3.59am, on the condition that none of the esphome nodes in the list has in_progress=true, and passes only the first node that has update.state=on to the update.install service .

  - service: update.install
    target:
      entity_id: "{{ (states.update | selectattr('entity_id', 'match', 'update\.node\d+_firmware')
        | selectattr('state', 'eq', 'on') | first).entity_id }}"

Unless you meant calling up the entire states.update object and them filtering them poses a huge cpu drain, then yea that I’m unaware of… Is processing a list of entities really that resource intensive?

1 Like

Thanks for the script, @loongyh. I am getting an error when trying to edit it in the File Editor - but can’t figure out what is wrong.

image

The code is copy / pasted from yours so I’m pretty sure it’s not a typing error and as I am a novice at this I don’t know how to fix it.

Does anyone have an idea, please?

I just stumbled upon this myself today and decided to go a slightly different way:

automation:
  - alias: Auto-update ESP devices
    trigger:
      - platform: time_pattern
        hours: "3"
        minutes: "*"
    action:
      - variables:
          outdated_esp_items: >-
            {{ states.update
                | selectattr('state', 'eq', 'on')
                | selectattr('attributes.title', 'match', 'ESPHome')
                | selectattr('attributes.auto_update', 'eq', false)
                | selectattr('attributes.in_progress', 'eq', false)
                | sort(attribute='attributes.installed_version')
                | map(attribute='entity_id')
                | list }}
          next_to_update: "{{ outdated_esp_items | first }}"
      - condition:
          - "{{ outdated_esp_items | count > 0 }}"
      - service: update.install
        target:
          entity_id: "{{ next_to_update }}"
    mode: single
6 Likes