Добрый день, уважаемый читатель! В прошлых статьях я рассказал, как собрать устройство на базе ESP32 DevKitC WROOM-32x и запрограммировать его. Если вы не читали их, то рекомендую ознакомиться.
Итак, если вы прошили микроконтроллер предлагаемой прошивкой, то получили на своем MQTT-брокере целый букет топиков, по большей части в JSON-формате. Теперь давайте разберемся, с чем это едят и для чего всё это нужно.
Почему JSON?
Для начала объясню, почему был выбран формат JSON.
Прошивка, в принципе, позволяет публиковать данные с сенсоров "напрямую" PLAIN, без оборачивания в JSON-формат (включить это можно с помощью макроса CONFIG_SENSOR_AS_PLAIN, установив его значение в 1). Но, поскольку прошивка публикует для каждого сенсора сразу целый набор данных (значение, время изменения, экстремумы за разные периоды времени и т.д.), то это зачастую приводит к тому, что MQTT-клиент, да и сам брокер просто "захлебываются", не справляясь с большим потоком данных. Кроме того, это ведёт к большему расходу кучи, так как приходится генерировать для всего этого отдельные топики. В итоге в своих устройствах я практически полностью отказался от публикации данных в "открытом" виде, исключением являются только топики обмена данными между устройствами.
Локальный и публичный брокеры
Как я уже писал в прошлой статье, прошивка поддерживает два брокера: основной и резервный. При этом каждый из них может быть локальным и публичным (облачным). Локальный брокер расположен "внутри" вашей локальной сети, а публичный - где-то в глобальной сети интернет. Топики для них могут различаться: например для локального брокера нет необходимости указывать локацию, а добавлять её к топику динамически в настройках моста "локальный <-> публичный". Подробнее об этом можно почитать в самом конце статьи ниже
Составные части топиков для локального брокера помечены как 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.
Все параметры - интервалы указываются с точностью до минуты. Но при этом интервалы всегда отсчитываются с начала минуты, то есть 00 секунд каждой минуты.
<LOCATION>/<DEVICE>/confirm/notifications - здесь собраны параметры, отвечающие за то, какие уведомления о проблемах вы будете получать от устройства.
Думаю, особые пояснения в данном случае нужны только под одному параметру: 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 не строго фиксированная, её можно корректировать в некоторых пределах:
Топики контроля диапазонов
В программе за контролем значений внутри заданных диапазонов "занимается" определенный класс, которому выделен шаблон топиков:
<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 - просто количество включений нагрузки за те же периоды. Не спрашивайте зачем - не знаю, путь будет
Разумеется, ваша прошивка может публиковать данные в какие-то другие топики. Вы можете проявить фантазию и реализовать ваши идеи. Но базовый набор строительных "кубиков" для сенсоров, диапазонов и нагрузок уже имеется, нужно только их правильно использовать. А как это сделать, я расскажу в следующих статьях.
А в следующей статье я планирую рассказать, как настроить клиент Mqtt Dash для работы с этой прошивкой
Предыдущие статьи цикла:
_______________
На этом пока всё, до встречи на сайте и на dzen-канале!
👍 Понравилась статья? Поддержите канал лайком или комментарием! Каналы на Дзене "живут" только за счет ваших лайков.
📌Подпишитесь на канал и вы всегда будете в курсе новых статей.
🔶 Полный архив статей вы найдете здесь
Благодарю за вашу поддержку! 🙏