Добрый день, уважаемый читатель!
Данная статья является в первую продолжением серии “Термостат на ESP32 с удаленным управлением“, но описанное в статье в полной мере относится и к “Автоматической теплице“, и к “Автоматическому поливу“, да и к вообще к любым устройствам автоматики, собранным на ESP32 и запрограммированным с помощью ESP-IDF и моих библиотек для этой платформы. Поэтому вынес её в “общий” раздел под названием “Прошивка K12 для ESP32“. А поговорим мы в ней о командах управления.
Если вы читали статьи из перечисленных серий, то наверное поняли, что основным каналом управления устройством является протокол MQTT. С его помощью мы можем изменять различные настройки и управлять исполнительными устройствами, используя специальные топики управления и изменения параметров с обратной связью. Но иногда нужно просто отправить на устройство какую – либо команду без ожидания ответа от устройства, например:
- перезапустить устройство
- обновить прошивку через технологию OTA
- сбросить сохраненные данные о зафиксированных минимумах и максимумах для сенсоров
- выполнить какую-либо другую команду поверх логики встроенного конечного автомата
Конечно, это легко можно сделать, “прикрутив” в прошивку подписки на какие-нибудь специальные “управляющие” топики для каждой команды в отдельности. Но я поступил по другому – создал всего два “специальных” топика, в которые можно отправлять заранее определенные текстовые команды:
- %location%/%device%/system/terminal – в этот топик можно отправлять текстовые команды управления ( почему именно terminal а не commands? – на момент создания этого функционала топик commands был “занят” под другие команды, но потом я от него избавился совсем; впрочем это можно изменить )
- %location%/%device%/system/ota – а сюда вы должны отправить ссылку на BIN-файл прошивки для “обновления по воздуху” ( OTA ) в формате “https://server/…/firmware.bin”
где %location%/%device% – параметры-макросы CONFIG_MQTTx_PUB_LOCATION и CONFIG_MQTTx_PUB_DEVICE из файла project_config.h. Про обновление прошивки через OTA поговорим в следующей статье, а в данной обсудим только команды управления, а также как можно добавить свои собственные команды в прошивку.
Поскольку эти топики работают только в одном направлении (на вход), их нельзя увидеть через программу MQTT Explorer.
Как это работает?
После запуска микроконтроллера и подключения устройства к MQTT-брокеру, оно “подписывается” на указанный выше топик “/system/terminal” и ждет новых сообщений. Про поступлении нового сообщения в указанном топике устройство обрабатывает его и само стирает данные из топика, отправив туда NULL. Это является подтверждением того, что команда была получена и обработана должным образом. Дополнительно отправляется сообщение в telegram-канал управления:%location%/%device%
Вся “физическая” обработка команд реализована в функции paramsExecCmd() в модуле reParams.cpp:
Для простоты понимания здесь я выделил блоки:
- синий – отправка сообщения в telegram о получении команды
- желтый – удаление полученной команды из топика
- красный – выполнение встроенных команд (пока одна – подробнее см. ниже)
- зеленый – если команда не встроенная, то отправляем событие
RE_SYSTEM_EVENTS.RE_SYS_COMMANDв прикладной цикл событий с текстом полученной команды. Ваше приложение должно подписаться на это событие, получить его и соответствующим образом обработать. Как это сделать – будет рассказано ниже.
Встроенные команды
На текущий момент устройства (например термостат) имеют всего одну “встроенную” команду: restart – программный перезапуск устройства.
Получив сообщение с текстом “restart” в топик “…/system/terminal” устройство проведет все необходимые “заключительные процедуры” (например сохранит оперативные данные на флеш-память и отключит нагрузку), а затем выполнит программный сброс через системную функцию esp_restart().
Через какое-то время устройство выполнит указанное действие:
Вот, в общем-то и все “встроенные” команды.
Пользовательские команды
Но не все так грустно – вы можете легко и просто добавлять обработчики своих, кастомных команд по своему разумению. Более того, в перечисленных выше прошивках “Термостат“, “Автоматическая теплица” и “Автополив” уже имеются такие “пользовательские команды, даже несколько.
ClrExtr – cброс экстремумов сенсоров
Если вы читали статью про класс rSensor и для чего он нужен, то, наверное, помните, что любой драйвер сенсора или датчика, построенный на базе этого класса, умеет накапливать данные об экстремумах измеренных значений, то есть минимумах и максимумах за текущие сутки, неделю и все время работы. Эти данные сохраняются в энергонезависимой памяти ( NVS ) по при следующем включении устройства будут восстановлены. Но иногда необходимо “сбросить” эти сохраненные данные, например если были получены явно неверные данные по каким-либо причинам. Дабы иметь возможность для такого сброса, предусмотрена специальная “пользовательская” команда – clrextr (CLear EXTRemums), которая уже имеется в прошивках.
Кроме того, команда clrextr – не совсем простая команда. Она может быть выполнена в нескольких вариантах:
- clrextr – сброс всех экстремумов у всех датчиков, подключенных к данному устройству
- clrextr daily – сброс только суточных экстремумов у всех датчиков, подключенных к данному устройству
- clrextr weekly – сброс только недельных экстремумов у всех датчиков, подключенных к данному устройству
- clrextr entirely – сброс только абсолютных экстремумов у всех датчиков, подключенных к данному устройству
- clrextr датчик – сброс всех экстремумов только для конкретно указанного датчика
- clrextr датчик daily – сброс только суточных экстремумов и только для конкретно указанного датчика
- clrextr датчик weekly – сброс только недельных экстремумов и только для конкретно указанного датчика
- clrextr датчик entirely – сброс только абсолютных экстремумов и только для конкретно указанного датчика
Системное имя датчика задается программистом при написании прошивки.
Но почему эта команда не “встроенная”? Дело в том, что заранее никогда не известно сколько и каких датчиков будет подключено к устройству. А “дернуть за хвост” приходится каждый конкретный экземпляр (объект) датчика. Вот и приходится каждый раз копипастить этот код из прошивки в прошивку с необходимыми изменениями. На её примере я и расскажу, как добавлять свои команды при необходимости.
Итак, как я уже рассказал выше, нам нужно подписаться на событие RE_SYSTEM_EVENTS.RE_SYS_COMMAND и обработать его. Для этого напишем функцию: void sensorsCommandsEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data):
static void sensorsCommandsEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
if ((event_id == RE_SYS_COMMAND) && (event_data)) {
char* buf = malloc_string((char*)event_data);
if (buf != nullptr) {
const char* seps = " ";
char* cmd = nullptr;
char* mode = nullptr;
char* sensor = nullptr;
uint8_t imode = 0;
cmd = strtok(buf, seps);
// Команды сброса датчиков
if ((cmd != nullptr) && (strcasecmp(cmd, CONFIG_SENSOR_COMMAND_EXTR_RESET) == 0)) {
rlog_i(logTAG, "Reset extremums: %s", buf);
sensor = strtok(nullptr, seps);
if (sensor != nullptr) {
mode = strtok(nullptr, seps);
};
// Определение режима сброса
if (mode == nullptr) {
// Возможно, вторым токеном идет режим, в этом случае сбрасываем для всех сенсоров
if (sensor) {
if (strcasecmp(sensor, CONFIG_SENSOR_EXTREMUMS_DAILY) == 0) {
sensor = nullptr;
imode = 1;
} else if (strcasecmp(sensor, CONFIG_SENSOR_EXTREMUMS_WEEKLY) == 0) {
sensor = nullptr;
imode = 2;
} else if (strcasecmp(sensor, CONFIG_SENSOR_EXTREMUMS_ENTIRELY) == 0) {
sensor = nullptr;
imode = 3;
};
};
} else if (strcasecmp(mode, CONFIG_SENSOR_EXTREMUMS_DAILY) == 0) {
imode = 1;
} else if (strcasecmp(mode, CONFIG_SENSOR_EXTREMUMS_WEEKLY) == 0) {
imode = 2;
} else if (strcasecmp(mode, CONFIG_SENSOR_EXTREMUMS_ENTIRELY) == 0) {
imode = 3;
};
// Определение сенсора
if ((sensor == nullptr) || (strcasecmp(sensor, CONFIG_SENSOR_COMMAND_SENSORS_PREFIX) == 0)) {
sensorsResetExtremumsSensors(imode);
} else {
if (strcasecmp(sensor, SENSOR_SOIL_TOPIC) == 0) {
sensorsResetExtremumsSensor(&sensorSoil, SENSOR_SOIL_TOPIC, imode);
} else if (strcasecmp(sensor, SENSOR_INDOOR_TOPIC) == 0) {
sensorsResetExtremumsSensor(&sensorIndoor, SENSOR_INDOOR_TOPIC, imode);
} else if (strcasecmp(sensor, SENSOR_HEATING_TOPIC) == 0) {
sensorsResetExtremumsSensor(&sensorHeating, SENSOR_HEATING_TOPIC, imode);
} else {
rlog_w(logTAG, "Sensor [ %s ] not found", sensor);
#if CONFIG_TELEGRAM_ENABLE
tgSend(CONFIG_SENSOR_COMMAND_KIND, CONFIG_SENSOR_COMMAND_PRIORITY, CONFIG_SENSOR_COMMAND_NOTIFY, CONFIG_TELEGRAM_DEVICE,
CONFIG_MESSAGE_TG_SENSOR_CLREXTR_UNKNOWN, sensor);
#endif // CONFIG_TELEGRAM_ENABLE
};
};
};
};
if (buf != nullptr) free(buf);
};
}
затем зарегистрируем её с помощью другой функции eventHandlerRegister(RE_SYSTEM_EVENTS, RE_SYS_COMMAND, &sensorsCommandsEventHandler, nullptr).
bool sensorsEventHandlersRegister()
{
return eventHandlerRegister(RE_MQTT_EVENTS, ESP_EVENT_ANY_ID, &sensorsMqttEventHandler, nullptr)
&& eventHandlerRegister(RE_TIME_EVENTS, ESP_EVENT_ANY_ID, &sensorsTimeEventHandler, nullptr)
&& eventHandlerRegister(RE_GPIO_EVENTS, ESP_EVENT_ANY_ID, &sensorsGpioEventHandler, nullptr)
&& eventHandlerRegister(RE_SYSTEM_EVENTS, RE_SYS_COMMAND, &sensorsCommandsEventHandler, nullptr)
&& eventHandlerRegister(RE_SYSTEM_EVENTS, RE_SYS_OTA, &sensorsOtaEventHandler, nullptr);
}
Её вы можете более подробно рассмотреть в модуле sensors.cpp. На что здесь стоит обратить внимание:
- Любые прикрепленные к событию данные будут автоматически уничтожены после того как цикл событий отправит его всем подписчикам. Поэтому стоит создать локальную копию полученной команды-строки с помощью malloc_string((char*)event_data), дабы чего не вышло. Впрочем, наверное я перестраховываюсь.
- Далее разбиваем полученную строку на токены , разделенные пробелами с помощью системной функции strtok(buf, seps); чтобы получить (возможно) параметры команды – имя сенсора и тип экстремума. А затем уже вызываем соответствующую функцию: sensorsResetExtremumsSensors(mode) для сброса всех сенсоров или sensorsResetExtremumsSensor(&sensorBoiler, SENSOR_BOILER_TOPIC, imode) для конкретного сенсора. Реализации этих дополнительных функций вы можете посмотреть самостоятельно – в них нет ничего сложного, я надеюсь.
А теперь посмотрим как это работает (измените имя датчика на необходимое):
В ответ мы должны получить следующее:
Дополнительные команды для теплицы
Наверное, предыдущие пример вам показался ужасно сложным и непонятным. Не расстраивайтесь! Все не так грустно! Просто посмотрите как реализованы дополнительные команды для “полоумной теплицы“:
// Команды управления устройством
else if ((cmd != nullptr) && (strcasecmp(cmd, CONFIG_COMMAND_FILLING) == 0)) {
rlog_i(logTAG, "Forced filling");
xEventGroupSetBits(_sensorsFlags, BTN_CHANGED | BTN_FILLING);
}
else if ((cmd != nullptr) && (strcasecmp(cmd, CONFIG_COMMAND_WATERING1) == 0)) {
rlog_i(logTAG, "Forced watering 1");
xEventGroupSetBits(_sensorsFlags, BTN_CHANGED | BTN_WATERING1);
}
else if ((cmd != nullptr) && (strcasecmp(cmd, CONFIG_COMMAND_WATERING2) == 0)) {
rlog_i(logTAG, "Forced watering 2");
xEventGroupSetBits(_sensorsFlags, BTN_CHANGED | BTN_WATERING2);
};
Вот видите – всё просто – сравнили с “образцом” – выполнили. Только не стоит из обработчика событий выполнять долгие и нудные действия – иначе можете словить малоприятные и н сложно обнаруживаемые глюки – тормоза. Я здесь просто “взвожу” соответствующие флаги в группе событий.
Команды ОПС
Забегая вперед, перечислю дополнительные команды для модуля “охранно-пожарная сигнализация” доступны дополнительные команды:
- alarm off – отключить режим охраны
- alarm full – включить полный режим охраны
- alarm perimiter – включить режим охраны только периметра (режим “дома”)
- alarm garage – включить режим охраны только внешних помещений (если установлены соответствующие зоны)
- alarm cancel – отменить тревогу (выключить сирену) без отключения режима охраны
- alarm reset – сбросить счетчик тревог без отключения сирены и изменения режима охраны
Переключить режим охраны можно и другими способами (с пульта 433 Мгц или через MQTT, например), а вот две последние команды можно выполнить только через топик команд system/terminal.
А баба Яга против!
А что если мне не нравится название топиков и (или) команд? Что ж, это легко исправить: открываете файл с:\PlatformIO\libs\consts\def_mqtt_commands.h и исправляете название топика команд по своему разумению:
Ну а сами команды вы можете легко поправить, просто кликнув на соответствующий макрос с клавишей [ctrl]:
На этом, пожалуй, на сегодня закончим. Всем добра и до следующих встреч!
💠 Полный архив статей вы найдете здесь
Пожалуйста, оцените статью:
-= Каталог статей (список по разделам) =- -= Архив статей (плитки, все подряд) =-






