Некоторое время назад попались мне на глаза достаточно интересные BLE метки от братьев-китайцев производства Holyiot. Я приобрел некоторое их количество и успешно пристроил для целей отслеживания ключей и пропусков. Но по прошествии некоторого времени у меня встал вопрос о мониторинге заряда их батарей. В итоге получилось достаточно интересно.
Практически все BLE устройства в моем хозяйстве интегрированы в HA с помощью ESPHome Bluetooth Proxy. Это действительно очень удобный механизм (хоть и не без недостатков конечно), позволяющий не думать о покрытии Bluetooth, просто добавляя в нужных местах и нужном количестве платы ESP32 с прошивкой ESPHome (Например M5Stack Atom Lite). Бонусом получая возможность некоторого Room Presence, например.
Не исключение и BLE метки Holyiot. В их магазине на Aliexpress большой выбор меток в разных форм-факторах, а так же с разным набором датчиков (кнопки, вибрация, температура и т.д.).
Вживую выглядят они как то так:


С помощью соответствующего приложения есть возможность изменить ряд настроек, например режим работы (beacon, ibeacon, eddystone), всевозможные ID или мощность и периодичность отправки пакетов (advertisement). Что ценно, особенно мощность сигнала и его периодичность. Уменьшив мощность и увеличив интервал можно существенно продлить срок службы батарейки.
Моя самая старая метка работает на одной батарейке уже больше года с остатком заряда ~50%.
На всякий случай — пароль по умолчанию для подключения к меткам Holyiot — aa14061112



И вот, озадачившись вопросом получения текущего заряда батареек этих меток я наткнулся на достаточно интересное решение позволяющее получить искомое. Для этого, правда, пришлось немного погрузиться в вопросы активного и пассивного сканирования эфира BLE и немного его поменять.
Дело в том, что компонент esp32_ble_tracker может работать как в пассивном (просто слушая эфир), так и в активном режиме (взаимодействуя с устройствами, самостоятельно отправляя запросы на которые устройства должны отвечать). При этом активный режим потребляет несколько больше энергии с т.з. конечных устройств, утилизируя заряд батареи более активно, что нам совершенно не нужно. Выдержка из документации:
active (Optional, boolean): Whether to actively send scan requests to request more data after having received an advertising packet. With some devices this is necessary to receive all data, but also drains those devices’ power a bit more. Some devices don’t need this, in that case you can save power and RF pollution by setting it tofalse. Defaults totrue.
Соответственно, для экономии заряда в идеале нужно все BT Proxy держать в пассивном режиме, но к сожалению это не позволяет получить данные заряда батареи. Метка отдает заряд только при активном сканировании.
Отсюда основная идея заключается в том, чтобы включать активный режим только в определенных ситуациях. Например, при получении первого advertise пакета от метки (срабатывает бинарный сенсор присутствия метки), или по расписанию. При таком подходе большую часть времени ESP слушает эфир в пассивном режиме, переключаясь в активный только по определенным событиям.
Далее будет представлен пример кода с комментариями. Все тайминги в свое время были рекомендованы авторами ESPHome Bluetooth Proxy, но с тех пор они поменяли свое мнение. Тем не менее, у меня данные параметры прекрасно себя зарекомендовали, поэтому оставлю их здесь без изменений. При необходимости можете самостоятельно попробовать менять значения interval/window и duration, желательно при этом опираясь на какую-либо документацию =)
esp32_ble_tracker:
id: ble_tracker_id
# На старте компонента мы указываем следующие параметры сканирования:
scan_parameters:
interval: 450ms
window: 160ms
duration: 10s
active: true
continuous: true
# При получении пакета от определенного mac-адреса (метки) с нужным нам service_uuid (5242),
# отправляем второй по счету (от 0) бит в нужный сенсор заряда батареи.
on_ble_service_data_advertise:
- mac_address: F9:9D:1D:40:23:40
service_uuid: '5242'
# id(black1_battery) = id нужного нам template сенсора, x[1] = бит из сообщения, содержащий заряд батареи.
# Найдено здесь - https://github.com/custom-components/ble_monitor/issues/1183
then:
- lambda: !lambda |-
id(black1_battery).publish_state(x[1]);
# Все то же самое для второй метки
- mac_address: E6:9F:37:5B:1D:DA
service_uuid: '5242'
then:
- lambda: !lambda |-
id(white1_battery).publish_state(x[1]);
sensor:
# Два template сенсора, в которые будут отправлены показания заряда батареек
- platform: template
name: Holy-IoT Black-1 Battery
id: black1_battery
device_class: battery
unit_of_measurement: '%'
entity_category: diagnostic
accuracy_decimals: 0
disabled_by_default: false
force_update: false
update_interval: 60s
- platform: template
name: Holy-IoT White-1 Battery
id: white1_battery
device_class: battery
unit_of_measurement: '%'
entity_category: diagnostic
accuracy_decimals: 0
disabled_by_default: false
force_update: false
update_interval: 60s
binary_sensor:
# binary_sensor присутствия метки, переключается в on при получении пакета с нужными ibeacon данными (uuid\major\minor)
# для того, что бы сгладить возможный "дребезг" датчика из за потерянных пакетов, установлен delayed_off в 30 сек.
# После перехода сенсора в on, запускается скрипт ble_active_scan, который и отвечает за изменение режимов.
- platform: ble_presence
name: Hallway Holy IoT Black-1 Presence
ibeacon_uuid: fda50693-a4e2-4fb1-afcf-c6eb07647825
ibeacon_major: 10011
ibeacon_minor: 1
device_class: presence
icon: mdi:key-chain-variant
filters:
- delayed_off: 30s
on_press:
- then:
- script.execute:
id: ble_active_scan
disabled_by_default: false
# То же самое для второй метки
- platform: ble_presence
name: Hallway Holy IoT White-1 Presence
ibeacon_uuid: fda50693-a4e2-4fb1-afcf-c6eb07647826
ibeacon_major: 10011
ibeacon_minor: 3
device_class: presence
icon: mdi:car-key
filters:
- delayed_off: 30s
on_press:
- then:
- script.execute:
id: ble_active_scan
script:
# Скрипт "на ходу" меняет режим (active\passive) и прочие настройки сканирования.
# Краткое описание параметров:
# set_scan_active = bool (true\false)
# set_scan_duration = milliseconds
# set_scan_interval = milliseconds / 0.625 (example: 450ms / 0.625 = 720)
# set_scan_window = milliseconds / 0.625 (example: 160ms / 0.625 = 256)
# set_scan_continuous = bool (true\false)
- id: ble_active_scan
# Режим запуска скрипта - single
mode: single
then:
# Остановка текущего сканирования.
- lambda: !lambda |-
id(ble_tracker_id).stop_scan();
# Задержка 2 сек
- delay: 2s
# Настройка параметров и старт сканирования (Active).
- lambda: !lambda |-
id(ble_tracker_id).set_scan_active(true);
id(ble_tracker_id).set_scan_duration(10);
id(ble_tracker_id).set_scan_interval(720);
id(ble_tracker_id).set_scan_window(256);
id(ble_tracker_id).set_scan_continuous(true);
id(ble_tracker_id).start_scan();
# Запись в лог.
- logger.log:
format: Active scan started
level: INFO
args: []
tag: main
# Продолжительность активного сканирования 30 секунд.
- delay: 30s
# Запись в лог.
- logger.log:
format: Active scan stopped
level: INFO
args: []
tag: main
# Остановка текущего (активного) сканирования.
- lambda: !lambda |-
id(ble_tracker_id).stop_scan();
# Задержка 2 сек.
- delay: 2s
# Настройка параметров и старт сканирования (Passive).
- lambda: !lambda |-
id(ble_tracker_id).set_scan_active(false);
id(ble_tracker_id).set_scan_duration(10);
id(ble_tracker_id).set_scan_interval(720);
id(ble_tracker_id).set_scan_window(256);
id(ble_tracker_id).set_scan_continuous(true);
id(ble_tracker_id).start_scan();
interval:
# Запуск активного сканирования по расписанию, раз в 30 минут.
# Вполне можно изменить расписание на раз в 24 часа или вообще убрать.
- interval: 30min
then:
- script.execute:
id: ble_active_scan
У меня датчики батареек для всех меток созданы на одной единственной ESP, которая расположена в коридоре, возле входной двери. А т.к. все мои метки висят либо на ключах, либо на пропусках, данные батареек будут гарантированно получены как минимум при входе или выходе из квартиры.

А для оповещения о низком заряде батарейных устройств у меня есть соответствующая статья.