Files
gunner47-GyverLamp/firmware/GyverLamp_v1.4/MqttManager.h

349 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 0 - установить режим "избранное", параметры - см. команду FAV ниже
* BTN ON - разблокировать кнопку на лампе
* BTN OFF - заблокировать кнопку на лампе
* Лампа отправляет своё состояние сразу после включения и после каждого изменения в топик LedLamp/LedLamp_<ChipId>/state; payload:
* "CURR 7 14 4 50 1 1 1 0 1 21:25:50", где:
* CURR - идентификатор команды, CURR - текущее состояние лампы
* 7 - номер текущего эффекта
* 14 - яркость
* 4 - скорость
* 50 - масштаб
* 1 - признак "матрица включена"
* 1 - режим ESP_MODE
* 1 - признак USE_NTP (пытаться синхронизировать время по серверам времени в интернете)
* 0 - признак "работает таймер"
* 1 - признак "кнопка разблокирована"
* 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 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, char* lampInputBuffer, 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 char* lampInputBuffer; // ссылка на inputBuffer для записи в него пришедшей MQTT команды и последующей её обработки лампой
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, char* lampInputBuffer, SendCurrentDelegate sendCurrentDelegate)
{
allocStr_P(&MqttManager::mqttServer, MqttServer);
allocStr_P(&MqttManager::mqttUser, MqttUser);
allocStr_P(&MqttManager::mqttPassword, MqttPassword);
MqttManager::mqttClient = mqttClient;
MqttManager::lampInputBuffer = lampInputBuffer;
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(lampInputBuffer, payload, len);
lampInputBuffer[len] = '\0';
needToPublish = true;
}
#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(lampInputBuffer);
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