Найти в Дзене
K12 :: О ESP32 и не только

Термостат на ESP32 с удаленным управлением. Часть 4. MQTT-топики

Оглавление

Добрый день, уважаемый читатель! В прошлых статьях я рассказал, как собрать устройство на базе ESP32 DevKitC WROOM-32x и запрограммировать его. Если вы не читали их, то рекомендую ознакомиться.

Итак, если вы прошили микроконтроллер предлагаемой прошивкой, то получили на своем MQTT-брокере целый букет топиков, по большей части в JSON-формате. Теперь давайте разберемся, с чем это едят и для чего всё это нужно.

Пример структуры данных, публикуемых прошивкой на MQTT брокере
Пример структуры данных, публикуемых прошивкой на MQTT брокере

Почему 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 не строго фиксированная, её можно корректировать в некоторых пределах:

Настройки формирования 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 - просто количество включений нагрузки за те же периоды. Не спрашивайте зачем - не знаю, путь будет

Разумеется, ваша прошивка может публиковать данные в какие-то другие топики. Вы можете проявить фантазию и реализовать ваши идеи. Но базовый набор строительных "кубиков" для сенсоров, диапазонов и нагрузок уже имеется, нужно только их правильно использовать. А как это сделать, я расскажу в следующих статьях.

А в следующей статье я планирую рассказать, как настроить клиент Mqtt Dash для работы с этой прошивкой

Предыдущие статьи цикла:

_______________

На этом пока всё, до встречи на сайте и на dzen-канале!

👍 Понравилась статья? Поддержите канал лайком или комментарием! Каналы на Дзене "живут" только за счет ваших лайков.

📌Подпишитесь на канал и вы всегда будете в курсе новых статей.

🔶 Полный архив статей вы найдете здесь

Благодарю за вашу поддержку! 🙏