From 921a25bd16a1e5a65cb4bef5afde38548103f5c2 Mon Sep 17 00:00:00 2001 From: gunner47 Date: Sat, 5 Oct 2019 22:20:33 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D0=BE=20=D0=BF=D1=80=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D1=83=20MQTT;=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE?= =?UTF-8?q?=D0=B6=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=B2=D1=8B=D0=B2=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0=20=D0=BE=D1=82=D0=BB=D0=B0=D0=B4=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=B2=20telnet;=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B8=20(=D0=B1=D1=83=D0=B4=D0=B8=D0=BB=D1=8C=D0=BD=D0=B8?= =?UTF-8?q?=D0=BA,=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=8F=D1=80=D0=BA=D0=BE=D1=81=D1=82=D1=8C=D1=8E?= =?UTF-8?q?=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=BE=D0=B9,=20=D0=B7=D0=B0?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=B0=D1=80?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=BE=D0=B2=20WiFi=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=20=D0=B2=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- firmware/GyverLamp_v1.4/Constants.h | 20 ++ firmware/GyverLamp_v1.4/FavoritesManager.h | 3 +- firmware/GyverLamp_v1.4/GyverLamp_v1.4.ino | 140 +++++++-- firmware/GyverLamp_v1.4/MqttManager.h | 342 +++++++++++++++++++++ firmware/GyverLamp_v1.4/OtaManager.h | 40 +-- firmware/GyverLamp_v1.4/TelnetManager.ino | 33 ++ firmware/GyverLamp_v1.4/TimerManager.h | 2 +- firmware/GyverLamp_v1.4/Types.h | 2 + firmware/GyverLamp_v1.4/button.ino | 49 ++- firmware/GyverLamp_v1.4/effectTicker.ino | 4 + firmware/GyverLamp_v1.4/parsing.ino | 152 ++++++--- firmware/GyverLamp_v1.4/time.ino | 10 +- 12 files changed, 681 insertions(+), 116 deletions(-) create mode 100644 firmware/GyverLamp_v1.4/MqttManager.h create mode 100644 firmware/GyverLamp_v1.4/TelnetManager.ino diff --git a/firmware/GyverLamp_v1.4/Constants.h b/firmware/GyverLamp_v1.4/Constants.h index 8e8afd0..d3deaf7 100644 --- a/firmware/GyverLamp_v1.4/Constants.h +++ b/firmware/GyverLamp_v1.4/Constants.h @@ -1,5 +1,7 @@ #pragma once +#include + // ============= НАСТРОЙКИ ============= // --- ESP ----------------------------- @@ -34,6 +36,12 @@ const uint8_t AP_STATIC_IP[] = {192, 168, 4, 1}; // статичес #define NTP_ADDRESS ("ntp2.colocall.net") // сервер времени #define NTP_INTERVAL (30UL * 60UL * 1000UL) // интервал синхронизации времени (30 минут) +// --- ВНЕШНЕЕ УПРАВЛЕНИЕ -------------- +#define USE_MQTT (true) // true - используется mqtt клиент, false - нет +#if USE_MQTT +#define MQTT_RECONNECT_TIME (10U) // время в секундах перед подключением к MQTT брокеру в случае потери подключения +#endif + // --- РАССВЕТ ------------------------- #define DAWN_BRIGHT (200U) // максимальная яркость рассвета (0-255) #define DAWN_TIMEOUT (1U) // сколько рассвет светит после времени будильника, минут @@ -86,6 +94,18 @@ const uint8_t AP_STATIC_IP[] = {192, 168, 4, 1}; // статичес //#define MAX_UDP_BUFFER_SIZE (UDP_TX_PACKET_MAX_SIZE + 1) #define MAX_UDP_BUFFER_SIZE (129U) // максимальный размер буффера UDP сервера +#define GENERAL_DEBUG_TELNET (true) // true - отладочные сообщения будут выводиться в telnet вместо Serial порта (для удалённой отладки без подключения usb кабелем) +#define TELNET_PORT (23U) // номер telnet порта + +#if defined(GENERAL_DEBUG) && GENERAL_DEBUG_TELNET +WiFiServer telnetServer(TELNET_PORT); // telnet сервер +WiFiClient telnet; // обработчик событий telnet клиента +bool telnetGreetingShown = false; // признак "показано приветствие в telnet" +#define LOG telnet +#else +#define LOG Serial +#endif + // --- БИБЛИОТЕКИ ---------------------- #define FASTLED_INTERRUPT_RETRY_COUNT (0U) #define FASTLED_ALLOW_INTERRUPTS (0U) diff --git a/firmware/GyverLamp_v1.4/FavoritesManager.h b/firmware/GyverLamp_v1.4/FavoritesManager.h index 5aeffd7..2ad7467 100644 --- a/firmware/GyverLamp_v1.4/FavoritesManager.h +++ b/firmware/GyverLamp_v1.4/FavoritesManager.h @@ -1,6 +1,7 @@ #pragma once #include #include "EepromManager.h" +#include "Constants.h" #define DEFAULT_FAVORITES_INTERVAL (300U) // значение по умолчанию для интервала переключения избпранных эффектов в секундах #define DEFAULT_FAVORITES_DISPERSION (0U) // значение по умолчанию для разброса интервала переключения избпранных эффектов в секундах @@ -99,7 +100,7 @@ class FavoritesManager nextModeAt = getNextTime(); #ifdef GENERAL_DEBUG - Serial.printf_P(PSTR("Переключение на следующий избранный режим: %d\n\n"), (*currentMode)); + LOG.printf_P(PSTR("Переключение на следующий избранный режим: %d\n\n"), (*currentMode)); #endif return true; diff --git a/firmware/GyverLamp_v1.4/GyverLamp_v1.4.ino b/firmware/GyverLamp_v1.4/GyverLamp_v1.4.ino index 4cec549..5bde7ac 100644 --- a/firmware/GyverLamp_v1.4/GyverLamp_v1.4.ino +++ b/firmware/GyverLamp_v1.4/GyverLamp_v1.4.ino @@ -16,8 +16,8 @@ - Добавлено "#define USE_NTP" - позволяет запретить обращаться в интернет - Добавлено "#define ESP_USE_BUTTON - позволяет собирать лампу без физической кнопки, иначе яркость эффектов самопроизвольно растёт до максимальной - Переработаны параметры IP адресов, STA_STATIC_IP теперь пустой по умолчанию - избавляет от путаницы с IP адресами из неправильных диапазонов - - Добавлено "#define GENERAL_DEBUG" - выводит в Serial некоторые отладочные сообщения - - Добавлено "#define WIFIMAN_DEBUG (true)" - выводит в Serial отладочные сообщения библиотеки WiFiManager + - Добавлено "#define GENERAL_DEBUG" - выводит в Serial/Telnet некоторые отладочные сообщения + - Добавлено "#define WIFIMAN_DEBUG (true)" - выводит в Serial/Telnet отладочные сообщения библиотеки WiFiManager - Добавлена таблица с тест кейсами - Форматирование кода, комментарии --- 11.07.2019 @@ -67,6 +67,13 @@ - Убрана очистка параметров WiFi при старте с зажатой кнопкой; регулируется директивой ESP_RESET_ON_STASRT, которая определена как false по умолчанию --- 24.09.2019 - Добавлены изменения из прошивка от Alex Gyver v1.5: бегущая строка с IP адресом лампы по пятикратному клику на кнопку + --- 29.09.2019 + - Добавлена опция вывода отладочных сообщений по пртоколу telnet вместо serial для удалённой отладки + - Исправлена ошибка регулировки яркости кнопкой + --- 05.10.2019 + - Добавлено управление по протоколу MQTT + - Исправлена ошибка выключения будильника кнопкой + - Добавлена задержка в 1 секунду сразу после старта, в течение которой нужно нажать кнопку, чтобы очистить сохранённые параметры WiFi (если ESP_RESET_ON_STASRT == true) */ // Ссылка для менеджера плат: @@ -93,6 +100,9 @@ #ifdef OTA #include "OtaManager.h" #endif +#if USE_MQTT +#include "MqttManager.h" +#endif #include "TimerManager.h" #include "FavoritesManager.h" #include "EepromManager.h" @@ -114,11 +124,28 @@ timerMinim timeTimer(3000); #ifdef ESP_USE_BUTTON GButton touch(BTN_PIN, LOW_PULL, NORM_OPEN); #endif + #ifdef OTA OtaManager otaManager; OtaPhase OtaManager::OtaFlag = OtaPhase::None; #endif +#if USE_MQTT +AsyncMqttClient* mqttClient = NULL; +AsyncMqttClient* MqttManager::mqttClient = NULL; +char* MqttManager::mqttServer = NULL; +char* MqttManager::mqttUser = NULL; +char* MqttManager::mqttPassword = NULL; +char* MqttManager::clientId = NULL; +char* MqttManager::topicInput = NULL; +char* MqttManager::topicOutput = NULL; +bool MqttManager::needToPublish = false; +char MqttManager::mqttBuffer[] = {}; +uint32_t MqttManager::mqttLastConnectingAttempt = 0; +SendCurrentDelegate MqttManager::sendCurrentDelegate = NULL; +// volatile uint32_t wifiLastConnectingAttempt = 0; +#endif + // --- ИНИЦИАЛИЗАЦИЯ ПЕРЕМЕННЫХ ------- uint16_t localPort = ESP_UDP_PORT; char packetBuffer[MAX_UDP_BUFFER_SIZE]; // buffer to hold incoming packet @@ -160,30 +187,47 @@ void setup() Serial.begin(115200); Serial.println(); - ESP.wdtDisable(); - //ESP.wdtEnable(WDTO_8S); - #if defined(ESP_USE_BUTTON) && ESP_RESET_ON_STASRT - touch.setStepTimeout(100); - touch.setClickTimeout(500); - buttonTick(); - if (touch.state()) // сброс сохранённых SSID и пароля при старте с зажатой кнопкой, если разрешено + // TELNET + #if defined(GENERAL_DEBUG) && GENERAL_DEBUG_TELNET + telnetServer.begin(); + for (uint8_t i = 0; i < 100; i++) // пауза 10 секунд в отладочном режиме, чтобы успеть подключиться по протоколу telnet до вывода первых сообщений { - wifiManager.resetSettings(); - - #ifdef GENERAL_DEBUG - Serial.println(F("Настройки WiFiManager сброшены")); - #endif + handleTelnetClient(); + delay(100); } #endif - // ЛЕНТА + ESP.wdtDisable(); + //ESP.wdtEnable(WDTO_8S); + + + // КНОПКА + #if defined(ESP_USE_BUTTON) + touch.setStepTimeout(100); + touch.setClickTimeout(500); + #if ESP_RESET_ON_STASRT + delay(1000); // ожидание инициализации модуля кнопки ttp223 (по спецификации 250мс) + if (digitalRead(BTN_PIN)) + { + wifiManager.resetSettings(); // сброс сохранённых SSID и пароля при старте с зажатой кнопкой, если разрешено + + #ifdef GENERAL_DEBUG + LOG.println(F("Настройки WiFiManager сброшены")); + #endif + } + #endif + #endif + + + // ЛЕНТА/МАТРИЦА FastLED.addLeds(leds, NUM_LEDS)/*.setCorrection(TypicalLEDStrip)*/; FastLED.setBrightness(BRIGHTNESS); if (CURRENT_LIMIT > 0) FastLED.setMaxPowerInVoltsAndMilliamps(5, CURRENT_LIMIT); FastLED.clear(); FastLED.show(); + // WI-FI wifiManager.setDebugOutput(WIFIMAN_DEBUG); // вывод отладочных сообщений //wifiManager.setMinimumSignalQuality(); // установка минимально приемлемого уровня сигнала WiFi сетей (8% по умолчанию) @@ -197,23 +241,23 @@ void setup() WiFi.softAP(AP_NAME, AP_PASS); - Serial.println(F("Режим WiFi точки доступа")); - Serial.print(F("IP адрес: ")); - Serial.println(WiFi.softAPIP()); + LOG.println(F("Режим WiFi точки доступа")); + LOG.print(F("IP адрес: ")); + LOG.println(WiFi.softAPIP()); wifiServer.begin(); } else // режим WiFi клиента (подключаемся к роутеру, если есть сохранённые SSID и пароль, иначе создаём WiFi точку доступа и запрашиваем их) { - Serial.println(F("Режим WiFi клиента")); + LOG.println(F("Режим WiFi клиента")); if (WiFi.SSID()) { - Serial.print(F("Подключение WiFi сети: ")); - Serial.println(WiFi.SSID()); + LOG.print(F("Подключение WiFi сети: ")); + LOG.println(WiFi.SSID()); } else { - Serial.println(F("WiFi сеть не определена, запуск WiFi точки доступа для настройки параметров подключения к WiFi сети...")); + LOG.println(F("WiFi сеть не определена, запуск WiFi точки доступа для настройки параметров подключения к WiFi сети...")); } if (STA_STATIC_IP) @@ -230,7 +274,7 @@ void setup() if (WiFi.status() != WL_CONNECTED) { - Serial.println(F("Время ожидания ввода SSID и пароля от WiFi сети или подключения к WiFi сети превышено\nПерезагрузка модуля")); + LOG.println(F("Время ожидания ввода SSID и пароля от WiFi сети или подключения к WiFi сети превышено\nПерезагрузка модуля")); #if defined(ESP8266) ESP.reset(); @@ -239,22 +283,35 @@ void setup() #endif } - Serial.print(F("IP адрес: ")); - Serial.println(WiFi.localIP()); + LOG.print(F("IP адрес: ")); + LOG.println(WiFi.localIP()); } - Serial.printf_P(PSTR("Порт UDP сервера: %u\n"), localPort); + LOG.printf_P(PSTR("Порт UDP сервера: %u\n"), localPort); Udp.begin(localPort); + + // EEPROM EepromManager::InitEepromSettings( // инициализация EEPROM; запись начального состояния настроек, если их там ещё нет; инициализация настроек лампы значениями из EEPROM modes, alarms, &ONflag, &dawnMode, ¤tMode, &(FavoritesManager::ReadFavoritesFromEeprom), &(FavoritesManager::SaveFavoritesToEeprom)); + + // NTP #ifdef USE_NTP timeClient.begin(); #endif + + // MQTT + #if (USE_MQTT && ESP_MODE == 1) + mqttClient = new AsyncMqttClient(); + MqttManager::setupMqtt(mqttClient, &sendCurrent); // создание экземпляров объектов для работы с MQTT, их инициализация и подключение к MQTT брокеру + #endif + + + // ОСТАЛЬНОЕ memset(matrixValue, 0, sizeof(matrixValue)); randomSeed(micros()); changePower(); @@ -266,19 +323,25 @@ void loop() { parseUDP(); effectsTick(); + EepromManager::HandleEepromTick(&settChanged, &eepromTimeout, &ONflag, - ¤tMode, modes, &(FavoritesManager::SaveFavoritesToEeprom)); + ¤tMode, modes, &(FavoritesManager::SaveFavoritesToEeprom)); + #ifdef USE_NTP timeTick(); #endif + #ifdef ESP_USE_BUTTON buttonTick(); #endif + #ifdef OTA otaManager.HandleOtaUpdate(); // ожидание и обработка команды на обновление прошивки по воздуху #endif + TimerManager::HandleTimer(&ONflag, &settChanged, // обработка событий таймера отключения лампы &eepromTimeout, &changePower); + if (FavoritesManager::HandleFavorites( // обработка режима избранных эффектов &ONflag, ¤tMode, @@ -292,6 +355,29 @@ void loop() FastLED.clear(); delay(1); } + + #if USE_MQTT + if (ESP_MODE == 1 && mqttClient && WiFi.isConnected() && !mqttClient->connected()) + { + MqttManager::mqttConnect(); // библиотека не умеет восстанавливать соединение в случае потери подключения к MQTT брокеру, нужно управлять этим явно + MqttManager::needToPublish = true; + } + + if (MqttManager::needToPublish) + { + if (strlen(inputBuffer) > 0) // проверка входящего MQTT сообщения; если оно не пустое - выполнение команды из него и формирование MQTT ответа + { + processInputBuffer(inputBuffer, MqttManager::mqttBuffer, true); + } + + MqttManager::publishState(); + } + #endif + + #if defined(GENERAL_DEBUG) && GENERAL_DEBUG_TELNET + handleTelnetClient(); + #endif + ESP.wdtFeed(); // пнуть собаку yield(); // обработать все "служебные" задачи: wdt, WiFi подключение и т.д. (?) } diff --git a/firmware/GyverLamp_v1.4/MqttManager.h b/firmware/GyverLamp_v1.4/MqttManager.h new file mode 100644 index 0000000..fc21c95 --- /dev/null +++ b/firmware/GyverLamp_v1.4/MqttManager.h @@ -0,0 +1,342 @@ +#ifdef USE_MQTT +/* + * Библиотека асинхронных MQTT запросов https://github.com/marvinroger/async-mqtt-client + * Не умеет автоматически восстанавливать разорванное соединение с MQTT брокером, поэтому требует периодической проверки подключения + * Зависит от библиотек: + * ESPAsyncTCP https://github.com/me-no-dev/ESPAsyncTCP + * AsyncTCP https://github.com/me-no-dev/AsyncTCP + * Лампа подписана на топик: LedLamp/LedLamp_xxxxxxxx/cmnd, где xxxxxxxx - ESP.getChipID(); payload - строка, содержащая те же команды, что отправляются приложением (регистр важен): + * P_ON - включить матрицу + * P_OFF - выключить матрицу + * EFF0 - сделать активным эффект №0 (нумерация с нуля) + * BRI44 - установить яркость 44; диапазон [1..255] + * SPD3 - установить скорость 3; диапазон [1..255] + * SCA1 - установить масштаб 1; диапазон [1..100] + * ALM_SET1 ON - завести будильник 1 (понедельник); ON - вкл, OFF - выкл + * ALM_SET1 390 - установить время будильника 1 (понедельник) на 06:30 (количество минут от начала суток) + * DAWN1 - установить "рассвет" за 5 минут до будильника (1 = 5 минут - номер опции в выпадающем списке в приложении, нумерация с единицы) + * TMR_SET 1 3 300 - установить таймер; описание параметров - см. команду TMR ниже + * FAV_SET 1 60 120 0 0 1 0 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0 0 - установить режим "избранное", параметры - см. команду FAV ниже + * Лампа отправляет своё состояние сразу после включения и после каждого изменения в топик LedLamp/LedLamp_00316765/state; payload: + * "CURR 7 14 4 50 1 1 1 0 21:25:50", где: + * CURR - идентификатор команды, CURR - текущее состояние лампы + * 7 - номер текущего эффекта + * 14 - яркость + * 4 - скорость + * 50 - масштаб + * 1 - признак "матрица включена" + * 1 - режим ESP_MODE + * 1 - признак "работает таймер" + * 0 - признак USE_NTP (пытаться синхронизировать время по серверам времени в интернете) + * 21:25:50 - текущее время (если не синхронизировано, показывает время от старта модуля) + * "ALMS 1 0 0 0 0 0 0 0 390 0 0 0 0 0 0 1" + * ALMS - идентификатор команды, ALMS - настройки будильников + * первые 7 цифр - признак "будильник заведён" по дням недели, начиная с понедельника + * последующие 7 цифр - время в минутах от начала суток, на которое заведён будильник (по дням недели); 390 = 06:30 + * последняя цифра - опция "рассвет за ... минут", цифра указывает на номер значения в выпадающем списке: 1 - 5 минут, 2 - 10 минут... (см. в приложении) + * "TMR 1 3 300" + * TMR - идентификатор команды, TMR - таймер + * 1 - признак "таймер взведён" + * 3 - опция "выключить лампу через ...", цифра указывает на номер значения в выпадающем списке: 1 - не выключать, 2 - 1 минута... (см. в приложении) + * 300 - количество секунд, через которое выключится лампа (0 - не выключать) + * "FAV 1 60 120 0 0 1 0 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0 0" + * FAV - идентификатор команды, FAV - избранное + * 1 - режим "избранное" включен + * 60 - интервал смены эффектов в секундах + * 120 - случайный разброс смены эффектов (применяется дополнительно к интервалу) в секундах + * 0 - признак "запомнить состояние" вкл/выкл режима "избранное" в энергонезависимую память + * оставшиеся цифры - признак (0/1) "эффект №... добавлен в избранные", где номер цифры соотвтетсвует номеру эффекта в списке (см. приложение) +*/ + +#include +#include "pgmspace.h" +#include "Constants.h" +#include "Types.h" + +static const char TopicBase[] PROGMEM = "LedLamp"; // базовая часть топиков +static const char TopicCmnd[] PROGMEM = "cmnd"; // часть командных топиков (входящие команды лампе) +static const char TopicState[] PROGMEM = "state"; // часть топиков состояния (ответ от лампы) + +static const char MqttServer[] PROGMEM = "192.168.0.100"; // строка с IP адресом MQTT брокера +static const uint16_t MqttPort = 1883U; // порт MQTT брокера +static const char MqttUser[] PROGMEM = ""; // пользователь MQTT брокера +static const char MqttPassword[] PROGMEM = ""; // пароль пользователя MQTT брокера +static const char MqttClientIdPrefix[] PROGMEM = "LedLamp_"; // id клиента MQTT брокера (к нему будет добавлен ESP.getChipId) + + +class MqttManager +{ + public: + static uint32_t mqttLastConnectingAttempt; + static void setupMqtt(AsyncMqttClient* mqttClient, SendCurrentDelegate sendCurrentDelegate); + static void mqttConnect(); + static void onMqttConnect(bool sessionPresent); + static void onMqttDisconnect(AsyncMqttClientDisconnectReason reason); + static void onMqttMessage(char *topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total); + static bool publish(const char *topic, const char *value); + static void publishState(); + static bool needToPublish; + static char mqttBuffer[MAX_UDP_BUFFER_SIZE]; + private: + static char* mqttServer; + static char* mqttUser; + static char* mqttPassword; + static char* topicInput; // TopicBase + '/' + MqttClientIdPrefix + ESP.getChipId + '/' + TopicCmnd + static char* topicOutput; // TopicBase + '/' + MqttClientIdPrefix + ESP.getChipId + '/' + TopicState + static char* clientId; + static AsyncMqttClient* mqttClient; + static const uint8_t qos = 0U; // MQTT quality of service + static const uint32_t connectionTimeout = MQTT_RECONNECT_TIME * 1000U; // период времени для проверки (пере)подключения к MQTT брокеру, 10 секунд + static char* byteToHex(char *out, uint8_t value); + static bool allocStr(char **str, const char *src); + static bool allocStr_P(char **str, PGM_P src); + static SendCurrentDelegate sendCurrentDelegate; +}; + + +void MqttManager::setupMqtt(AsyncMqttClient* mqttClient, SendCurrentDelegate sendCurrentDelegate) +{ + allocStr_P(&MqttManager::mqttServer, MqttServer); + allocStr_P(&MqttManager::mqttUser, MqttUser); + allocStr_P(&MqttManager::mqttPassword, MqttPassword); + + MqttManager::mqttClient = mqttClient; + MqttManager::sendCurrentDelegate = sendCurrentDelegate; + MqttManager::mqttClient->setServer(MqttManager::mqttServer, MqttPort); + + char clientIdBuf[sizeof(MqttClientIdPrefix) + 8]; + strcpy_P(clientIdBuf, MqttClientIdPrefix); + uint32_t chipId = ESP.getChipId(); + for (uint8_t i = 0; i < 4; ++i) + { + byteToHex(&clientIdBuf[i * 2 + sizeof(MqttClientIdPrefix) - 1], chipId >> ((3 - i) * 8)); + } + allocStr(&clientId, clientIdBuf); + MqttManager::mqttClient->setClientId(clientId); + + if (MqttManager::mqttUser != NULL) + { + MqttManager::mqttClient->setCredentials(MqttManager::mqttUser, MqttManager::mqttPassword); + } + + uint8_t topicLength = sizeof(TopicBase) + 1 + strlen(clientId) + 1 + sizeof(TopicCmnd) + 1; + topicInput = (char*)malloc(topicLength); + sprintf_P(topicInput, PSTR("%s/%s/%s"), TopicBase, clientId, TopicCmnd); // topicInput = TopicBase + '/' + MqttClientIdPrefix + ESP.getChipId + '/' + TopicCmnd + + topicLength = sizeof(TopicBase) + 1 + strlen(clientId) + 1 + sizeof(TopicState) + 1; + topicOutput = (char*)malloc(topicLength); + sprintf_P(topicOutput, PSTR("%s/%s/%s"), TopicBase, clientId, TopicState); // topicOutput = TopicBase + '/' + MqttClientIdPrefix + ESP.getChipId + '/' + TopicState + + #ifdef GENERAL_DEBUG + LOG.printf_P(PSTR("MQTT топик для входящих команд: %s\n"), topicInput); + LOG.printf_P(PSTR("MQTT топик для исходящих ответов лампы: %s\n"), topicOutput); + #endif + + mqttClient->onConnect(onMqttConnect); + mqttClient->onDisconnect(onMqttDisconnect); + mqttClient->onMessage(onMqttMessage); +} + +void MqttManager::mqttConnect() +{ + if ((!mqttLastConnectingAttempt) || (millis() - mqttLastConnectingAttempt >= connectionTimeout)) + { + #ifdef GENERAL_DEBUG + LOG.print(F("Подключение к MQTT брокеру \"")); + LOG.print(MqttManager::mqttServer); + LOG.print(':'); + LOG.print(MqttPort); + LOG.println(F("\"...")); + #endif + mqttClient->disconnect(); + mqttClient->connect(); + mqttLastConnectingAttempt = millis(); + } +} + +bool MqttManager::publish(const char *topic, const char *value) +{ + if (mqttClient->connected()) + { + #ifdef GENERAL_DEBUG + LOG.print(F("Отправлено MQTT: топик \"")); + LOG.print(topic); + LOG.print(F("\", значение \"")); + LOG.print(value); + LOG.println('"'); + LOG.println(); + #endif + + return mqttClient->publish(topic, qos, true, value, 0) != 0; + } + + return false; +} + +void MqttManager::onMqttConnect(bool sessionPresent) +{ + #ifdef GENERAL_DEBUG + LOG.println(F("Подключено к MQTT брокеру")); + #endif + mqttLastConnectingAttempt = 0; + + mqttClient->subscribe(topicInput, 1); + publishState(); +} + +void MqttManager::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) +{ + #ifdef GENERAL_DEBUG + LOG.println(F("Отключено от MQTT брокера")); + #endif +} + +void MqttManager::onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) +{ + if (payload != NULL) // сохраняем пришедшее MQTT сообщение для дальнейшей обработки + { + strncpy(mqttBuffer, payload, len); + mqttBuffer[len] = '\0'; + } + + #ifdef GENERAL_DEBUG + LOG.print(F("Получено MQTT:")); + LOG.print(F(" топик \"")); + LOG.print(topic); + LOG.print("\""); + /* + LOG.print(F(" qos: ")); + LOG.println(properties.qos); + LOG.print(F(" dup: ")); + LOG.println(properties.dup); + LOG.print(F(" retain: ")); + LOG.println(properties.retain); + LOG.print(F(" len: ")); + LOG.println(len); + LOG.print(F(" index: ")); + LOG.println(index); + LOG.print(F(" total: ")); + LOG.println(total); + */ + LOG.print(F(", значение \"")); + LOG.print(mqttBuffer); + LOG.println("\""); + LOG.println(); + #endif +} + +void MqttManager::publishState() +{ + if (mqttBuffer == NULL || strlen(mqttBuffer) <= 0) + { + sendCurrentDelegate(mqttBuffer); // если буфер MQTT ответа не задан, но метод MQTT публикации вызван, закполняем его текущим состоянием лампы + } + + if (mqttBuffer != NULL && strlen(mqttBuffer) > 0) + { + publish(topicOutput, mqttBuffer); // публикация буфера MQTT ответа + mqttBuffer[0] = '\0'; // очистка буфера + needToPublish = false; // сброс флага для предотвращения повторной публикации + } +} + +char* MqttManager::byteToHex(char *out, uint8_t value) +{ + uint8_t b; + + b = value >> 4; + if (b < 10) + { + out[0] = '0' + b; + } + else + { + out[0] = 'A' + (b - 10); + } + b = value & 0x0F; + if (b < 10) + { + out[1] = '0' + b; + } + else + { + out[1] = 'A' + (b - 10); + } + out[2] = '\0'; + + return out; +} + +bool MqttManager::allocStr(char **str, const char *src) +{ + if (src && *src) + { + if (*str) + { + void *ptr = realloc(*str, strlen(src) + 1); + + if (!ptr) + { + return false; + } + *str = (char*)ptr; + } + else + { + *str = (char*)malloc(strlen(src) + 1); + if (!*str) + { + return false; + } + } + strcpy(*str, src); + } + else + { + if (*str) + { + free(*str); + *str = NULL; + } + } + + return true; +} + +bool MqttManager::allocStr_P(char **str, PGM_P src) +{ + if (src && pgm_read_byte(src)) + { + if (*str) + { + void *ptr = realloc(*str, strlen_P(src) + 1); + + if (!ptr) + { + return false; + } + *str = (char*)ptr; + } + else + { + *str = (char*)malloc(strlen_P(src) + 1); + if (!*str) + { + return false; + } + } + strcpy_P(*str, src); + } + else + { + if (*str) + { + free(*str); + *str = NULL; + } + } + + return true; +} + +#endif diff --git a/firmware/GyverLamp_v1.4/OtaManager.h b/firmware/GyverLamp_v1.4/OtaManager.h index a1e52d8..e14895e 100644 --- a/firmware/GyverLamp_v1.4/OtaManager.h +++ b/firmware/GyverLamp_v1.4/OtaManager.h @@ -38,7 +38,7 @@ class OtaManager if (ESP_MODE != 1) { #ifdef GENERAL_DEBUG - Serial.print(F("Запрос обновления по воздуху поддерживается только в режиме ESP_MODE = 1\n")); + LOG.print(F("Запрос обновления по воздуху поддерживается только в режиме ESP_MODE = 1\n")); #endif return false; @@ -50,7 +50,7 @@ class OtaManager momentOfFirstConfirmation = millis(); #ifdef GENERAL_DEBUG - Serial.print(F("Получено первое подтверждение обновления по воздуху\nОжидание второго подтверждения\n")); + LOG.print(F("Получено первое подтверждение обновления по воздуху\nОжидание второго подтверждения\n")); #endif return false; @@ -61,7 +61,7 @@ class OtaManager OtaFlag = OtaPhase::GotSecondConfirm; #ifdef GENERAL_DEBUG - Serial.print(F("Получено второе подтверждение обновления по воздуху\nСтарт режима обновления\n")); + LOG.print(F("Получено второе подтверждение обновления по воздуху\nСтарт режима обновления\n")); #endif startOtaUpdate(); @@ -80,7 +80,7 @@ class OtaManager momentOfFirstConfirmation = 0; #ifdef GENERAL_DEBUG - Serial.print(F("Таймаут ожидания второго подтверждения превышен\nСброс флага в исходное состояние\n")); + LOG.print(F("Таймаут ожидания второго подтверждения превышен\nСброс флага в исходное состояние\n")); #endif return; @@ -93,7 +93,7 @@ class OtaManager momentOfOtaStart = 0; #ifdef GENERAL_DEBUG - Serial.print(F("Таймаут ожидания прошивки по воздуху превышен\nСброс флага в исходное состояние\nПерезагрузка\n")); + LOG.print(F("Таймаут ожидания прошивки по воздуху превышен\nСброс флага в исходное состояние\nПерезагрузка\n")); delay(500); #endif @@ -139,7 +139,7 @@ class OtaManager // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() #ifdef GENERAL_DEBUG - Serial.printf_P(PSTR("Start updating %s\n"), type.c_str()); + LOG.printf_P(PSTR("Start updating %s\n"), type.c_str()); #endif }); @@ -148,7 +148,7 @@ class OtaManager OtaFlag = OtaPhase::Done; #ifdef GENERAL_DEBUG - Serial.print(F("Обновление по воздуху выполнено\nПерезапуск")); + LOG.print(F("Обновление по воздуху выполнено\nПерезапуск")); delay(500); #endif }); @@ -156,7 +156,7 @@ class OtaManager ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { #ifdef GENERAL_DEBUG - Serial.printf_P(PSTR("Ход выполнения: %u%%\r"), (progress / (total / 100))); + LOG.printf_P(PSTR("Ход выполнения: %u%%\r"), (progress / (total / 100))); #endif }); @@ -165,42 +165,42 @@ class OtaManager OtaFlag = OtaPhase::None; #ifdef GENERAL_DEBUG - Serial.printf_P(PSTR("Обновление по воздуху завершилось ошибкой [%u]: "), error); + LOG.printf_P(PSTR("Обновление по воздуху завершилось ошибкой [%u]: "), error); #endif if (error == OTA_AUTH_ERROR) { #ifdef GENERAL_DEBUG - Serial.println(F("Auth Failed")); + LOG.println(F("Auth Failed")); #endif } else if (error == OTA_BEGIN_ERROR) { #ifdef GENERAL_DEBUG - Serial.println(F("Begin Failed")); + LOG.println(F("Begin Failed")); #endif } else if (error == OTA_CONNECT_ERROR) { #ifdef GENERAL_DEBUG - Serial.println(F("Connect Failed")); + LOG.println(F("Connect Failed")); #endif } else if (error == OTA_RECEIVE_ERROR) { #ifdef GENERAL_DEBUG - Serial.println(F("Receive Failed")); + LOG.println(F("Receive Failed")); #endif } else if (error == OTA_END_ERROR) { #ifdef GENERAL_DEBUG - Serial.println(F("End Failed")); + LOG.println(F("End Failed")); #endif } #ifdef GENERAL_DEBUG - Serial.print(F("Сброс флага в исходное состояние\nПереход в режим ожидания запроса прошивки по воздуху\n")); + LOG.print(F("Сброс флага в исходное состояние\nПереход в режим ожидания запроса прошивки по воздуху\n")); #endif }); @@ -211,11 +211,11 @@ class OtaManager momentOfOtaStart = 0; #ifdef GENERAL_DEBUG - Serial.printf_P(PSTR("Для обновления в Arduino IDE выберите пункт меню Инструменты - Порт - '%s at "), espHostName); - Serial.print(WiFi.localIP()); - Serial.println(F("'")); - Serial.printf_P(PSTR("Затем нажмите кнопку 'Загрузка' в течение %u секунд и по запросу введите пароль '%s'\n"), ESP_CONF_TIMEOUT, AP_PASS); - Serial.println(F("Устройство с Arduino IDE должно быть в одной локальной сети с модулем ESP!")); + LOG.printf_P(PSTR("Для обновления в Arduino IDE выберите пункт меню Инструменты - Порт - '%s at "), espHostName); + LOG.print(WiFi.localIP()); + LOG.println(F("'")); + LOG.printf_P(PSTR("Затем нажмите кнопку 'Загрузка' в течение %u секунд и по запросу введите пароль '%s'\n"), ESP_CONF_TIMEOUT, AP_PASS); + LOG.println(F("Устройство с Arduino IDE должно быть в одной локальной сети с модулем ESP!")); #endif } }; diff --git a/firmware/GyverLamp_v1.4/TelnetManager.ino b/firmware/GyverLamp_v1.4/TelnetManager.ino new file mode 100644 index 0000000..a7dfc1b --- /dev/null +++ b/firmware/GyverLamp_v1.4/TelnetManager.ino @@ -0,0 +1,33 @@ +#if defined(GENERAL_DEBUG) && GENERAL_DEBUG_TELNET + +void handleTelnetClient() +{ + if (telnetServer.hasClient()) + { + if (!telnet || !telnet.connected()) + { + if (telnet) + { + telnet.stop(); // клиент отключился + telnetGreetingShown = false; + } + telnet = telnetServer.available(); // готов к подключению нового клиента + } + else + { + telnetServer.available().stop(); // один клиент уже подключен, блокируем подключение нового + telnetGreetingShown = false; + } + } + + if (telnet && telnet.connected() && telnet.available()) + { + if (!telnetGreetingShown) + { + telnet.println("Подключение к устройтву по протоколу telnet установлено\n-------"); + telnetGreetingShown = true; + } + } +} + +#endif diff --git a/firmware/GyverLamp_v1.4/TimerManager.h b/firmware/GyverLamp_v1.4/TimerManager.h index ee021fd..24212c9 100644 --- a/firmware/GyverLamp_v1.4/TimerManager.h +++ b/firmware/GyverLamp_v1.4/TimerManager.h @@ -20,7 +20,7 @@ class TimerManager millis() >= TimerManager::TimeToFire) { #ifdef GENERAL_DEBUG - Serial.print(F("Выключение по таймеру\n\n")); + LOG.print(F("Выключение по таймеру\n\n")); #endif TimerManager::TimerRunning = false; diff --git a/firmware/GyverLamp_v1.4/Types.h b/firmware/GyverLamp_v1.4/Types.h index 600961c..af010d1 100644 --- a/firmware/GyverLamp_v1.4/Types.h +++ b/firmware/GyverLamp_v1.4/Types.h @@ -13,3 +13,5 @@ struct ModeType uint8_t Speed = 30; uint8_t Scale = 40; }; + +typedef void (*SendCurrentDelegate)(char *outputBuffer); diff --git a/firmware/GyverLamp_v1.4/button.ino b/firmware/GyverLamp_v1.4/button.ino index bb30e1e..576f77e 100644 --- a/firmware/GyverLamp_v1.4/button.ino +++ b/firmware/GyverLamp_v1.4/button.ino @@ -1,6 +1,8 @@ #ifdef ESP_USE_BUTTON bool brightDirection; +static bool startButtonHolding = false; // флаг: кнопка удерживается для изменения яркости лампы + void buttonTick() { touch.tick(); @@ -23,6 +25,10 @@ void buttonTick() settChanged = true; eepromTimeout = millis(); loadingFlag = true; + + #if (USE_MQTT && ESP_MODE == 1) + MqttManager::needToPublish = true; + #endif } if (ONflag && clickCount == 2) @@ -34,6 +40,10 @@ void buttonTick() eepromTimeout = millis(); FastLED.clear(); delay(1); + + #if (USE_MQTT && ESP_MODE == 1) + MqttManager::needToPublish = true; + #endif } if (ONflag && clickCount == 3) @@ -45,9 +55,13 @@ void buttonTick() eepromTimeout = millis(); FastLED.clear(); delay(1); + + #if (USE_MQTT && ESP_MODE == 1) + MqttManager::needToPublish = true; + #endif } - if (ONflag && clickCount == 4) + if (clickCount == 4) { #ifdef OTA if (otaManager.RequestOtaUpdate()) @@ -67,34 +81,39 @@ void buttonTick() while(!fillString(WiFi.localIP().toString().c_str())) delay(1); loadingFlag = true; } - } + } if (ONflag && touch.isHolded()) { brightDirection = !brightDirection; + startButtonHolding = true; } if (ONflag && touch.isStep()) { - if (brightDirection) - { - if (modes[currentMode].Brightness < 10) modes[currentMode].Brightness += 1; - else if (modes[currentMode].Brightness < 250) modes[currentMode].Brightness += 5; - else modes[currentMode].Brightness = 255; - } - else - { - if (modes[currentMode].Brightness > 15) modes[currentMode].Brightness -= 5; - else if (modes[currentMode].Brightness > 1) modes[currentMode].Brightness -= 1; - else modes[currentMode].Brightness = 0; - } + uint8_t delta = modes[currentMode].Brightness < 10 // определение шага изменения яркости: при яркости [1..10] шаг = 1, при [11..16] шаг = 3, при [17..255] шаг = 15 + ? 1 + : 5; + modes[currentMode].Brightness = + constrain(brightDirection + ? modes[currentMode].Brightness + delta + : modes[currentMode].Brightness - delta, + 1, 255); FastLED.setBrightness(modes[currentMode].Brightness); settChanged = true; eepromTimeout = millis(); #ifdef GENERAL_DEBUG - Serial.printf_P(PSTR("New brightness value: %d\n"), modes[currentMode].Brightness); + LOG.printf_P(PSTR("New brightness value: %d\n"), modes[currentMode].Brightness); #endif } + + #if (USE_MQTT && ESP_MODE == 1) + if (ONflag && !touch.isHold() && startButtonHolding) // кнопка отпущена после удерживания, нужно отправить MQTT сообщение об изменении яркости лампы + { + MqttManager::needToPublish = true; + startButtonHolding = false; + } + #endif } #endif diff --git a/firmware/GyverLamp_v1.4/effectTicker.ino b/firmware/GyverLamp_v1.4/effectTicker.ino index 8114046..fdbd990 100644 --- a/firmware/GyverLamp_v1.4/effectTicker.ino +++ b/firmware/GyverLamp_v1.4/effectTicker.ino @@ -78,4 +78,8 @@ void changePower() { FavoritesManager::TurnFavoritesOff(); } + + #if (USE_MQTT && ESP_MODE == 1) + MqttManager::needToPublish = true; + #endif } diff --git a/firmware/GyverLamp_v1.4/parsing.ino b/firmware/GyverLamp_v1.4/parsing.ino index b5db4de..86aae44 100644 --- a/firmware/GyverLamp_v1.4/parsing.ino +++ b/firmware/GyverLamp_v1.4/parsing.ino @@ -1,7 +1,6 @@ void parseUDP() { int32_t packetSize = Udp.parsePacket(); - char buff[MAX_UDP_BUFFER_SIZE], *endToken = NULL; if (packetSize) { @@ -10,10 +9,39 @@ void parseUDP() strcpy(inputBuffer, packetBuffer); #ifdef GENERAL_DEBUG - Serial.print(F("Inbound UDP packet: ")); - Serial.println(inputBuffer); + LOG.print(F("Inbound UDP packet: ")); + LOG.println(inputBuffer); #endif + if (Udp.remoteIP() == WiFi.localIP()) // не реагировать на свои же пакеты + { + return; + } + + char reply[MAX_UDP_BUFFER_SIZE]; + processInputBuffer(inputBuffer, reply, true); + + #if (USE_MQTT && ESP_MODE == 1) // отправка ответа выполнения команд по MQTT, если разрешено + strcpy(MqttManager::mqttBuffer, reply); // разрешение определяется при выполнении каждой команды отдельно, команды GET, DEB, DISCOVER и OTA, пришедшие по UDP, игнорируются (приходят раз в 2 секунды от приложения) + #endif + + Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); + Udp.write(reply); + Udp.endPacket(); + + #ifdef GENERAL_DEBUG + LOG.print(F("Outbound UDP packet: ")); + LOG.println(reply); + LOG.println(); + #endif + } +} + + +void processInputBuffer(char *inputBuffer, char *outputBuffer, bool generateOutput) +{ + char buff[MAX_UDP_BUFFER_SIZE], *endToken = NULL; + if (!strncmp_P(inputBuffer, PSTR("DEB"), 3)) { #ifdef USE_NTP @@ -25,7 +53,7 @@ void parseUDP() else if (!strncmp_P(inputBuffer, PSTR("GET"), 3)) { - sendCurrent(); + sendCurrent(inputBuffer); } else if (!strncmp_P(inputBuffer, PSTR("EFF"), 3)) @@ -36,8 +64,12 @@ void parseUDP() loadingFlag = true; FastLED.clear(); delay(1); - sendCurrent(); + sendCurrent(inputBuffer); FastLED.setBrightness(modes[currentMode].Brightness); + + #if (USE_MQTT && ESP_MODE == 1) + MqttManager::needToPublish = true; + #endif } else if (!strncmp_P(inputBuffer, PSTR("BRI"), 3)) @@ -48,7 +80,11 @@ void parseUDP() loadingFlag = true; settChanged = true; eepromTimeout = millis(); - sendCurrent(); + sendCurrent(inputBuffer); + + #if (USE_MQTT && ESP_MODE == 1) + MqttManager::needToPublish = true; + #endif } else if (!strncmp_P(inputBuffer, PSTR("SPD"), 3)) @@ -58,7 +94,11 @@ void parseUDP() loadingFlag = true; settChanged = true; eepromTimeout = millis(); - sendCurrent(); + sendCurrent(inputBuffer); + + #if (USE_MQTT && ESP_MODE == 1) + MqttManager::needToPublish = true; + #endif } else if (!strncmp_P(inputBuffer, PSTR("SCA"), 3)) @@ -68,7 +108,11 @@ void parseUDP() loadingFlag = true; settChanged = true; eepromTimeout = millis(); - sendCurrent(); + sendCurrent(inputBuffer); + + #if (USE_MQTT && ESP_MODE == 1) + MqttManager::needToPublish = true; + #endif } else if (!strncmp_P(inputBuffer, PSTR("P_ON"), 4)) @@ -78,7 +122,11 @@ void parseUDP() settChanged = true; eepromTimeout = millis(); changePower(); - sendCurrent(); + sendCurrent(inputBuffer); + + #if (USE_MQTT && ESP_MODE == 1) + MqttManager::needToPublish = true; + #endif } else if (!strncmp_P(inputBuffer, PSTR("P_OFF"), 5)) @@ -87,7 +135,11 @@ void parseUDP() settChanged = true; eepromTimeout = millis(); changePower(); - sendCurrent(); + sendCurrent(inputBuffer); + + #if (USE_MQTT && ESP_MODE == 1) + MqttManager::needToPublish = true; + #endif } else if (!strncmp_P(inputBuffer, PSTR("ALM_SET"), 7)) @@ -97,12 +149,12 @@ void parseUDP() if (strstr_P(inputBuffer, PSTR("ON")) - inputBuffer == 9) { alarms[alarmNum].State = true; - sendAlarms(); + sendAlarms(inputBuffer); } else if (strstr_P(inputBuffer, PSTR("OFF")) - inputBuffer == 9) { alarms[alarmNum].State = false; - sendAlarms(); + sendAlarms(inputBuffer); } else { @@ -110,14 +162,19 @@ void parseUDP() alarms[alarmNum].Time = atoi(buff); uint8_t hour = floor(alarms[alarmNum].Time / 60); uint8_t minute = alarms[alarmNum].Time - hour * 60; - sendAlarms(); + sendAlarms(inputBuffer); } EepromManager::SaveAlarmsSettings(&alarmNum, alarms); + + #if (USE_MQTT && ESP_MODE == 1) + strcpy(MqttManager::mqttBuffer, inputBuffer); + MqttManager::needToPublish = true; + #endif } else if (!strncmp_P(inputBuffer, PSTR("ALM_GET"), 7)) { - sendAlarms(); + sendAlarms(inputBuffer); } else if (!strncmp_P(inputBuffer, PSTR("DAWN"), 4)) @@ -125,7 +182,11 @@ void parseUDP() memcpy(buff, &inputBuffer[4], strlen(inputBuffer)); // взять подстроку, состоящую последних символов строки inputBuffer, начиная с символа 5 dawnMode = atoi(buff) - 1; EepromManager::SaveDawnMode(&dawnMode); - sendAlarms(); + sendAlarms(inputBuffer); + + #if (USE_MQTT && ESP_MODE == 1) + MqttManager::needToPublish = true; + #endif } else if (!strncmp_P(inputBuffer, PSTR("DISCOVER"), 8)) // обнаружение приложением модуля esp в локальной сети @@ -143,7 +204,7 @@ void parseUDP() else if (!strncmp_P(inputBuffer, PSTR("TMR_GET"), 7)) { - sendTimer(); + sendTimer(inputBuffer); } else if (!strncmp_P(inputBuffer, PSTR("TMR_SET"), 7)) @@ -158,12 +219,16 @@ void parseUDP() TimerManager::TimeToFire = millis() + strtoull(buff, &endToken, 10) * 1000; TimerManager::TimerHasFired = false; - sendTimer(); + sendTimer(inputBuffer); + + #if (USE_MQTT && ESP_MODE == 1) + MqttManager::needToPublish = true; + #endif } else if (!strncmp_P(inputBuffer, PSTR("FAV_GET"), 7)) { - FavoritesManager::SetStatus(inputBuffer); + FavoritesManager::SetStatus(inputBuffer); } else if (!strncmp_P(inputBuffer, PSTR("FAV_SET"), 7)) @@ -172,6 +237,10 @@ void parseUDP() FavoritesManager::SetStatus(inputBuffer); settChanged = true; eepromTimeout = millis(); + + #if (USE_MQTT && ESP_MODE == 1) + MqttManager::needToPublish = true; + #endif } else if (!strncmp_P(inputBuffer, PSTR("OTA"), 3)) @@ -198,29 +267,16 @@ void parseUDP() return; } - if (Udp.remoteIP() == WiFi.localIP()) // не реагировать на свои же пакеты + if (generateOutput) // если запрошен вывод ответа выполнения команд, копируем его в исходящий буфер { - return; + strcpy(outputBuffer, inputBuffer); } - - char reply[strlen(inputBuffer) + 1]; - strcpy(reply, inputBuffer); - inputBuffer[0] = '\0'; // очистка буфера, читобы не он не интерпретировался, как следующий udp пакет - Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); - Udp.write(reply); - Udp.endPacket(); - - #ifdef GENERAL_DEBUG - Serial.print(F("Outbound UDP packet: ")); - Serial.println(reply); - Serial.println(); - #endif - } + inputBuffer[0] = '\0'; // очистка буфера, читобы не он не интерпретировался, как следующий входной пакет } -void sendCurrent() +void sendCurrent(char *outputBuffer) { - sprintf_P(inputBuffer, PSTR("CURR %u %u %u %u %u %u"), + sprintf_P(outputBuffer, PSTR("CURR %u %u %u %u %u %u"), currentMode, modes[currentMode].Brightness, modes[currentMode].Speed, @@ -229,40 +285,40 @@ void sendCurrent() ESP_MODE); #ifdef USE_NTP - strcat_P(inputBuffer, PSTR(" 1")); + strcat_P(outputBuffer, PSTR(" 1")); #else - strcat_P(inputBuffer, PSTR(" 0")); + strcat_P(outputBuffer, PSTR(" 0")); #endif - sprintf_P(inputBuffer, PSTR("%s %u"), inputBuffer, (uint8_t)TimerManager::TimerRunning); + sprintf_P(outputBuffer, PSTR("%s %u"), outputBuffer, (uint8_t)TimerManager::TimerRunning); #ifdef USE_NTP - sprintf_P(inputBuffer, PSTR("%s %s"), inputBuffer, timeClient.getFormattedTime().c_str()); + sprintf_P(outputBuffer, PSTR("%s %s"), outputBuffer, timeClient.getFormattedTime().c_str()); #else - sprintf_P(inputBuffer, PSTR("%s %ull"), inputBuffer, millis()); + sprintf_P(outputBuffer, PSTR("%s %ull"), outputBuffer, millis()); #endif } -void sendAlarms() +void sendAlarms(char *outputBuffer) { - strcpy_P(inputBuffer, PSTR("ALMS")); + strcpy_P(outputBuffer, PSTR("ALMS")); for (byte i = 0; i < 7; i++) { - sprintf_P(inputBuffer, PSTR("%s %u"), inputBuffer, (uint8_t)alarms[i].State); + sprintf_P(outputBuffer, PSTR("%s %u"), outputBuffer, (uint8_t)alarms[i].State); } for (byte i = 0; i < 7; i++) { - sprintf_P(inputBuffer, PSTR("%s %u"), inputBuffer, alarms[i].Time); + sprintf_P(outputBuffer, PSTR("%s %u"), outputBuffer, alarms[i].Time); } - sprintf_P(inputBuffer, PSTR("%s %u"), inputBuffer, dawnMode + 1); + sprintf_P(outputBuffer, PSTR("%s %u"), outputBuffer, dawnMode + 1); } -void sendTimer() +void sendTimer(char *outputBuffer) { - sprintf_P(inputBuffer, PSTR("TMR %u %u %u"), + sprintf_P(outputBuffer, PSTR("TMR %u %u %u"), TimerManager::TimerRunning, TimerManager::TimerOption, (TimerManager::TimerRunning ? (uint16_t)floor((TimerManager::TimeToFire - millis()) / 1000) : 0)); diff --git a/firmware/GyverLamp_v1.4/time.ino b/firmware/GyverLamp_v1.4/time.ino index f0978a0..a39c278 100644 --- a/firmware/GyverLamp_v1.4/time.ino +++ b/firmware/GyverLamp_v1.4/time.ino @@ -32,7 +32,7 @@ void timeTick() if (!ntpServerAddressResolved) { #ifdef GENERAL_DEBUG - Serial.println(F("Функции будильника отключены до восстановления подключения к интернету")); + LOG.println(F("Функции будильника отключены до восстановления подключения к интернету")); #endif } } @@ -60,6 +60,7 @@ void timeTick() { if (!manualOff) // будильник не был выключен вручную (из приложения или кнопкой) { + LOG.println("Будильник включен"); // величина рассвета 0-255 int32_t dawnPosition = 255 * ((float)(thisTime - (alarms[thisDay].Time - pgm_read_byte(&dawnOffsets[dawnMode]))) / pgm_read_byte(&dawnOffsets[dawnMode])); dawnPosition = constrain(dawnPosition, 0, 255); @@ -75,15 +76,16 @@ void timeTick() } else { + // не время будильника (ещё не начался или закончился по времени) if (dawnFlag) { dawnFlag = false; - manualOff = false; FastLED.clear(); delay(2); FastLED.show(); changePower(); // выключение матрицы или установка яркости текущего эффекта в засисимости от того, была ли включена лампа до срабатывания будильника } + manualOff = false; } } } @@ -102,7 +104,7 @@ void resolveNtpServerAddress(bool &ntpServerAddressResolved) // ф #ifdef GENERAL_DEBUG if (ntpServerAddressResolved) { - Serial.println(F("Подключение к интернету отсутствует")); + LOG.println(F("Подключение к интернету отсутствует")); } #endif @@ -113,7 +115,7 @@ void resolveNtpServerAddress(bool &ntpServerAddressResolved) // ф #ifdef GENERAL_DEBUG if (!ntpServerAddressResolved) { - Serial.println(F("Подключение к интернету установлено")); + LOG.println(F("Подключение к интернету установлено")); } #endif