8 Commits
v1.0 ... v1.2

Author SHA1 Message Date
219cf17855 Version 1.2
Fixed some minor bugs.
Added window/door sensor support.
2023-01-06 13:09:21 +03:00
3aca3b7bbd Minor changes 2023-01-05 12:34:42 +03:00
9a86f806e4 Version 1.1
To Do item 1 performed.
2023-01-04 14:02:05 +03:00
aeaf9ad12c Version 1.01
Minor code optimization.
2023-01-03 21:23:43 +03:00
70181f56f0 Minor changes 2022-12-29 20:25:45 +03:00
028894a8cb Minor changes 2022-12-29 19:00:26 +03:00
409cdf88b8 Minor changes 2022-12-29 16:40:12 +03:00
280198da3a Minor changes 2022-12-28 21:49:07 +03:00
4 changed files with 159 additions and 484 deletions

View File

@ -4,18 +4,19 @@ Gateway for data exchange between ESP-NOW devices and MQTT broker via WiFi.
## Features ## Features
1. The first time turn on (or after rebooting) creates an access point named "ESP-NOW Gateway XXXXXXXXXXXX" with password "12345678" (IP 192.168.4.1) if fails to connect to WiFi. In case of lost a WiFi connection after successfuly connection search the required WiFi SSID availability every 30 seconds. 1. Creates an access point named "ESP-NOW Gateway XXXXXXXXXXXX" with password "12345678" (IP 192.168.4.1).
2. Possibility a device search through the Windows Network Environment via SSDP. 2. Possibility a device search through the Windows Network Environment via SSDP.
3. Periodically transmission of system information to the MQTT broker (every 60 seconds) and availability status to the ESP-NOW network and to the MQTT broker (every 10 seconds). 3. Periodically transmission of system information to the MQTT broker (every 60 seconds) and availability status to the ESP-NOW network and to the MQTT broker (every 10 seconds).
4. Automatically adds gateway configuration to Home Assistan via MQTT discovery as a binary_sensor. 4. Automatically adds gateway configuration to Home Assistan via MQTT discovery as a binary_sensor.
5. Possibility firmware update over OTA. 5. Automatically adds supported ESP-NOW devices configurations to Home Assistan via MQTT discovery.
6. Web interface for settings. 6. Possibility firmware update over OTA.
7. Web interface for settings.
## Notes ## Notes
1. ESP-NOW mesh network based on the library [ZHNetwork](https://github.com/aZholtikov/ZHNetwork). 1. ESP-NOW mesh network based on the library [ZHNetwork](https://github.com/aZholtikov/ZHNetwork).
2. Regardless of the status of connections to WiFi or MQTT the device perform ESP-NOW node function. 2. Regardless of the status of connections to WiFi or MQTT the device perform ESP-NOW node function.
3. For restart the device (without using the Web interface and only if MQTT connection established) send an "restart" command to the device's root topic (example - "homeassistant/gateway/70039F44BEF7"). 3. For restart the device (without using the Web interface and only if MQTT connection established) send an "restart" command to the device's root topic (example - "homeassistant/espnow_gateway/70039F44BEF7").
## Attention ## Attention
@ -23,17 +24,22 @@ Gateway for data exchange between ESP-NOW devices and MQTT broker via WiFi.
2. Upload the "data" folder (with web interface) into the filesystem before flashing. 2. Upload the "data" folder (with web interface) into the filesystem before flashing.
3. WiFi router must be set on channel 1. 3. WiFi router must be set on channel 1.
## Tested on
1. NodeMCU 1.0 (ESP-12E Module). ESP-NOW + WiFi mode. Unstable work.
2. AZ-Delivery ESP-32 Dev Kit C V4. ESP-NOW + WiFi mode. Stable work.
## Supported devices ## Supported devices
1. [RF - Gateway](https://github.com/aZholtikov/RF-Gateway) 1. [RF Gateway](https://github.com/aZholtikov/RF-Gateway) (coming soon)
2. [ESP-NOW Switch](https://github.com/aZholtikov/ESP-NOW-Switch) 2. [ESP-NOW Switch](https://github.com/aZholtikov/ESP-NOW-Switch)
3. [ESP-NOW Led Light/Strip](https://github.com/aZholtikov/ESP-NOW-Led-Light-Strip) 3. [ESP-NOW Light/Led Strip](https://github.com/aZholtikov/ESP-NOW-Light-Led-Strip)
4. [ESP-NOW Window/Door Sensor](https://github.com/aZholtikov/ESP-NOW-Window-Door-Sensor) 4. [ESP-NOW Window/Door Sensor](https://github.com/aZholtikov/ESP-NOW-Window-Door-Sensor)
5. [ESP-NOW Water Leakage Sensor](https://github.com/aZholtikov/ESP-NOW-Water-Leakage-Sensor) 5. [ESP-NOW Water Leakage Sensor](https://github.com/aZholtikov/ESP-NOW-Water-Leakage-Sensor) (coming soon)
## To Do ## To Do
- [ ] Automatically add ESP-NOW devices configurations to Home Assistan via MQTT discovery. - [X] Automatically add ESP-NOW devices configurations to Home Assistan via MQTT discovery.
- [ ] LAN connection support. - [ ] LAN connection support.
- [ ] nRF24 device support (in current time uses "RF Gateway"). - [ ] nRF24 device support (in current time uses "RF Gateway").
- [ ] BLE device support (for ESP32). - [ ] BLE device support (for ESP32).

356
main.cpp
View File

@ -1,356 +0,0 @@
#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncMqttClient.h>
#include <ESP32SSDP.h>
#include "ArduinoJson.h" // Версия 5. С другой не работает.
#include <SPIFFS.h>
#include <FS.h>
#include <time.h>
#include <Ticker.h>
#include <ArduinoOTA.h>
#include "AsyncTelegram.h"
//*******************************************************************************************************************************//
String ssidStaName = "ZH-SMART"; // Имя Wi-Fi сети.
String passwordSta = "Firan1978"; // Пароль Wi-Fi сети.
String ssidApName = "ESP32"; // Имя точки доступа.
String passwordAp = ""; // Пароль точки доступа.
String deviceName = "Smart Home Controller";
String modelName = "Smart Home Controller";
String modelNumber = "00000003";
String serialNumber = "00000001";
String uuid = "3c1b475a-e586-40e9-8605-f818f0ad5891";
IPAddress IP(192, 168, 4, 1); // IP адрес точки доступа.
String mqttHostName = "mqtt.zh.com.ru"; // Адрес MQTT сервера.
uint mqttHostPort = 1883; // Порт MQTT сервера.
String mqttUserLogin = "";
String mqttUserPassword = "";
String token = "1471595796:AAGZPvrk8fa6-KaV0oTaveuPXMzA-_3Ql9U"; // Telegram токен.
ulong userID = 1472083376;
String controllerTopicHead = "Квартира/Контроллеры/Шлюз";
bool countertop_lighting_status_Kitchen;
bool heating_battery_1_valve_status_Living_Room;
int heating_battery_1_temperature_Living_Room;
//*******************************************************************************************************************************//
//*******************************************************************************************************************************//
void loadNetConfigFile(void); // Функция загрузки конфигурации из файла netconfig.json.
void saveNetConfigFile(void); // Функция записи конфигурации в файл netconfig.json.
void setupSsdp(void); // Функция настройки протокола SSDP.
void setupWebServer(void); // Функция настройки WEB сервера.
void connectToWiFi(void); // Функция подключения к Wi-Fi сети.
void connectToMqtt(void); // Функция подключения к MQTT серверу.
void reboot(void); // Функция перезагрузки при работе в режиме точки доступа.
String processor(const String &var); // Функция обработки HTTP запросов.
String xmlNode(String tags, String data); // Функция "сборки" информационного SSDP файла.
String decToHex(uint32_t decValue, byte desiredStringLength); // Функция перевода в шестнадцатеричную систему.
void onMqttConnect(bool sessionPresent); // Событие. Если выполнено подключение к MQTT серверу.
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason); // Событие. Если произошло отключение от MQTT сервера.
void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total); // Событие. Если получен топик.
//*******************************************************************************************************************************//
//*******************************************************************************************************************************//
AsyncWebServer server(80); // Создаём объект server для работы с библиотекой ESPAsyncWebServer.
AsyncMqttClient mqttClient; // Создаём объект mqttClient для работы с библиотекой AsyncMqttClient.
WiFiClient client; // Создаём объект client для работы с библиотекой WiFi.
AsyncTelegram myBot; // Создаём объект myBot для работы с библиотекой AsyncTelegram.
Ticker mqttReconnectTimer; // Создаём таймер переподключения к MQTT серверу (при потере соединения).
Ticker apModeRebootTimer; // Создаём таймер перезагрузки при работе в режиме точки доступа.
//*******************************************************************************************************************************//
void setup()
{
SPIFFS.begin(); // Инициируем работу файловой системы.
Serial.begin(115200); // Инициируем работу SERIAL.
mqttClient.onConnect(onMqttConnect); // Включаем обработчик события подключения к MQTT серверу.
mqttClient.onDisconnect(onMqttDisconnect); // Включаем обработчик события отключения от MQTT сервера.
mqttClient.onMessage(onMqttMessage); // Включаем обработчик события получения топика.
mqttClient.setServer(mqttHostName.c_str(), mqttHostPort); // Устанавливаем параметры подключения к MQTT серверу.
mqttClient.setCredentials(mqttUserLogin.c_str(), mqttUserPassword.c_str());
saveNetConfigFile();
loadNetConfigFile(); // Загружаем конфигурацию из файла netconfig.json.
connectToWiFi(); // Подключаемся к Wi-Fi.
setupSsdp(); // Настраиваем протокол SSDP.
setupWebServer(); // Настраиваем WEB сервер.
connectToMqtt(); // Подключаемся к MQTT серверу.
myBot.setTelegramToken(token.c_str());
myBot.begin();
ArduinoOTA.begin(); // Запускаем сервер обновления "по воздуху".
apModeRebootTimer.attach(300, reboot); // Запускаем таймер перезагрузки при работе в режиме точки доступа.
}
void loop()
{
ArduinoOTA.handle();
}
void loadNetConfigFile(void) // Функция загрузки конфигурации из файла netconfig.json.
{
if (!SPIFFS.exists("/netconfig.json")) // Если файл не существует:
saveNetConfigFile(); // Создаем файл, записав в него данные по умолчанию.
File file = SPIFFS.open("/netconfig.json", "r"); // Открываем файл для чтения.
String jsonFile = file.readString(); // Читаем файл в переменную.
DynamicJsonDocument json(1024); // Резервируем память для JSON объекта.
deserializeJson(json, jsonFile);
ssidStaName = json["ssidStaName"].as<String>(); // Читаем поля JSON.
passwordSta = json["passwordSta"].as<String>();
ssidApName = json["ssidApName"].as<String>();
passwordAp = json["passwordAp"].as<String>();
deviceName = json["deviceName"].as<String>();
mqttHostName = json["mqttHostName"].as<String>();
mqttHostPort = json["mqttHostPort"];
mqttUserLogin = json["mqttUserLogin"].as<String>();
mqttUserPassword = json["mqttUserPassword"].as<String>();
token = json["token"].as<String>();
userID = json["userID"];
file.close(); // Закрываем файл.
}
void saveNetConfigFile(void) // Функция записи конфигурации в файл netconfig.json.
{
DynamicJsonDocument json(1024); // Резервируем память для JSON объекта.
json["ssidStaName"] = ssidStaName; // Заполняем поля JSON.
json["passwordSta"] = passwordSta;
json["ssidApName"] = ssidApName;
json["passwordAp"] = passwordAp;
json["deviceName"] = deviceName;
json["mqttHostName"] = mqttHostName;
json["mqttHostPort"] = mqttHostPort;
json["mqttUserLogin"] = mqttUserLogin;
json["mqttUserPassword"] = mqttUserPassword;
json["token"] = token;
json["userID"] = userID;
File file = SPIFFS.open("/netconfig.json", "w"); // Открываем файл для записи.
serializeJsonPretty(json, file); // Записываем строку JSON в файл.
file.close(); // Закрываем файл.
}
void setupSsdp(void) // Функция настройки протокола SSDP.
{
SSDP.setSchemaURL("description.xml");
SSDP.setDeviceType("upnp:rootdevice");
server.on("/description.xml", HTTP_GET, [](AsyncWebServerRequest *request) {
String ssdpSend = "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">";
String ssdpHeder = xmlNode("major", "1");
ssdpHeder += xmlNode("minor", "0");
ssdpHeder = xmlNode("specVersion", ssdpHeder);
ssdpHeder += xmlNode("URLBase", "http://" + WiFi.localIP().toString());
String ssdpDescription = xmlNode("deviceType", "upnp:rootdevice");
ssdpDescription += xmlNode("friendlyName", deviceName);
ssdpDescription += xmlNode("presentationURL", "/");
ssdpDescription += xmlNode("serialNumber", serialNumber);
ssdpDescription += xmlNode("modelName", modelName);
ssdpDescription += xmlNode("modelNumber", modelNumber);
ssdpDescription += xmlNode("modelURL", "http://zh.com.ru");
ssdpDescription += xmlNode("manufacturer", "Alexey Zholtikov");
ssdpDescription += xmlNode("manufacturerURL", "http://zh.com.ru");
ssdpDescription += xmlNode("UDN", uuid);
ssdpDescription = xmlNode("device", ssdpDescription);
ssdpHeder += ssdpDescription;
ssdpSend += ssdpHeder;
ssdpSend += "</root>";
request->send(200, "text/xml", ssdpSend);
});
SSDP.begin(); // Запускаем протокол SSDP.
}
void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
if(!index){
Serial.printf("UploadStart: %s\n", filename.c_str());
}
for(size_t i=0; i<len; i++){
Serial.write(data[i]);
}
if(final){
Serial.printf("UploadEnd: %s, %u B\n", filename.c_str(), index+len);
}
}
void setupWebServer(void) // Функция настройки WEB сервера.
{
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(SPIFFS, "/index.htm", String(), false, processor);
});
server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", String(ESP.getFreeHeap()));
});
server.on(
"/upload", HTTP_POST, [](AsyncWebServerRequest *request) {
request->send(200);
},
handleUpload);
server.on("/restart", HTTP_GET, [](AsyncWebServerRequest *request) { // Перезагрузка модуля по запросу вида /restart?device=ok.
if (request->getParam("device")->value() == "ok")
{
request->send(200, "text/plain", "Reset OK");
ESP.restart();
}
else
{
request->send(200, "text/plain", "No Reset");
}
});
server.on("/status", HTTP_GET, [](AsyncWebServerRequest *request) { // Получение статуса модуля.
request->send(200, "text/plain", "OK");
});
server.on("/countertop_lighting_status_Kitchen", HTTP_GET, [](AsyncWebServerRequest *request) { // Получение статуса подсветки на кухне.
StaticJsonDocument<100> json;
json["value"] = countertop_lighting_status_Kitchen;
char buffer[100];
serializeJson(json, buffer);
request->send(200, "text/json", buffer);
});
server.on("/heating_battery_1_valve_status_Living_Room", HTTP_GET, [](AsyncWebServerRequest *request) { // Получение статуса клапана батареи №1 в гостиной.
StaticJsonDocument<100> json;
json["value"] = heating_battery_1_valve_status_Living_Room;
char buffer[100];
serializeJson(json, buffer);
request->send(200, "text/json", buffer);
});
server.on("/heating_battery_1_temperature_Living_Room", HTTP_GET, [](AsyncWebServerRequest *request) { // Получение температуры батареи №1 в гостиной.
StaticJsonDocument<100> json;
json["value"] = heating_battery_1_temperature_Living_Room;
char buffer[100];
serializeJson(json, buffer);
request->send(200, "text/json", buffer);
});
server.onNotFound([](AsyncWebServerRequest *request) { // Передача файлов на страницу.
if (SPIFFS.exists(request->url()))
request->send(SPIFFS, request->url(), String(), false);
else
{
request->send(404, "text/plain", "File Not Found");
}
});
server.onFileUpload(handleUpload);
server.begin(); // Запускаем WEB сервер.
}
void connectToWiFi(void) // Функция подключения к Wi-Fi сети.
{
WiFi.mode(WIFI_STA); // Устанавливаем режим работы (WIFI_STA - подключение к сети Wi-Fi).
byte tries = 10; // Счетчик количества попыток подключения.
WiFi.begin(ssidStaName.c_str(), passwordSta.c_str()); // Подключаемся к Wi-Fi сети.
while (tries-- && WiFi.status() != WL_CONNECTED) // Пытаемся подключиться к Wi-Fi сети.
{
delay(1000); // Пауза между попытками подключения.
}
if (WiFi.status() != WL_CONNECTED) // Если подключение не удалось:
{
WiFi.disconnect(); // Отключаем Wi-Fi.
WiFi.mode(WIFI_AP); // Устанавливаем режим работы (WIFI_AP - точка доступа).
WiFi.softAPConfig(IP, IP, IPAddress(255, 255, 255, 0)); // Задаем настройки сети.
WiFi.softAP(ssidApName.c_str(), passwordAp.c_str()); // Включаем Wi-Fi в режиме точки доступа.
}
}
void connectToMqtt() // Функция подключения к MQTT серверу.
{
mqttClient.connect();
}
void reboot(void) // Функция перезагрузки при работе в режиме точки доступа.
{
if (WiFi.getMode() == WIFI_AP)
{
ESP.restart();
}
}
String processor(const String &var) // Функция обработки HTTP запросов.
{
return String();
}
String xmlNode(String tags, String data) // Функция "сборки" информационного SSDP файла.
{
String temp = "<" + tags + ">" + data + "</" + tags + ">";
return temp;
}
String decToHex(uint32_t decValue, byte desiredStringLength) // Функция перевода в шестнадцатеричную систему.
{
String hexString = String(decValue, HEX);
while (hexString.length() < desiredStringLength)
hexString = "0" + hexString;
return hexString;
}
void onMqttConnect(bool sessionPresent) // Если выполнено подключение к MQTT серверу:
{
mqttReconnectTimer.detach(); // Отключаем таймер переподключения к MQTT серверу.
mqttClient.publish(String(controllerTopicHead + "/IP").c_str(), 2, true, String(WiFi.localIP().toString()).c_str()); // Публикуем IP.
mqttClient.publish(String(controllerTopicHead + "/ID").c_str(), 2, true, decToHex(ESP.getEfuseMac(), 6).c_str()); // Публикуем Chip ID.
mqttClient.publish(String(controllerTopicHead + "/Статус").c_str(), 2, true, "Работает"); // Публикуем статус.
mqttClient.subscribe("#", 2); // Подписываемся на все топики.
}
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) // Если произошло отключение от MQTT сервера:
{
mqttReconnectTimer.attach(10, connectToMqtt); // Включаем таймер переподключения к MQTT серверу.
}
void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) // Если получен топик:
{
if (String(topic) == "Квартира/Освещение/Кухня/Подсветка столешницы/Состояние/Реле")
{
if (String(payload).substring(0, len) == "Вкл")
{
countertop_lighting_status_Kitchen = true;
return;
}
if (String(payload).substring(0, len) == "Выкл")
{
countertop_lighting_status_Kitchen = false;
return;
}
}
if (String(topic) == "Квартира/Отопление/Гостиная/Батарея №1/Состояние/Клапан")
{
if (String(payload).substring(0, len) == "Открыт")
{
heating_battery_1_valve_status_Living_Room = true;
return;
}
if (String(payload).substring(0, len) == "Закрыт")
{
heating_battery_1_valve_status_Living_Room = false;
return;
}
}
if (String(topic) == "Квартира/Отопление/Гостиная/Батарея №1/Состояние/Температура")
{
heating_battery_1_temperature_Living_Room = String(payload).substring(0, len).toInt();
return;
}
}

View File

@ -38,7 +38,7 @@ lib_deps =
platform = espressif32 platform = espressif32
board = az-delivery-devkit-v4 board = az-delivery-devkit-v4
framework = arduino framework = arduino
upload_port = 192.168.1.144 upload_port = 192.168.1.143
upload_protocol = espota upload_protocol = espota
lib_deps = lib_deps =
https://github.com/aZholtikov/ZHNetwork https://github.com/aZholtikov/ZHNetwork

View File

@ -33,9 +33,8 @@ String xmlNode(String tags, String data);
void setupWebServer(void); void setupWebServer(void);
void connectToMqtt(void); void connectToMqtt(void);
void connectToWifi(void);
const String firmware{"1.0"}; const String firmware{"1.2"};
String espnowNetName{"DEFAULT"}; String espnowNetName{"DEFAULT"};
@ -50,22 +49,16 @@ String mqttUserLogin{""};
String mqttUserPassword{""}; String mqttUserPassword{""};
String topicPrefix{"homeassistant"}; String topicPrefix{"homeassistant"};
bool isWasConnectionToWifi{false};
ZHNetwork myNet; ZHNetwork myNet;
AsyncWebServer webServer(80); AsyncWebServer webServer(80);
AsyncMqttClient mqttClient; AsyncMqttClient mqttClient;
Ticker wifiReconnectTimer;
bool wifiReconnectTimerSemaphore{false};
void wifiReconnectTimerCallback(void);
Ticker mqttReconnectTimer; Ticker mqttReconnectTimer;
bool mqttReconnectTimerSemaphore{false}; bool mqttReconnectTimerSemaphore{false};
void mqttReconnectTimerCallback(void); void mqttReconnectTimerCallback(void);
Ticker keepAliveMessageTimer; Ticker keepAliveMessageTimer;
bool keepAliveMessageTimerSemaphore{false}; bool keepAliveMessageTimerSemaphore{true};
void keepAliveMessageTimerCallback(void); void keepAliveMessageTimerCallback(void);
Ticker attributesMessageTimer; Ticker attributesMessageTimer;
@ -74,9 +67,11 @@ void attributesMessageTimerCallback(void);
void setup() void setup()
{ {
WiFi.onEvent(onWifiEvent);
SPIFFS.begin(); SPIFFS.begin();
loadConfig(); loadConfig();
WiFi.onEvent(onWifiEvent);
#if defined(ESP8266) #if defined(ESP8266)
WiFi.setSleepMode(WIFI_NONE_SLEEP); WiFi.setSleepMode(WIFI_NONE_SLEEP);
#endif #endif
@ -86,32 +81,48 @@ void setup()
WiFi.persistent(false); WiFi.persistent(false);
WiFi.setAutoConnect(false); WiFi.setAutoConnect(false);
WiFi.setAutoReconnect(false); WiFi.setAutoReconnect(true);
myNet.begin(espnowNetName.c_str()); myNet.begin(espnowNetName.c_str(), true);
myNet.setOnBroadcastReceivingCallback(onEspnowMessage); myNet.setOnBroadcastReceivingCallback(onEspnowMessage);
myNet.setOnUnicastReceivingCallback(onEspnowMessage); myNet.setOnUnicastReceivingCallback(onEspnowMessage);
WiFi.softAP(("ESP-NOW Gateway " + myNet.getNodeMac()).c_str(), "12345678");
uint8_t scan = WiFi.scanNetworks(false, false, 1);
String name;
int32_t rssi;
uint8_t encryption;
uint8_t *bssid;
int32_t channel;
bool hidden;
for (int8_t i = 0; i < scan; i++)
{
#if defined(ESP8266)
WiFi.getNetworkInfo(i, name, encryption, rssi, bssid, channel, hidden);
#endif
#if defined(ESP32)
WiFi.getNetworkInfo(i, name, encryption, rssi, bssid, channel);
#endif
if (name == ssid)
WiFi.begin(ssid.c_str(), password.c_str());
}
mqttClient.onConnect(onMqttConnect); mqttClient.onConnect(onMqttConnect);
mqttClient.onDisconnect(onMqttDisconnect); mqttClient.onDisconnect(onMqttDisconnect);
mqttClient.onMessage(onMqttMessage); mqttClient.onMessage(onMqttMessage);
mqttClient.setServer(mqttHostName.c_str(), mqttHostPort); mqttClient.setServer(mqttHostName.c_str(), mqttHostPort);
mqttClient.setCredentials(mqttUserLogin.c_str(), mqttUserPassword.c_str()); mqttClient.setCredentials(mqttUserLogin.c_str(), mqttUserPassword.c_str());
connectToWifi();
setupWebServer(); setupWebServer();
ArduinoOTA.begin(); ArduinoOTA.begin();
sendKeepAliveMessage();
keepAliveMessageTimer.attach(10, keepAliveMessageTimerCallback); keepAliveMessageTimer.attach(10, keepAliveMessageTimerCallback);
} }
void loop() void loop()
{ {
if (wifiReconnectTimerSemaphore)
connectToWifi();
if (mqttReconnectTimerSemaphore) if (mqttReconnectTimerSemaphore)
connectToMqtt(); connectToMqtt();
if (keepAliveMessageTimerSemaphore) if (keepAliveMessageTimerSemaphore)
@ -124,19 +135,13 @@ void loop()
void onWifiEvent(WiFiEvent_t event) void onWifiEvent(WiFiEvent_t event)
{ {
switch (event)
{
#if defined(ESP8266) #if defined(ESP8266)
case WIFI_EVENT_STAMODE_DISCONNECTED: if (event == WIFI_EVENT_STAMODE_GOT_IP)
#endif #endif
#if defined(ESP32) #if defined(ESP32)
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP)
#endif #endif
WiFi.mode(WIFI_OFF); // Without rebooting WiFi stops working ESP-NOW. mqttClient.connect();
myNet.begin(espnowNetName.c_str());
wifiReconnectTimer.attach(30, wifiReconnectTimerCallback);
break;
}
} }
void onEspnowMessage(const char *data, const uint8_t *sender) void onEspnowMessage(const char *data, const uint8_t *sender)
@ -145,42 +150,124 @@ void onEspnowMessage(const char *data, const uint8_t *sender)
return; return;
esp_now_payload_data_t incomingData; esp_now_payload_data_t incomingData;
memcpy(&incomingData, data, sizeof(esp_now_payload_data_t)); memcpy(&incomingData, data, sizeof(esp_now_payload_data_t));
esp_now_payload_data_t jsonData; if (incomingData.payloadsType == ENPT_ATTRIBUTES)
memcpy(&jsonData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
switch (incomingData.payloadsType)
{
case ENPT_ATTRIBUTES:
mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, incomingData.message); mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, incomingData.message);
break; if (incomingData.payloadsType == ENPT_KEEP_ALIVE)
case ENPT_KEEP_ALIVE:
mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, "online"); mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, "online");
break; if (incomingData.payloadsType == ENPT_STATE)
case ENPT_SET:
break;
case ENPT_STATE:
mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, incomingData.message); mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, incomingData.message);
break; if (incomingData.payloadsType == ENPT_CONFIG)
case ENPT_UPDATE: {
break; if (incomingData.deviceType == ENDT_SWITCH)
case ENPT_RESTART: {
break; esp_now_payload_data_t configData;
case ENPT_SYSTEM: memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
break; StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
case ENPT_CONFIG: deserializeJson(json, configData.message);
break; uint8_t unit = json["unit"].as<uint8_t>();
case ENPT_FORWARD: ha_component_type_t type = json["type"].as<ha_component_type_t>();
deserializeJson(json, jsonData.message); StaticJsonDocument<2048> jsonConfig;
jsonConfig["platform"] = "mqtt";
jsonConfig["name"] = json["name"];
jsonConfig["unique_id"] = myNet.macToString(sender) + "-" + unit;
jsonConfig["device_class"] = getValueName(json["class"].as<ha_switch_device_class_t>());
jsonConfig["state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
jsonConfig["value_template"] = "{{ value_json.state }}";
jsonConfig["command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/set";
jsonConfig["json_attributes_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/attributes";
jsonConfig["availability_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/status";
jsonConfig["payload_on"] = json["reverse"] == "true" ? "OFF" : "ON";
jsonConfig["payload_off"] = json["reverse"] == "true" ? "ON" : "OFF";
jsonConfig["optimistic"] = "false";
jsonConfig["qos"] = 2;
jsonConfig["retain"] = "true";
char buffer[2048]{0};
serializeJsonPretty(jsonConfig, buffer);
mqttClient.publish((topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), 2, true, buffer);
}
if (incomingData.deviceType == ENDT_LED)
{
esp_now_payload_data_t configData;
memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
deserializeJson(json, configData.message);
uint8_t unit = json["unit"].as<uint8_t>();
ha_component_type_t type = json["type"].as<ha_component_type_t>();
esp_now_led_type_t ledClass = json["class"];
StaticJsonDocument<2048> jsonConfig;
jsonConfig["platform"] = "mqtt";
jsonConfig["name"] = json["name"];
jsonConfig["unique_id"] = myNet.macToString(sender) + "-" + unit;
jsonConfig["state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
jsonConfig["state_value_template"] = "{{ value_json.state }}";
jsonConfig["command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/set";
jsonConfig["brightness_state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
jsonConfig["brightness_value_template"] = "{{ value_json.brightness }}";
jsonConfig["brightness_command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/brightness";
if (ledClass == ENLT_RGB || ledClass == ENLT_RGBW || ledClass == ENLT_RGBWW)
{
jsonConfig["rgb_state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
jsonConfig["rgb_value_template"] = "{{ value_json.rgb | join(',') }}";
jsonConfig["rgb_command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/rgb";
}
if (ledClass == ENLT_WW || ledClass == ENLT_RGBWW)
{
jsonConfig["color_temp_state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
jsonConfig["color_temp_value_template"] = "{{ value_json.temperature }}";
jsonConfig["color_temp_command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/temperature";
}
jsonConfig["json_attributes_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/attributes";
jsonConfig["availability_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/status";
jsonConfig["payload_on"] = "ON";
jsonConfig["payload_off"] = "OFF";
jsonConfig["optimistic"] = "false";
jsonConfig["qos"] = 2;
jsonConfig["retain"] = "true";
char buffer[2048]{0};
serializeJsonPretty(jsonConfig, buffer);
mqttClient.publish((topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), 2, true, buffer);
}
if (incomingData.deviceType == ENDT_SENSOR)
{
esp_now_payload_data_t configData;
memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
deserializeJson(json, configData.message);
uint8_t unit = json["unit"].as<uint8_t>();
ha_component_type_t type = json["type"].as<ha_component_type_t>();
StaticJsonDocument<2048> jsonConfig;
jsonConfig["platform"] = "mqtt";
jsonConfig["name"] = json["name"];
jsonConfig["unique_id"] = myNet.macToString(sender) + "-" + unit;
jsonConfig["state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
jsonConfig["json_attributes_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/attributes";
jsonConfig["force_update"] = "true";
jsonConfig["qos"] = 2;
jsonConfig["retain"] = "true";
if (type == HACT_BINARY_SENSOR)
{
jsonConfig["device_class"] = getValueName(json["class"].as<ha_binary_sensor_device_class_t>());
jsonConfig["payload_on"] = json["payload_on"];
jsonConfig["payload_off"] = json["payload_off"];
}
char buffer[2048]{0};
serializeJsonPretty(jsonConfig, buffer);
mqttClient.publish((topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), 2, true, buffer);
}
}
if (incomingData.payloadsType == ENPT_FORWARD)
{
esp_now_payload_data_t forwardData;
memcpy(&forwardData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
deserializeJson(json, forwardData.message);
mqttClient.publish((topicPrefix + "/rf_sensor/" + getValueName(json["type"].as<rf_sensor_type_t>()) + "/" + json["id"].as<uint16_t>()).c_str(), 2, false, incomingData.message); mqttClient.publish((topicPrefix + "/rf_sensor/" + getValueName(json["type"].as<rf_sensor_type_t>()) + "/" + json["id"].as<uint16_t>()).c_str(), 2, false, incomingData.message);
break;
default:
break;
} }
} }
void onMqttConnect(bool sessionPresent) void onMqttConnect(bool sessionPresent)
{ {
mqttClient.subscribe((topicPrefix + "/gateway/#").c_str(), 2); mqttClient.subscribe((topicPrefix + "/espnow_gateway/#").c_str(), 2);
mqttClient.subscribe((topicPrefix + "/espnow_switch/#").c_str(), 2); mqttClient.subscribe((topicPrefix + "/espnow_switch/#").c_str(), 2);
mqttClient.subscribe((topicPrefix + "/espnow_led/#").c_str(), 2); mqttClient.subscribe((topicPrefix + "/espnow_led/#").c_str(), 2);
@ -189,8 +276,8 @@ void onMqttConnect(bool sessionPresent)
json["name"] = deviceName; json["name"] = deviceName;
json["unique_id"] = myNet.getNodeMac() + "-1"; json["unique_id"] = myNet.getNodeMac() + "-1";
json["device_class"] = "connectivity"; json["device_class"] = "connectivity";
json["state_topic"] = topicPrefix + "/gateway/" + myNet.getNodeMac() + "/status"; json["state_topic"] = topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/status";
json["json_attributes_topic"] = topicPrefix + "/gateway/" + myNet.getNodeMac() + "/attributes"; json["json_attributes_topic"] = topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/attributes";
json["payload_on"] = "online"; json["payload_on"] = "online";
json["expire_after"] = 30; json["expire_after"] = 30;
json["force_update"] = "true"; json["force_update"] = "true";
@ -273,12 +360,13 @@ void sendKeepAliveMessage()
{ {
keepAliveMessageTimerSemaphore = false; keepAliveMessageTimerSemaphore = false;
if (mqttClient.connected()) if (mqttClient.connected())
mqttClient.publish((topicPrefix + "/gateway/" + myNet.getNodeMac() + "/status").c_str(), 2, true, "online"); mqttClient.publish((topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/status").c_str(), 2, true, "online");
esp_now_payload_data_t outgoingData; esp_now_payload_data_t outgoingData;
outgoingData.deviceType = ENDT_GATEWAY; outgoingData.deviceType = ENDT_GATEWAY;
outgoingData.payloadsType = ENPT_KEEP_ALIVE; outgoingData.payloadsType = ENPT_KEEP_ALIVE;
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json; StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
json["MQTT"] = mqttClient.connected() ? "online" : "offline"; json["MQTT"] = mqttClient.connected() ? "online" : "offline";
json["frequency"] = 10; // For compatibility with the previous version. Will be removed in future releases.
char buffer[sizeof(esp_now_payload_data_t::message)]{0}; char buffer[sizeof(esp_now_payload_data_t::message)]{0};
serializeJsonPretty(json, buffer); serializeJsonPretty(json, buffer);
memcpy(&outgoingData.message, &buffer, sizeof(esp_now_payload_data_t::message)); memcpy(&outgoingData.message, &buffer, sizeof(esp_now_payload_data_t::message));
@ -309,7 +397,7 @@ void sendAttributesMessage()
json["Uptime"] = "Days:" + String(days) + " Hours:" + String(hours - (days * 24)) + " Mins:" + String(mins - (hours * 60)); json["Uptime"] = "Days:" + String(days) + " Hours:" + String(hours - (days * 24)) + " Mins:" + String(mins - (hours * 60));
char buffer[sizeof(esp_now_payload_data_t::message)]{0}; char buffer[sizeof(esp_now_payload_data_t::message)]{0};
serializeJsonPretty(json, buffer); serializeJsonPretty(json, buffer);
mqttClient.publish((topicPrefix + "/gateway/" + myNet.getNodeMac() + "/attributes").c_str(), 2, true, buffer); mqttClient.publish((topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/attributes").c_str(), 2, true, buffer);
} }
String getValue(String data, char separator, uint8_t index) String getValue(String data, char separator, uint8_t index)
@ -441,69 +529,6 @@ void connectToMqtt()
mqttClient.connect(); mqttClient.connect();
} }
void connectToWifi()
{
wifiReconnectTimerSemaphore = false;
uint8_t scan = WiFi.scanNetworks(false, false, 1);
String name;
int32_t rssi;
uint8_t encryption;
uint8_t *bssid;
int32_t channel;
bool hidden;
bool flag{false};
for (int8_t i = 0; i < scan; i++)
{
#if defined(ESP8266)
WiFi.getNetworkInfo(i, name, encryption, rssi, bssid, channel, hidden);
#endif
#if defined(ESP32)
WiFi.getNetworkInfo(i, name, encryption, rssi, bssid, channel);
#endif
if (name == ssid)
flag = true;
}
if (flag)
{
WiFi.begin(ssid.c_str(), password.c_str());
while (true)
{
if (WiFi.status() == WL_CONNECTED)
{
isWasConnectionToWifi = true;
wifiReconnectTimer.detach();
mqttClient.connect();
return;
}
if (WiFi.status() == WL_CONNECT_FAILED)
{
if (!isWasConnectionToWifi)
{
WiFi.mode(WIFI_AP);
WiFi.softAP(String("ESP-NOW Gateway " + myNet.getNodeMac()).c_str(), "12345678");
}
return;
}
delay(100);
}
}
else
{
WiFi.mode(WIFI_OFF); // Without rebooting WiFi stops working ESP-NOW.
myNet.begin(espnowNetName.c_str());
}
if (!isWasConnectionToWifi)
{
WiFi.mode(WIFI_AP);
WiFi.softAP(String("ESP-NOW Gateway " + myNet.getNodeMac()).c_str(), "12345678");
}
}
void wifiReconnectTimerCallback()
{
wifiReconnectTimerSemaphore = true;
}
void mqttReconnectTimerCallback() void mqttReconnectTimerCallback()
{ {
mqttReconnectTimerSemaphore = true; mqttReconnectTimerSemaphore = true;