Добавлено управление по протоколу MQTT; добавлена возможность вывода отладочных сообщений в telnet; исправлены ошибки (будильник, управление яркостью кнопкой, затирание параметров WiFi при включении)

This commit is contained in:
gunner47
2019-10-05 22:20:33 +03:00
parent d92b060335
commit 921a25bd16
12 changed files with 681 additions and 116 deletions

View File

@@ -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 <AsyncMqttClient.h>
#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