Один из самых популярных беспроводных стандартов для устройств умного дома – ZigBee. Это достаточно энергоэффективный стандарт, позволивший создать огромное количество устройств, питающихся от батареек. И т.к. таких устройств в использовании со временем становится все больше и больше, рано или поздно встает вопрос мониторинга их заряда.
У себя я очень долгое время обходился максимально простой автоматизацией отслеживания заряда с заранее определенным списком датчиков, добавляя в него новые устройства по мере их появления. Но, как-то раз в одном из чатов Телеграма посвященном Home Assistant’у мне попался на глаза очень любопытный пример автоматизации, создающей и (самое главное!) поддерживающей в актуальном состоянии группу объектов по заранее определенным признакам. Мне подумалось что было бы неплохо применить это как раз в разрезе оповещения о низком заряде батареек ZigBee-устройств, а после того как я увидел живую реализацию моих мыслей в конфиге @to4ko я окончательно решил отбросить лень. =)
Для начала стоит разобраться в получившейся автоматизации, собирающей в одну группу все сенсоры батареек и периодически ее обновляющую:
- alias: "Create Group of Battery Devices" trigger: - platform: homeassistant event: start - platform: time_pattern hours: "/2" action: - service: group.set data: object_id: battery_devices entities: >- {%- for state in states.sensor if is_state_attr(state.entity_id, 'device_class', 'battery') and (state.entity_id.endswith("_battery") or state.entity_id.endswith("_power")) %} {{ state.entity_id }}{%- if not loop.last -%}, {%- endif -%} {%- endfor %}
Здесь интересен сервис group.set
, который позволяет создать группу (если ее не существует), а так же актуализировать ее состояние при последующих вызовах. Помимо group.set, существуют так-же сервисы group.reload
и group.remove
, о предназначении которых можно судить исходя из их названия.
Параметр object_id
задает имя группы, в приведенном примере объект группы получит имя group.battery_devices
.
Список (массив) объектов для включения в группу формируется с помощью шаблона, в цикле for
отбирая объекты state из домена sensor
по признакам определенным в условии if
:is_state_attr(state.entity_id, 'device_class', 'battery')
– device_class
у объекта должен быть battery
,
и(state.entity_id.endswith("_battery") or state.entity_id.endswith("_power"))
– имена объектов должны заканчиваться на _battery
или _power
.
Т.к. у меня ZigBee устройства подключены к HA с помощью двух разных интеграций – XiaomiGateway3 и ZHA то и сенсоры батареек для устройств немного отличаются, отсюда необходимость отбирать объекты сразу по нескольким признакам.
Эта автоматизация будет выполнятся при каждой загрузке HA (т.к. группы нет в конфиге, и она создается только при вызове сервиса group.set) и далее каждые два часа по триггеру time_pattern
.
Результат ее работы будет выглядеть как-то так:
После этого, можно уже подумать над автоматизацией которая будет оперировать созданной группой и своевременно оповещать о необходимости замены батареек в устройствах. Здесь есть разные варианты, как то – обход группы скриптами по расписанию, сортировка объектов по уровню заряда и отправка ежедневных (например) отчетов и т.д.
Мне же хотелось получать оповещения именно по факту падения заряда ниже определенного значения и у меня получился вот такой вариант:
- alias: "Low Battery Alert" trigger: - platform: event event_type: state_changed condition: - condition: template value_template: >- {{ trigger.event.data.entity_id in (expand('group.battery_devices') | map(attribute='entity_id')) }} - condition: template value_template: >- {{ not trigger.event.data.new_state.state in ['unknown', 'unavailable'] }} - condition: template value_template: >- {{ (trigger.event.data.new_state.state | int) < 20 }} action: - service: notify.telegram data: message: | *Внимание, низкий заряд батареи!* {{ trigger.event.data.new_state.attributes.friendly_name }}: {{ trigger.event.data.new_state.state }}%!
Автоматизация срабатывает по событию (event) state_changed
, т.е. по изменению состояния любого объекта в системе, в т.ч. и сенсоров батареек. Далее, с помощью condition
отбираются интересующие нас объекты, а именно те, что входят в группу battery_devices:{{ trigger.event.data.entity_id in (expand('group.battery_devices') | map(attribute='entity_id')) }}
После этого отбрасываются не интересующие нас состояния – unknown, например:{{ not trigger.event.data.new_state.state in ['unknown', 'unavailable'] }}
И наконец, события фильтруются по новому заряду батареи – меньше 20%:{{ (trigger.event.data.new_state.state | int) < 20 }}
В результате, оповещение будет отправлено при изменении стейта (state) у любого из объектов в группе battery_devices, если новое значение будет ниже 20.
Важный момент здесь – оповещение будет отправляться при каждом изменении стейта объекта, если новое значение удовлетворяет условиям. Например, для любого из датчиков входящих в группу group.battery_devices оповещения будут отправляться при изменении его значения на 19, 18, 17, 16 и т.д.
С одной стороны это кажется излишним спамом, но с учетом того, что обычно заряд батареек не падает слишком быстро, это лишнее напоминание о том, что уже давно пора поменять батарейку.
Наверное, можно было бы обойтись и одной автоматизацией, совместив в ней одновременно и отбор нужных сенсоров из событий и их фильтрацию по состояниям, но вариант с группой кажется мне более универсальным, позволяя в дальнейшем переиспользовать группу в других местах.
На просторах интернета гуляет огромное кол-во вариантов решения задачи по мониторингу заряда батарей, от совсем простых, до огромных packages на 800+ строк. Но в этом то и прелесть ХА, что одну и ту же задачу можно решить множеством разных способов.
Бонусом приведу пару вариантов для наглядного отображения всех батареек с сортировкой по заряду:
Вариант с одной кастомной карточкой flex-table-card (отбор сенсоров по маскам, не очень удобно):
title: Battery Info path: battery icon: mdi:battery cards: - type: vertical-stack cards: - type: markdown content: "### <center> Device Battery Info </center>" - type: "custom:flex-table-card" clickable: false entities: include: - sensor.*_battery - sensor.battery_status_* - sensor.lumi_*_power exclude: - sensor.mirobot_1s_battery sort_by: state+ columns: - data: friendly_name name: Friendly Name modify: '(x+"").replace(/ Battery$/,"").replace(/ power$/,"")' - data: state name: Remaining % modify: "isNaN(parseInt(x, 10)) ? 0 : parseInt(x, 10)" css: table+: "padding-top: 2px;" "tbody tr:nth-child(even)": "background-color: #a2542f6;" td.left: "padding: 2px 2px 2px 2px" th.left: "padding: 0px 0px 2px 2px"
Вариант с комбинацией из flex-table-card и auto-entities (используется ранее созданная группа group.battery_devices):
title: Battery Info path: battery icon: mdi:battery cards: - type: vertical-stack cards: - type: markdown content: "### <center> Device Battery Info </center>" - type: "custom:auto-entities" filter: include: - group: group.battery_devices exclude: - entity_id: sensor.mirobot_1s_battery sort: method: state numeric: true card: type: "custom:flex-table-card" clickable: false columns: - data: friendly_name name: Friendly Name modify: '(x+"").replace(/ Battery$/,"").replace(/ power$/,"")' - data: state name: Remaining #modify: "isNaN(parseInt(x, 10)) ? 0 : parseInt(x, 10)" modify: x+' %' css: table+: "padding-top: 2px;" "tbody tr:nth-child(even)": "background-color: #a2542f6;" td.left: "padding: 2px 2px 2px 2px" th.left: "padding: 0px 0px 2px 2px"