Добрый день, уважаемый читатель! Продолжаю серию статей про готовое и вполне законченное устройство на ESP32 и Espressif IoT Development Framework. В прошлых статьях серии я рассказывал про:
Если вы не читали их, то рекомендую ознакомиться. В данной статье я приведу подробное (на мой взгляд, конечно) описание генерируемых устройством “стандартных” топиков.
Итак, если вы прошили микроконтроллер предлагаемой прошивкой, то получили на своем MQTT-брокере целый букет топиков, по большей части в JSON-формате. Теперь давайте разберемся, с чем это едят и для чего всё это нужно.

Пример структуры данных, публикуемых прошивкой на MQTT брокере
Почему JSON?
Для начала объясню, почему был выбран формат JSON.
Прошивка, в принципе, позволяет публиковать данные с сенсоров “напрямую” PLAIN, без оборачивания в JSON-формат (включить это можно с помощью макроса CONFIG_SENSOR_AS_PLAIN
, установив его значение в 1). Но, поскольку прошивка публикует для каждого сенсора сразу целый набор данных (значение, время изменения, экстремумы за разные периоды времени и т.д.), то это зачастую приводит к тому, что MQTT-клиент, да и сам брокер просто “захлебываются”, не справляясь с большим потоком данных. Кроме того, это ведёт к большему расходу кучи, так как приходится генерировать для всего этого отдельные топики. В итоге в своих устройствах я практически полностью отказался от публикации данных в “открытом” виде, исключением являются только топики обмена данными между устройствами.
Локальный и публичный брокеры
Как я уже писал в прошлой статье, прошивка поддерживает два брокера: основной и резервный. При этом каждый из них может быть локальным и публичным (облачным). Локальный брокер расположен “внутри” вашей локальной сети, а публичный – где-то в глобальной сети интернет. Топики для них могут различаться: например для локального брокера нет необходимости указывать локацию, а добавлять её к топику динамически в настройках моста “локальный <-> публичный”. Подробнее об этом можно почитать в самом конце статьи “MQTT брокер на роутере Keenetic“. Составные части топиков для локального брокера помечены как CONFIG_MQTTx_LOC_
, для публичных брокеров это будут, соответственно, CONFIG_MQTTx_PUB_
. Если вы не используете локальный брокер, вы можете не определять макросы CONFIG_MQTTx_LOC_
.
Схема генерации топиков
Большинство MQTT топиков программа формирует автоматически, из разных составных частей, определенных в файле конфигурации. Давайте рассмотрим эти части:
PREFIX
– общий префикс для топиков, определяется брокером. Большинство брокеров, работающих на mosquitto, не требует никаких префиксов в начале каждого топика. Но некоторые публичные брокеры, например clusterfly.ru или mqtt.by требуют указания имени пользователя в каждом топике, например: “/user_USERNAME/…”. Некоторые MQTT клиенты для Android требуют начинать топики с “/”. Вот для всех этих целей и предназначен префикс. Настраивается он с помощью параметров CONFIG_MQTT1_PUB_PREFIX
и CONFIG_MQTT1_LOC_PREFIX
(или CONFIG_MQTT2_PUB_PREFIX
и CONFIG_MQTT2_LOC_PREFIX
для резервного брокера).
LOCATION
– расположение устройства, или локация. Если PREFIX
не задан, то топики обычно начинаются именно с локации. Для локальных брокеров можно не указывать, но только если у вас одно расположение или если вы настроили автоматическое добавление локации в параметрах моста с локального брокера на публичный. Для указания LOCATION
используйте параметры CONFIG_MQTT1_PUB_LOCATION
и CONFIG_MQTT1_LOC_LOCATION
(или CONFIG_MQTT2_PUB_LOCATION
и CONFIG_MQTT2_LOC_LOCATION
для резервного брокера).
Применение <LOCATION>
дает ещё один не очевидный на первый взгляд плюс – позволяет разделить права на топики между разными пользователями на уровне брокера. Но в этой статье мы не будем пока касаться этой темы
DEVICE
– название устройства. Например это может быть конкретное место установки или функциональность устройства. По аналогии название настраивается с помощью макросов CONFIG_MQTT1_PUB_DEVICE
и CONFIG_MQTT1_LOC_DEVICE
.
Из этих составных частей формируется топики по общему правилу:
<PREFIX><LOCATION>/<DEVICE>/...
Обратите внимание – между <PREFIX>
и <LOCATION>
нет символа-разделителя /
, а между <LOCATION>
и <DEVICE>
он есть. Поэтому в PREFIX
, если он необходим, нужно указать его, а в остальных частях указывать разделитель уже не нужно.
На примере ниже первый уровень иерархии – это локация, второй уровень – устройства. Но иногда нужно добавить дополнительный уровень для дополнительно детализации, в этом случае я просто добавляю его в <DEVICE>
, например “home/main“.

Пример структуры топиков без префикса

Пример структуры топиков с префиксом
Думаю, теперь вам понятен принцип генерации топиков, можно переходить к их описанию
Системные топики
Устройство генерирует несколько “системных” топиков.
<LOCATION>/<DEVICE>/status
: cтатус устройства и одновременно LWT – Last Will and Testament, «последняя воля и завещание». Это один из немногих топиков, публикуемых в открытом виде (без JSON). Здесь может находиться следующая информация:
- Сразу после подключения к брокеру здесь публикуется значение “online” – это означает, что устройство доступно для управления
- Если подключение к устройству по какой-либо причине потеряно, брокер сам опубликует сюда значение “offline“. Это и есть LWT payload
- Когда устройство нормально работает, прошивка периодически (один раз в несколько минут) публикует сюда краткую системную информацию в виде трех строк.
- Первая строка – время наработки устройства с момента последнего запуска в формате ДНИ : ЧАСЫ : МИНУТЫ.
- Вторая строка – уровень сигнала WiFi.
- Третья строка: первая цифра – количество свободной кучи в %; затем идет количество зафиксированных ошибок выделения памяти; и в конце оставшийся объем свободных записей в разделе хранения параметров NVS.

Периодическая публикация системной информации
<LOCATION>/<DEVICE>/time
– в этот топик в начале каждой минуты публикуется системное время и дата в различных форматах в виде JSON-пакета. Это позволяет организовать на MQTT-клиенте плитку с датой и временем. С настройками “по умолчанию” данные в топике выглядят так:
{ "time": "10:34", "date": "12.10.22", "weekday": "ср", "timeday": "10:34 ср", "datetime1": "10:34\n12.10.22", "datetime2": "10:34 ср\n12.10.22", "year": 2022, "month": 9, "day": 12, "hour": 10, "min": 34, "wday": 3, "yday": 284, "worktime": {"days": 1, "hours": 13, "minutes": 48} }
<LOCATION>/<DEVICE>/sysinfo
– в этот топик периодически публикуется подробная системная информация, предназначенная по большей части для отладки устройства. Можно отключить с помощью макроса CONFIG_MQTT_SYSINFO_ENABLE
в файле конфигурации.

Настройки публикации системной информации
Данные в этом топике выглядят примерно так:
<LOCATION>/<DEVICE>/tasklist
– в этот топик периодически публикуется список задач с данными стека, также в JSON формате. Это позволяет подобрать оптимальный размер стека для каждой задачи. Можно включить или отключить с помощью макроса CONFIG_MQTT_TASKLIST_ENABLE
в файле конфигурации.
Топики настроек и параметров
Для удаленного управления параметрами устройства предусмотрен большой раздел <LOCATION>/<DEVICE>/config
. Все параметры здесь публикуются в открытом виде, без JSON.
В прошивке поддерживается изменение параметров с подтверждением получения в “ответном” разделе <LOCATION>/<DEVICE>/confirm
(включено по умолчанию). Это решает две задачи: клиент MQTT на смартфоне всегда знает последние актуальные настройки и мы всегда уверены, то изменение какого-либо параметра успешно “принято-понято” нашим устройством.
Как это работает:
В момент запуска и подключения к MQTT устройство публикует все известные ему параметры (которые хранятся в памяти NVS) в разделе <LOCATION>/<DEVICE>/confirm
.
Затем, при попытке изменения параметра мы должны отправить новое значение в <LOCATION>/<DEVICE>/config
. Получив эти данные, устройство проверяет значение, записывает его в хранилище NVS, и только после этого отправляет его обратно в <LOCATION>/<DEVICE>/confirm
. Клиент MQTT на смартфоне должен “откатить” отправленное значение, если не было получено подтверждение в течении некоторого времени.
Параметров много, “на все случаи жизни” даже в прошивке самой простой телеметрии / метеостанции:

Пример структуры топиков настроек
Давайте рассмотрим их поподробнее.
Я буду писать названия топиков “подтверждения” с confirm, но вы должны понимать, то для отправки данных на устройство нужно заменить на config.
<LOCATION>/<DEVICE>/confirm/common/silent_mode
– интервал времени суток “тихого режима”, когда отключаются встроенные светодиоды и (опционально) звуки, дабы не мешать сну. Указывать интервалы следует в формате Ч1:М1-Ч2:М2
.
Все параметры – интервалы указываются с точностью до минуты. Но при этом интервалы всегда отсчитываются с начала минуты, то есть 0 секунд каждой минуты.
<LOCATION>/<DEVICE>/confirm/notifications
– здесь собраны параметры, отвечающие за то, какие уведомления о проблемах вы будете получать от устройства.
Думаю, особые пояснения в данном случае нужны только под одному параметру: delay. Этот параметр отвечает за отложенные уведомления. Если на устройстве произошел какой-то сбой, то уведомление об этом сбое будет отправлено в чат только спустя указанное в delay количество секунд. Если в течение этого времени проблема самоликвидировалась, то и уведомления вы не получите. Это позволяет эффективно фильтровать кратковременные проблемы и отключения от сервисов. Поставьте 0 для немедленных уведомлений (полезно только для отладки).
<LOCATION>/<DEVICE>/confirm/ping
– параметры постоянного пинга серверов в интернете для контроля канала связи. Включается с помощью опции в файле конфигурации CONFIG_PINGER_ENABLE
. Параметров в этом разделе достаточно много, но изменять их требуется только в особо сложных случаях.
<LOCATION>/<DEVICE>/confirm/sensors/<НАЗВАНИЕ_СЕНСОРА>
– параметры сенсоров измерения различных физических величин. Для каждого сенсора доступны следующие параметры:
- offset – смещение значения. Может использоваться, если вы имеете образцовое средство измерения и хотите скорректировать измеренное до данных образцового
- filter_mode – режим фильтрации. На текущий момент может принимать три значения: 0 – фильтр отключен, 1 – среднее, 2 – медиана
- filter_size – размер буфера фильтра для filter_mode = 1 или 2
- delta_max – максимальное отклонение измеренного значения от предыдущего значения. Используется для сенсоров AHT10, у которых отсутствует CRC при передаче по шине, и невозможно как-то определить достоверность полученных данных. Из-за этого в некоторых случаях полученные данные могут скака аки тыгдымские кони. Вы можете задать максимальный лимит изменения за один период изменений, свыше которого полученное значение будет считаться “плохим” и отбрасываться.
<LOCATION>/<DEVICE>/confirm/temp_control/<НАЗВАНИЕ_СЕНСОРА>
– параметры контроля заданных диапазонов температуры. Контролировать можно не только температуры, правильнее было бы range_control, но так уж исторически сложилось…
- min – минимальное значение допустимого диапазона
- max – максимальное значение допустимого диапазона
- hysteresis – гистерезис, то есть разница между значениями перехода границы диапазона “туда” и “обратно”. Должен быть больше 0, чтобы не получать уведомления каждые несколько секунд на границах диапазонов
- notify – с помощью этого параметра можно включать и отключать уведомления в telegram
Топики сенсоров
Топики сенсоров генерируются по шаблону:
<LOCATION>/<DEVICE>/<НАЗВАНИЕ_СЕНСОРА>
Сюда публикуется JSON пакет данных, содержащий все имеющиеся данные для всех измеряемых величин (например температура, давление и влажность). Выглядит это примерно так:

Данные, полученные с сенсора
Корневой элемент JSON содержит поля:
- status – для информации о текущем состоянии сенсора
- display – смешанные многострочные данные с сенсора для отображения на одной плитке MQTT dashboard на смартфонах
- а также вложенные структуры JSON для каждого из измерительных элементов сенсора, в нашем случае это humidity и temperature.
Каждый элемент, в свою очередь также состоит из нескольких блоков информации:
- value – текущие (последние измерянные) данные
- extremums – зафиксированные экстремумы (минимумы и максимумы) за несколько периодов: entirely – всё время работы с момента его первого запуска; weekly – последняя неделя; daily – текущие сутки
Каждый из блоков содержит следующие поля:
- value – обработанное и отфильтрованное значение с учетом корректировки (смещения)
- raw – необработанное значение в том виде, а каком оно было получено непосредственно с сенсора
- time – время измерения
- tsv – обработанное значение, совмещенное с временем измерения в двух строках (для наглядности отображения)
Структура JSON не строго фиксированная, её можно корректировать в некоторых пределах:

Настройки формирования JSON пакета для сенсоров
Топики контроля диапазонов
В программе за контролем значений внутри заданных диапазонов (например температуры в помещении или инкубаторе) “занимается” определенный класс, которому выделен шаблон топиков:
<LOCATION>/<DEVICE>/temp_control/<НАЗВАНИЕ_СЕНСОРА>
Как и в предыдущих случаях, здесь также публикуется JSON-пакет, который содержит следующую информацию:

Контроль диапазонов
- status – состояние в цифровом виде: ошибка:-2; ниже нормы: -1; норма: 0; выше нормы: 1
- value – текущее значение
- last_normal – время, когда был зафиксирован последний переход к нормальному состоянию
- last_min – время, когда был зафиксирован переход ниже нижней границы
- last_max– время, когда был зафиксирован переход выше верхней границы
Этот класс может использоваться не только для контроля диапазонов температуры, но и например тока или напряжения.
Топики нагрузки
Если в составе устройства есть какая-либо программно-управляемая нагрузка, то устройство может публиковать состояние этой нагрузки.
<LOCATION>/<DEVICE>/<НАГРУЗКА>
Публикуемая информация имеет следующую структуру:

Информация о включениях нагрузки
- status – состояние нагрузки в текущий момент времени в цифровом виде: 0 – отключено; 1 – включено
- timestamp – время последнего включения и выключения нагрузки
- durations – длительность работы нагрузки в секундах за последний сеанс, текущий день, предыдущий день, неделю и т.д. Зная мощность нагрузки (если она примерно постоянная – вентилятор, котёл, лампа и т.д.), мы можем легко посчитать потребляемую мощность в кВт.
- counters – просто количество включений нагрузки за те же периоды. Не спрашивайте зачем – не знаю, путь будет
Разумеется, ваша прошивка может публиковать данные в какие-то другие топики. Вы можете проявить фантазию и реализовать ваши идеи. Но базовый набор строительных “кубиков” для сенсоров, диапазонов и нагрузок уже имеется, нужно только их правильно использовать. А как это сделать, я расскажу в следующих статьях.