Перейти к содержимому

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

Добрый день, уважаемый читатель! Продолжаю серию статей про готовое и вполне законченное устройство на 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": "12:50",
  "date": "03.05.24",
  "weekday": "пт",
  "timeday": "12:50 пт",
  "datetime1": "12:50\n03.05.24",
  "datetime2": "12:50 пт\n03.05.24",
  "year": 2024,
  "month": 4,
  "day": 3,
  "hour": 12,
  "min": 50,
  "wday": 5,
  "yday": 123,
  "worktime": {
    "days": 12,
    "hours": 6,
    "minutes": 41
  }
}

 

🔸<LOCATION>/<DEVICE>/sysinfo – в этот топик периодически публикуется подробная системная информация, предназначенная по большей части для отладки устройства. Можно отключить с помощью макроса CONFIG_MQTT_SYSINFO_ENABLE в файле конфигурации.

// EN: Allow periodic publication of system information
// RU: Разрешить периодическую публикацию системной информации
#define CONFIG_MQTT_SYSINFO_ENABLE 1
#define CONFIG_MQTT_SYSINFO_INTERVAL 1000*60*5
#define CONFIG_MQTT_SYSINFO_SYSTEM_FLAGS 1           

Данные в этом топике выглядят примерно так:

{
  "firmware": "20230826.120",
  "cpu_mhz": 240,
  "wifi": {"ssid": "k12iot", "rssi": -30, "ip": "192.168.2.51", "mask": "255.255.255.0", "gw": "192.168.2.1"},
  "worktime": {"days": 16, "hours": 7, "minutes": 57},
  "heap": {"total": 265.9, "errors": 0, "free": 130.2, "free_percents": 49, "free_min": 73.3, "free_min_percents": 27.5},
  "nvs": {"total_entries": 30870, "used_entries": 814, "used_percents": 2.6, "free_entries": 30056, "free_percents": 97.4, "namespaces": 28},
  "errors": {"general": 0, "heap": 0, "mqtt": 0, "telegram": 0, "smtp": 0, "site": 0, "thingspeak": 0, "openmon": 0, "narodmon": 0, "sensor0": 0, "sensor1": 0, "sensor2": 0, "sensor3": 0, "sensor4": 0, "sensor5": 0, "sensor6": 0, "sensor7": 0},
  "sys_flags": {"ota": 0, "rtc_enabled": 0, "sntp_sync": 1, "silent_mode": 0, "wifi_sta_started": 1, "wifi_sta_connected": 1, "inet_availabled": 1, "mqtt1_enabled": 0, "mqtt2_enabled": 0, "mqtt_connected": 1, "mqtt_primary": 1, "mqtt_local": 1},
  "wifi_flags": { "init_tcpip": 1, "init_low": 1, "sta_enabled": 1, "sta_started": 1, "sta_connected": 1, "sta_got_ip": 1, "disconnect_and_stop": 0, "disconnect_and_restore": 0}
}

 

🔸<LOCATION>/<DEVICE>/tasklist – в этот топик периодически публикуется список задач с данными стека, также в JSON формате. Это позволяет подобрать оптимальный размер стека для каждой задачи. Можно включить или отключить с помощью макроса CONFIG_MQTT_TASKLIST_ENABLE в файле конфигурации.

[
  {"id": 3, "name": "esp_timer", "core": 0, "state": "running", "current_priority": 22, "base_priority": 22, "run_time_counter": 0, "stack_base": "3ffbeca0", "stack_minimum": 1776},
  {"id": 6, "name": "IDLE", "core": 1, "state": "ready", "current_priority": 0, "base_priority": 0, "run_time_counter": 0, "stack_base": "3ffc0fac", "stack_minimum": 1700},
  {"id": 5, "name": "IDLE", "core": 0, "state": "ready", "current_priority": 0, "base_priority": 0, "run_time_counter": 0, "stack_base": "3ffc06a8", "stack_minimum": 1704},
  {"id": 17, "name": "alarm", "core": 1, "state": "blocked", "current_priority": 8, "base_priority": 8, "run_time_counter": 0, "stack_base": "3ffbbc84", "stack_minimum": 796},
  {"id": 23, "name": "mqtt_task", "core": -1, "state": "blocked", "current_priority": 12, "base_priority": 12, "run_time_counter": 0, "stack_base": "3ffda3a8", "stack_minimum": 2024},
  {"id": 8, "name": "led_system", "core": 1, "state": "blocked", "current_priority": 13, "base_priority": 13, "run_time_counter": 0, "stack_base": "3ffc9eb8", "stack_minimum": 408},
  {"id": 22, "name": "pinger", "core": 1, "state": "blocked", "current_priority": 3, "base_priority": 3, "run_time_counter": 0, "stack_base": "3ffb68d8", "stack_minimum": 696},
  {"id": 13, "name": "sensors", "core": 1, "state": "blocked", "current_priority": 6, "base_priority": 6, "run_time_counter": 0, "stack_base": "3ffb7710", "stack_minimum": 1232},
  {"id": 19, "name": "tiT", "core": -1, "state": "blocked", "current_priority": 18, "base_priority": 18, "run_time_counter": 0, "stack_base": "3ffd1838", "stack_minimum": 1208},
  {"id": 10, "name": "tg_send", "core": 1, "state": "blocked", "current_priority": 11, "base_priority": 11, "run_time_counter": 0, "stack_base": "3ffb3e65", "stack_minimum": 747},
  {"id": 7, "name": "Tmr Svc", "core": 0, "state": "blocked", "current_priority": 1, "base_priority": 1, "run_time_counter": 0, "stack_base": "3ffc1a14", "stack_minimum": 1580},
  {"id": 1, "name": "ipc0", "core": 0, "state": "blocked", "current_priority": 24, "base_priority": 24, "run_time_counter": 0, "stack_base": "3ffaf0a0", "stack_minimum": 448},
  {"id": 12, "name": "buzzer", "core": 0, "state": "blocked", "current_priority": 14, "base_priority": 14, "run_time_counter": 0, "stack_base": "3ffb3844", "stack_minimum": 364},
  {"id": 14, "name": "led_alarm", "core": 1, "state": "blocked", "current_priority": 13, "base_priority": 13, "run_time_counter": 0, "stack_base": "3ffcd018", "stack_minimum": 488},
  {"id": 18, "name": "sys_evt", "core": 0, "state": "blocked", "current_priority": 20, "base_priority": 20, "run_time_counter": 0, "stack_base": "3ffd05b0", "stack_minimum": 1536},
  {"id": 2, "name": "ipc1", "core": 1, "state": "blocked", "current_priority": 24, "base_priority": 24, "run_time_counter": 0, "stack_base": "3ffaf608", "stack_minimum": 472},
  {"id": 9, "name": "re_events", "core": 0, "state": "blocked", "current_priority": 16, "base_priority": 16, "run_time_counter": 0, "stack_base": "3ffca8fc", "stack_minimum": 1812},
  {"id": 11, "name": "http_send", "core": 1, "state": "blocked", "current_priority": 5, "base_priority": 5, "run_time_counter": 0, "stack_base": "3ffb528c", "stack_minimum": 1124},
  {"id": 20, "name": "wifi", "core": 0, "state": "blocked", "current_priority": 23, "base_priority": 23, "run_time_counter": 0, "stack_base": "3ffd3484","stack_minimum": 4556},
  {"id": 15, "name": "siren", "core": 1, "state": "blocked", "current_priority": 13, "base_priority": 13, "run_time_counter": 0, "stack_base": "3ffcd6cc", "stack_minimum": 516},
  {"id": 16, "name": "flasher", "core": 1, "state": "blocked", "current_priority": 13, "base_priority": 13, "run_time_counter": 0, "stack_base": "3ffcdd24", "stack_minimum": 524}
]

 

Есть ещё два топика, которые работают только “на вход”,  то есть через них можно что-то отправить на устройство, но их нельзя увидеть например через MQTT Explorer:

🔸<LOCATION>/<DEVICE>/system/ota – в этот топик вы должны отправить ссылку на прошивку (BIN-файл) для обновления “по воздуху” (ОТА). Подробнее об этом можно почитать тут.

🔸<LOCATION>/<DEVICE>/system/terminal – в этот топик можно отправлять служебные команды для выполнения каких-либо действий, например перезагрузки. Подробнее об этом можно почитать тут.

 


Топики настроек и параметров

Для удаленного управления параметрами устройства предусмотрен большой раздел <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 пакет данных, содержащий все имеющиеся данные для всех измеряемых величин (например температура, давление и влажность). Выглядит это примерно так:

{
  "status": "OK",
  "humidity": {
    "value": {"value": 39.83, "raw": 33, "time": "03.05.24 12:41", "tsv": "39.83%\n03|12:41"},
    "extremums": {
      "entirely": {
        "min": {"value": 21.15, "raw": 21.15345, "time": "19.06.23 11:38", "tsv": "21.15%\n19|11:38"},
        "max": {"value": 100, "raw": 99.999908, "time": "09.07.23 21:45", "tsv": "100.00%\n09|21:45"}
      },
      "weekly": {
        "min": {"value": 33.66, "raw": 27.200001, "time": "30.04.24 14:51", "tsv": "33.66%\n30|14:51"},
        "max": {"value": 94.87, "raw": 87.800003, "time": "30.04.24 04:43", "tsv": "94.87%\n30|04:43"}
      },
      "daily": {
        "min": {"value": 37.59, "raw": 31.799999, "time": "03.05.24 12:10", "tsv": "37.59%\n03|12:10"},
        "max": {"value": 90.89, "raw": 83.5, "time": "03.05.24 01:56", "tsv": "90.89%\n03|01:56"}
      }
    }
  },
  "temperature": {
    "value": {"value": 6.32, "raw": 7.2, "time": "03.05.24 12:41", "tsv": "6.32°С\n03|12:41"},
    "extremums": {
      "entirely": {
        "min": {"value": -22.68, "raw": -22.676277, "time": "09.12.23 07:23", "tsv": "-22.68°С\n09|07:23"},
        "max": {"value": 43.45, "raw": 43.449593, "time": "06.08.23 11:05", "tsv": "43.45°С\n06|11:05"}
      },
      "weekly": {
        "min": {"value": -1.12, "raw": 0.1, "time": "03.05.24 04:58", "tsv": "-1.12°С\n03|04:58"},
        "max": {"value": 24.37, "raw": 25.6, "time": "30.04.24 14:11", "tsv": "24.37°С\n30|14:11"}
      },
      "daily": {
        "min": {"value": -1.12, "raw": 0.1, "time": "03.05.24 04:58", "tsv": "-1.12°С\n03|04:58"},
        "max": {"value": 7.39, "raw": 8.6, "time": "03.05.24 12:26", "tsv": "7.39°С\n03|12:26"}
      }
    }
  },
  "display": "6.32°С\n39.83%"
}

Корневой элемент 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": 0,
  "value": 18.56237,
  "last_normal": "01.02.2024 12:05",
  "last_min": "01.02.2024 11:57",
  "last_max": "---"
}
  • status – состояние в цифровом виде: ошибка:-2; ниже нормы: -1; норма: 0; выше нормы: 1
  • value – текущее значение
  • last_normal – время, когда был зафиксирован последний переход к нормальному состоянию
  • last_min – время, когда был зафиксирован переход ниже нижней границы
  • last_max– время, когда был зафиксирован переход выше верхней границы

Этот класс может использоваться не только для контроля диапазонов температуры, но и например тока или напряжения.


Топики нагрузки

Если в составе устройства есть какая-либо программно-управляемая нагрузка, то устройство может публиковать состояние этой нагрузки: <LOCATION>/<DEVICE>/<НАГРУЗКА>

Публикуемая информация имеет следующую структуру:

{
  "status": 0,
  "timestamp": {"on": "03.05.2024 07:28:23", "off": "03.05.2024 07:47:21"},
  "durations": {"last": 1138, "total": 556347, "today": 29027, "yesterday": 46470, "week_curr": 76123, "week_prev": 0, "month_curr": 75497, "month_prev": 626, "period_curr": 76123, "period_prev": 0, "year_curr": 76123, "year_prev": 0},
  "counters": {"total": 33, "today": 1, "yesterday": 5, "week_curr": 9, "week_prev": 0, "month_curr": 7, "month_prev": 2, "period_curr": 9, "period_prev": 0, "year_curr": 9, "year_prev": 0}
}
  • status – состояние нагрузки в текущий момент времени в цифровом виде: 0 – отключено1 – включено
  • timestamp – время последнего включения и выключения нагрузки
  • durations – длительность работы нагрузки в секундах за последний сеанс, текущий день, предыдущий день, неделю и т.д. Зная мощность нагрузки (если она примерно постоянная – вентилятор, котёл, лампа и т.д.), мы можем легко посчитать потребляемую мощность в кВт.
  • counters – просто количество включений нагрузки за те же периоды. Не спрашивайте зачем – не знаю, путь будет

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

 


Все статьи цикла “Термостат и ОПС”:

Прошивка K12 для ESP32 и ESP-IDF:

Дополнительные статьи, которые применимы к любым устройствам, запрограммированным с помощью тех же самых библиотек.

 

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


Пожалуйста, оцените статью:
[ 5 из 5, всего 2 оценок ]

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *