How to: Update zigbee devices one by one when update available

A problem: I have many Hue devices in my home, all connected to HA with Z2M (I removed the Hue integration because of poor responsiveness). When Philips releases an updated, then suddenly I have long list of devices to update.

The main issues is that when Zigbee device is being updated, it (1) takes long time and more importantly (2) blocks almost all communication. If I start updating all devices at the same time, sooner than later update will stop working.

It takes few days to update all, and it is painful.

I’m looking for a way to update the Zigbee devices in some kind of automatic mode. I was thinking to have:

  • template sensor that would tell me number of devices waiting for an update. If we can filter by integration, then I’d filter devices on zigbee/mqtt.
  • template sensor that would tell me if any device is currently updating
  • An automation that would be activated manually (when I detect available updates)
  • When enabled, automation is triggered every x minutes
  • Automation checks template sensor if anything is available to update and if no update is currently active
  • automation starts an update of next device.

Questions:

  • Is there a better way to do such sequential update? I could turn the automation on when I leave the house or during the night, which would periodically start and update all devices
  • Did someone do something similar?
  • How to get next device for update in a nice way? By using list template sensor?

Thanks

While not exactly what you want, here is how. Update multiple ESP Home devices. I don’t use zigbee, so not sure how it gets initiated, but maybe you can adapt this for your use.


#############################################################
#   Automatically update ESPHome Devices
#############################################################
- id: update_esphome_devices
  alias: Update ESPHome Devices
  initial_state: TRUE
  mode: queued
  description: >-
    Automatically update ESPHome Devices and notify when integration updated.
  triggers:
  # standard time trigger
  - trigger: time
    at: "01:15:00"
    id: update_start
  # Manual trigger
  - trigger: state
    entity_id: input_boolean.update_esp32
    to: "on"
    id: update_start
  # triggerd after one device completes
  - trigger: event
    event_type: UPDATE_NEXT_DEVICE
    id: update_next
  # Group is Updated
  - trigger: state
    entity_id: group.esp_32
    to: "off"
    for: "00:00:10"
    id: update_finished
#
  # Timer finished - INCOMPLETE
  - trigger: state
    entity_id: timer.esphome_updates_timer
    to: "idle"
    for: "00:15:00"
    id: update_restart
#
  # Timer finished - ERROR
  - trigger: event
    event_type: timer.finished
    event_data:
      entity_id: timer.esphome_updates_timer
    id: update_timeout
  # ----------------------------------------------
  conditions:
  - condition: state
    entity_id:
      - update.d1m_gar_heat_fans_firmware
      - update.bt_proxy_3_gar_sen_firmware
      - update.d1m_light_sensor1_firmware
      - update.d1m_srms_firmware
      - update.d1m_temp_sensor1_firmware
      - update.esp32_miscale_firmware
    match: any
    state: "on"
  # ----------------------------------------------
  actions:
  # Start a watch-dog timer to make sure things get done. Only start one time, does not reset each loop
  - if:
    - "{{ states('timer.esphome_updates_timer') == 'idle' }}"
    # Start timer
    then:
    - action: timer.start
      target:
        entity_id: timer.esphome_updates_timer
      data:
        duration:
          "{{ (states.update|selectattr('state','eq','on')|map(attribute='entity_id')|select('in',integration_entities('esphome'))|list|count)*600 }}"
        # Allows 10 minutes (600 sec) for each device.
  - choose:
# Update Start
    - conditions:
        - condition: trigger
          id: update_start
      sequence:
      - action: script.turn_on
        data_template:
          entity_id: script.reset_logging_esp32_status
      - action: script.turn_on
        data_template:
          entity_id: script.update_esp32_device
# Update Next
    - conditions:
      - condition: trigger
        id: update_next
      sequence:
      - action: script.turn_on
        data_template:
          entity_id: script.update_esp32_device
# Update Finished, ALL devices are updated!
    - conditions:
      - condition: trigger
        id: update_finished
      sequence:
      - action: script.turn_on
        data_template:
          entity_id: script.finished_update_esp32_device
# Update Timeout - Error!
    - conditions:
      - condition: trigger
        id: update_timeout
      - condition: state
        entity_id: group.esp_32
        state: "on"
      sequence:
      - action: script.turn_on
        data_template:
          entity_id: script.error_update_esp32_device
# Update ReStart
    - conditions:
        - condition: trigger
          id: update_restart
      sequence:
      - action: script.turn_on
        data_template:
          entity_id: script.reset_logging_esp32_status
      - action: script.turn_on
        data_template:
          entity_id: script.update_esp32_device
# No conditions met
    default:
      sequence:
        # Persistent Notification
        - action: persistent_notification.create
          data_template:
            title: ESPHome - Default Condition
            message: >-
              At {{ now().strftime("%m/%d/%Y %H:%M:%S") }}.<br>
              The Update ESPHome Devices automation has encountered a problem and has entered the default condition.<br>
#

If you need help creating the EVENTs just let me know and I’ll post that code as well.

This looks great indeed!

Yes, could you please post the script and event generator?

My suggestion because zigbee doesn’t really like multiple devices updated at once.
Change this:

To this:

  • template sensor that would tell me number of devices waiting for an update. If we can filter by integration, then I’d filter devices on zigbee/mqtt.
  • template sensor that would tell me if the number of available updates has been reduced by 1.
  • automation starts an update of next device.

I’m sorry, but I’m not that well versed into templating to tell you how to do the steps above. However, I hope the above puts you (and others who might be willing to help) on the right track.

I’ve solved the problem. At least for now, it seems to work fine.

The solution is based on 3 template sensors and one automation.

  • template sensor to get number of devices with available update
{{ states.update|selectattr('state','eq','on')|map(attribute='entity_id')|select('in',integration_entities('mqtt'))|list|count }}
  • template sensor to get number of devices currently updating
{{ states.update|selectattr('attributes.in_progress', 'eq', True)|map(attribute='entity_id')|select('in', integration_entities('mqtt'))|list|count }}
  • template sensor to retrieve next update entity
{{ states.update|selectattr('state','eq','on')|map(attribute='entity_id')|select('in',integration_entities('mqtt'))|list|first }}

Automation, that is triggered on state change for either new devices have an update available or number of devices updating has changed.

alias: MQTT-auto-update-sequentially
description: ""
triggers:
  - trigger: state
    entity_id:
      - sensor.mqtt_auto_update_number_of_devices_currently_updating
      - sensor.mqtt_auto_update_number_of_devices_with_update_available
    for:
      hours: 0
      minutes: 1
      seconds: 0
conditions:
  - condition: numeric_state
    entity_id: sensor.mqtt_auto_update_number_of_devices_currently_updating
    below: 1
  - condition: numeric_state
    entity_id: sensor.mqtt_auto_update_number_of_devices_with_update_available
    above: 0
actions:
  - action: update.install
    metadata: {}
    data: {}
    target:
      entity_id: "{{ states('sensor.mqtt_auto_update_next_entity_to_update') }}"
mode: single

Not sure if it’s still needed, but here are the scripts for the update and event actions. You’ll see that generating the EVENT is part of the script sequence. It is then later used in the automation triggers.

#########################################################
#  Update ESP32 Device  (ESPHome)
#########################################################
update_esp32_device:
  alias: Update ESP32 Device
  mode: restart
  sequence:
  # only necessary for manual updates
  - action: input_boolean.turn_off
    entity_id: input_boolean.update_esp32
  # set [var.v_esp32_active_device] to repeat.item
  - action: var.set
    data:
      entity_id: var.v_esp32_active_device
      value_template: >-
        {% set esp32_device = states.update | selectattr('state', 'eq', 'on') | map(attribute='entity_id') | select('in', integration_entities('esphome')) | list | first %}
        {{ esp32_device }}
  # Set Current Item to [sensor.vh_esp32_device_update]
  - action: variable.update_sensor
    target:
      entity_id: sensor.vh_esp32_device_update
    data:
      value: "{{now().strftime('%m/%d %H:%M:%S')}}, {{states('var.v_esp32_active_device')}}"
      attributes:
        history_1: "{{states('sensor.vh_esp32_device_update')}}"
        history_2: "{{state_attr('sensor.vh_esp32_device_update','history_1')}}"
        history_3: "{{state_attr('sensor.vh_esp32_device_update','history_2')}}"
        history_4: "{{state_attr('sensor.vh_esp32_device_update','history_3')}}"
        history_5: "{{state_attr('sensor.vh_esp32_device_update','history_4')}}"
        history_6: "{{state_attr('sensor.vh_esp32_device_update','history_5')}}"
        history_7: "{{state_attr('sensor.vh_esp32_device_update','history_6')}}"
        history_8: "{{state_attr('sensor.vh_esp32_device_update','history_7')}}"
        last_updated: "{{now().strftime('%Y-%m-%d %H:%M:%S')}}"
  # Persistent Notification
  - action: persistent_notification.create
    data_template:
      title: ESPHome - Starting Updates
      message: >-
        At {{ now().strftime("%m/%d/%Y %H:%M:%S") }}.<br>
        Device: [{{states('var.v_esp32_active_device')}}].<br>
        Latest version: {{ state_attr(states('var.v_esp32_active_device'), 'latest_version') }}<br>
        Installed version: {{ state_attr(states('var.v_esp32_active_device'), 'installed_version') }}
  # Maintenance Log Start
  - action: script.turn_on
    entity_id: script.notify_logs_maintenance
    data_template:
      variables:
        log_type: "Script"
        log_name: "Update ESP32 Device"
        log_event: "Device - {{states('var.v_esp32_active_device')}}"
        log_slug: "update_esp32_device"
        log_trigger: ""
        log_var1: "Trying to update {{states('var.v_esp32_active_device')}}."
  # Maintenance Log End
  # Do Update
#  - action: persistent_notification.create
#    data_template:
#      title: ESPHome - Installing (w 5 min delay)
#      message: >-
#        At {{ now().strftime("%m/%d/%Y %H:%M:%S") }}.<br>
  - action: update.install
    target:
      entity_id: "{{states('var.v_esp32_active_device')}}"
  - delay: '00:01:00'
  - wait_template: "{{ state_attr(states('var.v_esp32_active_device'), 'latest_version') == state_attr(states('var.v_esp32_active_device'), 'installed_version') }}"
    timeout: "00:10:00"
    continue_on_timeout: "true"
# Procces results
#  - action: persistent_notification.create
#    data_template:
#      title: ESPHome - Checking Update Status
#      message: >-
#        At {{ now().strftime("%m/%d/%Y %H:%M:%S") }}.<br>
#        Device: [{{states('var.v_esp32_active_device')}}].<br>
#        Latest version: {{ state_attr(states('var.v_esp32_active_device'), 'latest_version') }}<br>
#        Installed version: {{ state_attr(states('var.v_esp32_active_device'), 'installed_version') }}
  - if:
    - "{{ state_attr(states('var.v_esp32_active_device'), 'latest_version') == state_attr(states('var.v_esp32_active_device'), 'installed_version') }}"
  # Update was successful
    then:
      # Persistent Notification
    - action: persistent_notification.create
      data_template:
        title: ESPHome - Device Updated
        message: >-
          At {{ now().strftime("%m/%d/%Y %H:%M:%S") }}.<br>
          Device: [{{states('var.v_esp32_active_device')}}] is now complete.<br>
          Latest version: {{ state_attr(states('var.v_esp32_active_device'), 'latest_version') }}<br>
          Installed version: {{ state_attr(states('var.v_esp32_active_device'), 'installed_version') }}
    # Maintenance Log Start
    - action: script.turn_on
      entity_id: script.notify_logs_maintenance
      data_template:
        variables:
          log_type: "Script"
          log_name: "Update ESP32 Device"
          log_event: "Success - {{states('var.v_esp32_active_device')}}"
          log_slug: "update_esp32_device"
          log_trigger: ""
          log_var1: "{{states('var.v_esp32_active_device')}} update is now complete."
    # Maintenance Log End
    - if:
      - "{{ (states.update|selectattr('state','eq','on')|map(attribute='entity_id')|select('in',integration_entities('esphome'))|list|count) > 0 }}"
      # More to Update, Trigger Next
      then:
      - event: UPDATE_NEXT_DEVICE
        event_data:
          title: 'Update Next Device'
          friendly_name: 'Update Next Device'
      # Finished Updates, Trigger Done
      else:
      - event: UPDATE_FINISHED
        event_data:
          title: 'Update Finished'
          friendly_name: 'Update Finished'
   # Update failed
    else:
      # Persistent Notification
    - action: persistent_notification.create
      data_template:
        title: ESPHome - Device Update Failed
        message: >-
          At {{ now().strftime("%m/%d/%Y %H:%M:%S") }}.<br>
          Device: [{{states('var.v_esp32_active_device')}}] update failed.<br>
          Latest version: {{ state_attr(states('var.v_esp32_active_device'), 'latest_version') }}<br>
          Installed version: {{ state_attr(states('var.v_esp32_active_device'), 'installed_version') }}
      # Maintenance Log Start
    - action: script.turn_on
      entity_id: script.notify_logs_maintenance
      data_template:
        variables:
          log_type: "Script"
          log_name: "Update ESP32 Device"
          log_event: "Failed - {{states('var.v_esp32_active_device')}}"
          log_slug: "update_esp32_device"
          log_trigger: ""
          log_var1: "{{states('var.v_esp32_active_device')}} update failed."
      # Maintenance Log End
    - if:
      - "{{ (states.update|selectattr('state','eq','on')|map(attribute='entity_id')|select('in',integration_entities('esphome'))|list|count) > 0 }}"
      # More to Update, Trigger Next
      then:
      - event: UPDATE_NEXT_DEVICE
        event_data:
          title: 'Update Next Device'
          friendly_name: 'Update Next Device'
      else:
      - event: UPDATE_FINISHED
        event_data:
          title: 'Update Finished'
          friendly_name: 'Update Finished'
#
#########################################################
#  Finished Update ESP32 Device  (ESPHome)
#########################################################
finished_update_esp32_device:
  alias: Finished Update ESP32 Device
  mode: restart
  sequence:
  - action: timer.cancel
    data: {}
    target:
      entity_id: timer.esphome_updates_timer
  - action: input_boolean.turn_off
    entity_id: input_boolean.update_esp32
  - action: var.set
    data:
      entity_id: var.v_esp32_active_device
      value: ""
  # Persistent Notification
  - action: persistent_notification.create
    data_template:
      title: ESPHome - Updates Finished!
      message: >-
        At {{ now().strftime("%m/%d/%Y %H:%M:%S") }}.<br>
        Your ESPHome devices are now all up to date.
  # Maintenance Log Start
  - action: script.turn_on
    entity_id: script.notify_logs_maintenance
    data_template:
      variables:
        log_type: "Script"
        log_name: "Finished Update ESP32 Device"
        log_event: "Update Completed"
        log_slug: "finished_update_esp32_device"
        log_trigger: ""
        log_var1: "All ESPHome devices are now updated to {{ state_attr(trigger.entity_id, 'latest_version') }}."
  # Maintenance Log End
#
#########################################################
#  Error Update ESP32 Device  (ESPHome)
#########################################################
error_update_esp32_device:
  alias: Error Update ESP32 Device
  mode: restart
  sequence:
  - action: timer.cancel
    data: {}
    target:
      entity_id: timer.esphome_updates_timer
  - action: input_boolean.turn_off
    entity_id: input_boolean.update_esp32
  # Persistent Notification
  - action: persistent_notification.create
    data_template:
      title: ESPHome - Update Error!
      message: >-
        At {{ now().strftime("%m/%d/%Y %H:%M:%S") }}.<br>
        There was a problem updating [{{ states.update | selectattr('state', 'eq', 'on') | map(attribute='entity_id') | select('in', integration_entities('esphome')) | list }}]. Please check your ESPHome Addon.
  # Maintenance Log Start
  - action: script.turn_on
    entity_id: script.notify_logs_maintenance
    data_template:
      variables:
        log_type: "Script"
        log_name: "Error Update ESP32 Device"
        log_event: "Update Failed"
        log_slug: "error_update_esp32_device"
        log_trigger: ""
        log_var1: "There was a problem updating {{ states.update | selectattr('state', 'eq', 'on') | map(attribute='entity_id') | select('in', integration_entities('esphome')) | list }}. Please check your ESPHome Addon."
  # Maintenance Log End
#
1 Like