Compare commits

..

12 Commits
v1.26 ... main

Author SHA1 Message Date
b87092d4e1 Version 1.6
Changed config message for ESP-NOW sensors.
Changed config message for RF gateway.
Changed config message for nRF24 sensors.
Minor main code optimization.
Changed config data storage location to EEPROM.
Fixed bug with saving config from Web interface.
2023-03-07 11:54:29 +03:00
Alexey Zholtikov
0038361bb5 Version 1.5
Changed SDK version for 3.0.5 for ESP8266.
Changed config message for ESP-NOW switch.
Changed config message for ESP-NOW light/led strip controller.
Fixed minor bugs.
2023-03-04 08:07:32 +03:00
894eed59f1 Version 1.43
Minor changes.
2023-02-19 12:19:54 +03:00
69cb07f721 Minor changes 2023-02-17 10:08:02 +03:00
5fafe9a538 Minor changes 2023-02-16 18:21:08 +03:00
cf32071ee0 Version 1.42
Fixed bug with ESP-NOW devices not getting restart or update command.
Minor main code refactoring.
2023-02-13 19:04:46 +03:00
e4572cc31a Version 1.42
Fixed bug with ESP-NOW devices not getting restart or update command.
Minor main code refactoring.
2023-02-13 18:56:39 +03:00
57d0bc6481 Version 1.42
Fixed bug with ESP-NOW devices not getting restart or update command.
Minor main code refactoring.
2023-02-13 18:53:41 +03:00
1e745724e4 Version 1.41
Minor changes.
2023-02-12 18:37:12 +03:00
25ce7e419a Minor changes 2023-02-12 16:41:54 +03:00
15472ab36b Version 1.4
Added support nRF24 devices via RF Gateway.
2023-02-12 16:38:54 +03:00
7a74c4f2df Version 1.3
Changed library for MQTT connection.
Added support for LAN connection.
Added getting data from NTP server.
2023-02-09 18:22:15 +03:00
6 changed files with 521 additions and 263 deletions

View File

@ -1,38 +1,54 @@
# ESP-NOW gateway for ESP8266/ESP32 # ESP-NOW gateway for ESP8266/ESP32
Gateway for data exchange between ESP-NOW devices and MQTT broker via WiFi. Gateway for data exchange between ESP-NOW devices and MQTT broker via WiFi/LAN.
## Features ## Features
1. Creates an access point named "ESP-NOW gateway XXXXXXXXXXXX" with password "12345678" (IP 192.168.4.1). 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 (at ESP_NOW_WIFI mode).
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), availability status to the ESP-NOW network and to the MQTT broker (every 10 seconds) and current date and time to the ESP-NOW network (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. Automatically adds supported ESP-NOW devices configurations to Home Assistan via MQTT discovery. 5. Automatically adds supported ESP-NOW devices configurations to Home Assistan via MQTT discovery.
6. Possibility firmware update over OTA. 6. Automatically adds supported nRF24 devices configurations to Home Assistan via MQTT discovery.
7. Web interface for settings. 7. Possibility firmware update over OTA (at ESP_NOW_LAN mode via access point only).
8. Web interface for settings (at ESP_NOW_LAN mode via access point only).
9. 3 operating modes:
```text
ESP_NOW ESP-NOW node only. Default mode after flashing.
ESP_NOW_WIFI Gateway between ESP-NOW devices and MQTT broker via WiFi.
ESP_NOW_LAN Gateway between ESP-NOW devices and MQTT broker via Ethernet. Preferred mode.
```
## 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/espnow_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").
4. W5500 connection:
```text
ESP8266 (GPIO05 - CS, GPIO14 - SCK, GPIO12 - MISO, GPIO13 - MOSI).
ESP32 (GPIO05 - CS, GPIO18 - SCK, GPIO19 - MISO, GPIO23 - MOSI).
```
## Attention ## Attention
1. ESP-NOW network name must be set same of all another ESP-NOW devices in network. 1. ESP-NOW network name must be set same of all another ESP-NOW devices in network.
2. If encryption is used, the key must be set same of all another ESP-NOW devices in network. 2. If encryption is used, the key must be set same of all another ESP-NOW devices in network.
3. Upload the "data" folder (with web interface) into the filesystem before flashing. 3. Upload the "data" folder (with web interface) into the filesystem before flashing.
4. WiFi router must be set on channel 1. 4. At ESP_NOW_WIFI mode WiFi router must be set on channel 1.
## Tested on ## Tested on
1. NodeMCU 1.0 (ESP-12E Module). ESP-NOW + WiFi mode. Unstable work. 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. 2. AZ-Delivery ESP-32 Dev Kit C V4. ESP_NOW_WIFI mode. Stable work.
3. NodeMCU 1.0 (ESP-12E Module) + W5500. ESP_NOW_LAN mode. Stable work.
4. AZ-Delivery ESP-32 Dev Kit C V4 + W5500. ESP_NOW_LAN mode. Stable work.
## Supported devices ## Supported devices
1. [RF Gateway](https://github.com/aZholtikov/RF-Gateway) (coming soon) 1. [RF Gateway](https://github.com/aZholtikov/RF-Gateway)
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 Light/Led Strip](https://github.com/aZholtikov/ESP-NOW-Light-Led-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)
@ -41,7 +57,9 @@ Gateway for data exchange between ESP-NOW devices and MQTT broker via WiFi.
## To Do ## To Do
- [X] 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. - [X] LAN connection support.
- [ ] nRF24 device support (in current time uses "RF Gateway"). - [X] nRF24 device support (via [RF Gateway](https://github.com/aZholtikov/RF-Gateway)).
- [ ] BLE device support (for ESP32). - [ ] BLE device support (via BLE Gateway).
- [ ] LoRa device support. - [ ] LoRa device support (via LoRa Gateway).
Any feedback via [e-mail](mailto:github@zh.com.ru) would be appreciated. Or... [Buy me a coffee](https://paypal.me/aZholtikov).

View File

@ -10,7 +10,7 @@ function createXmlHttpObject() {
function load() { function load() {
if (xmlHttp.readyState == 0 || xmlHttp.readyState == 4) { if (xmlHttp.readyState == 0 || xmlHttp.readyState == 4) {
xmlHttp.open('PUT', '/config.json', true); xmlHttp.open('GET', '/config', true);
xmlHttp.send(null); xmlHttp.send(null);
xmlHttp.onload = function () { xmlHttp.onload = function () {
jsonResponse = JSON.parse(xmlHttp.responseText); jsonResponse = JSON.parse(xmlHttp.responseText);
@ -29,6 +29,7 @@ function loadBlock() {
} }
document.getElementsByTagName('body')[0].innerHTML = newString; document.getElementsByTagName('body')[0].innerHTML = newString;
setFirmvareValue('version', 'firmware'); setFirmvareValue('version', 'firmware');
setGpioValue('workModeSelect', 'workMode');
handleServerResponse(); handleServerResponse();
} }
@ -37,6 +38,12 @@ function getValue(id) {
return value; return value;
} }
function getSelectValue(id) {
var select = document.getElementById(id);
var value = select.value;
return value;
}
function sendRequest(submit, server) { function sendRequest(submit, server) {
request = new XMLHttpRequest(); request = new XMLHttpRequest();
request.open("GET", server, true); request.open("GET", server, true);
@ -45,11 +52,14 @@ function sendRequest(submit, server) {
function saveSetting(submit) { function saveSetting(submit) {
server = "/setting?ssid=" + getValue('ssid') + "&password=" + encodeURIComponent(getValue('password')) server = "/setting?ssid=" + getValue('ssid') + "&password=" + encodeURIComponent(getValue('password'))
+ "&host=" + getValue('mqttHostName') + "&port=" + getValue('mqttHostPort') + "&mqttHostName=" + getValue('mqttHostName') + "&mqttHostPort=" + getValue('mqttHostPort')
+ "&login=" + getValue('mqttUserLogin') + "&pass=" + encodeURIComponent(getValue('mqttUserPassword')) + "&mqttUserLogin=" + getValue('mqttUserLogin') + "&mqttUserPassword=" + encodeURIComponent(getValue('mqttUserPassword'))
+ "&prefix=" + getValue('topicPrefix') + "&topicPrefix=" + getValue('topicPrefix')
+ "&name=" + getValue('deviceName') + "&deviceName=" + getValue('deviceName')
+ "&net=" + getValue('espnowNetName'); + "&espnowNetName=" + getValue('espnowNetName')
+ "&workMode=" + getSelectValue('workModeSelect')
+ "&ntpHostName=" + getValue('ntpHostName')
+ "&gmtOffset=" + getValue('gmtOffset');
sendRequest(submit, server); sendRequest(submit, server);
alert("Please restart device for changes apply."); alert("Please restart device for changes apply.");
} }
@ -62,3 +72,8 @@ function restart(submit) {
function setFirmvareValue(id, value) { function setFirmvareValue(id, value) {
document.getElementById(id).innerHTML = document.getElementById(value).value; document.getElementById(id).innerHTML = document.getElementById(value).value;
} }
function setGpioValue(id, value) {
var select = document.getElementById(id);
select.value = document.getElementById(value).value;
}

View File

@ -10,7 +10,7 @@
<body onload="load();"> <body onload="load();">
<form class="box"> <form class="box">
<h1>ESP-NOW Gateway </h1> <h1>ESP-NOW Gateway</h1>
<div class="wrapper"> <div class="wrapper">
<p class="text">Firmware:</p> <p class="text">Firmware:</p>
<p class="text" id="version"></p> <p class="text" id="version"></p>
@ -29,6 +29,16 @@
title="ESP-NOW network name (1 to 20 characters)" /> title="ESP-NOW network name (1 to 20 characters)" />
</div> </div>
<div class="wrapper">
<p class="text-select">Work mode:</p>
<input id="workMode" value="{{workMode}}" hidden />
<p><select id="workModeSelect">
<option value="0">ESP-NOW</option>
<option value="1">ESP-NOW WIFI</option>
<option value="2">ESP-NOW LAN</option>
</select></p>
</div>
<p class="text">WiFi settings</p> <p class="text">WiFi settings</p>
<div class="wrapper"> <div class="wrapper">
<input class="text-inp" id="ssid" value="{{ssid}}" placeholder="SSID" label title="WiFi network name" /> <input class="text-inp" id="ssid" value="{{ssid}}" placeholder="SSID" label title="WiFi network name" />
@ -36,6 +46,13 @@
autocomplete="off" label title="WiFi password" /> autocomplete="off" label title="WiFi password" />
</div> </div>
<p class="text">NTP settings</p>
<div class="wrapper">
<input class="text-inp" id="ntpHostName" value="{{ntpHostName}}" placeholder="URL or IP" label
title="NTP server URL or IP" />
<input id="gmtOffset" value="{{gmtOffset}}" placeholder="Time zone" label title="Time zone" />
</div>
<p class="text">MQTT settings</p> <p class="text">MQTT settings</p>
<div class="wrapper"> <div class="wrapper">
<input class="text-inp" id="mqttHostName" value="{{mqttHostName}}" placeholder="URL or IP" label <input class="text-inp" id="mqttHostName" value="{{mqttHostName}}" placeholder="URL or IP" label

View File

@ -29,6 +29,13 @@ h1 {
margin: 10px 0; margin: 10px 0;
} }
.text-select {
width: 35%;
font-weight: 600;
flex-shrink: 0;
margin-right: 10px;
}
.text-inp { .text-inp {
width: 48%; width: 48%;
min-height: 30px; min-height: 30px;
@ -61,11 +68,28 @@ input {
margin-left: 10px; margin-left: 10px;
} }
select {
width: 140px;
min-height: 30px;
border-radius: 5px;
border: none;
margin-bottom: 10px;
padding: 0 10px;
color: rgb(0, 0, 0);
background: #a3e0f1;
transition: .5s;
}
input:hover { input:hover {
background: white; background: white;
cursor: pointer; cursor: pointer;
} }
select:hover {
background: white;
cursor: pointer;
}
.btn { .btn {
width: 48%; width: 48%;
background: rgb(65, 125, 238); background: rgb(65, 125, 238);
@ -88,7 +112,7 @@ input:hover {
} }
#espnowNetName { #espnowNetName {
margin-bottom: 10px; margin-bottom: 15px;
} }
.wrapper.wrapper--end { .wrapper.wrapper--end {

View File

@ -1,32 +1,38 @@
[env:ESP8266] [env:ESP8266]
platform = espressif8266 platform = espressif8266
board = nodemcuv2 board = esp12e
framework = arduino framework = arduino
build_flags = -D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305
board_build.filesystem = littlefs board_build.filesystem = littlefs
lib_deps = lib_deps =
https://github.com/aZholtikov/ZHNetwork https://github.com/aZholtikov/ZHNetwork
https://github.com/aZholtikov/ZHConfig https://github.com/aZholtikov/ZHConfig
https://github.com/aZholtikov/Async-Web-Server https://github.com/aZholtikov/Async-Web-Server
https://github.com/bblanchon/ArduinoJson https://github.com/bblanchon/ArduinoJson
https://github.com/marvinroger/async-mqtt-client https://github.com/arduino-libraries/Ethernet
https://github.com/knolleary/pubsubclient
https://github.com/arduino-libraries/NTPClient
[env:ESP8266-OTA] [env:ESP8266-OTA]
platform = espressif8266 platform = espressif8266
board = nodemcuv2 board = esp12e
framework = arduino framework = arduino
build_flags = -D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305
board_build.filesystem = littlefs board_build.filesystem = littlefs
upload_port = 192.168.1.113 upload_port = 192.168.4.1
upload_protocol = espota upload_protocol = espota
lib_deps = lib_deps =
https://github.com/aZholtikov/ZHNetwork https://github.com/aZholtikov/ZHNetwork
https://github.com/aZholtikov/ZHConfig https://github.com/aZholtikov/ZHConfig
https://github.com/aZholtikov/Async-Web-Server https://github.com/aZholtikov/Async-Web-Server
https://github.com/bblanchon/ArduinoJson https://github.com/bblanchon/ArduinoJson
https://github.com/marvinroger/async-mqtt-client https://github.com/arduino-libraries/Ethernet
https://github.com/knolleary/pubsubclient
https://github.com/arduino-libraries/NTPClient
[env:ESP32] [env:ESP32]
platform = espressif32 platform = espressif32
board = az-delivery-devkit-v4 board = esp32dev
framework = arduino framework = arduino
board_build.filesystem = littlefs board_build.filesystem = littlefs
lib_deps = lib_deps =
@ -34,20 +40,24 @@ lib_deps =
https://github.com/aZholtikov/ZHConfig https://github.com/aZholtikov/ZHConfig
https://github.com/aZholtikov/Async-Web-Server https://github.com/aZholtikov/Async-Web-Server
https://github.com/bblanchon/ArduinoJson https://github.com/bblanchon/ArduinoJson
https://github.com/marvinroger/async-mqtt-client https://github.com/arduino-libraries/Ethernet
https://github.com/knolleary/pubsubclient
https://github.com/arduino-libraries/NTPClient
https://github.com/luc-github/ESP32SSDP https://github.com/luc-github/ESP32SSDP
[env:ESP32-OTA] [env:ESP32-OTA]
platform = espressif32 platform = espressif32
board = az-delivery-devkit-v4 board = esp32dev
framework = arduino framework = arduino
board_build.filesystem = littlefs board_build.filesystem = littlefs
upload_port = 192.168.1.110 upload_port = 192.168.4.1
upload_protocol = espota upload_protocol = espota
lib_deps = lib_deps =
https://github.com/aZholtikov/ZHNetwork https://github.com/aZholtikov/ZHNetwork
https://github.com/aZholtikov/ZHConfig https://github.com/aZholtikov/ZHConfig
https://github.com/aZholtikov/Async-Web-Server https://github.com/aZholtikov/Async-Web-Server
https://github.com/bblanchon/ArduinoJson https://github.com/bblanchon/ArduinoJson
https://github.com/marvinroger/async-mqtt-client https://github.com/arduino-libraries/Ethernet
https://github.com/luc-github/ESP32SSDP https://github.com/knolleary/pubsubclient
https://github.com/arduino-libraries/NTPClient
https://github.com/luc-github/ESP32SSDP

View File

@ -1,9 +1,12 @@
#include "ArduinoJson.h" #include "ArduinoJson.h"
#include "ArduinoOTA.h" #include "ArduinoOTA.h"
#include "ESPAsyncWebServer.h" // https://github.com/aZholtikov/Async-Web-Server #include "ESPAsyncWebServer.h" // https://github.com/aZholtikov/Async-Web-Server
#include "AsyncMQTTClient.h" #include "Ethernet.h" // https://github.com/arduino-libraries/Ethernet
#include "PubSubClient.h"
#include "LittleFS.h" #include "LittleFS.h"
#include "EEPROM.h"
#include "Ticker.h" #include "Ticker.h"
#include "NTPClient.h"
#include "ZHNetwork.h" #include "ZHNetwork.h"
#include "ZHConfig.h" #include "ZHConfig.h"
#if defined(ESP8266) #if defined(ESP8266)
@ -13,16 +16,13 @@
#include "ESP32SSDP.h" #include "ESP32SSDP.h"
#endif #endif
void onWifiEvent(WiFiEvent_t event);
void onEspnowMessage(const char *data, const uint8_t *sender); void onEspnowMessage(const char *data, const uint8_t *sender);
void onMqttConnect(bool sessionPresent); void onMqttMessage(char *topic, byte *payload, unsigned int length);
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason);
void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total);
void sendKeepAliveMessage(void); void sendKeepAliveMessage(void);
void sendAttributesMessage(void); void sendAttributesMessage(void);
void sendConfigMessage(void);
String getValue(String data, char separator, uint8_t index); String getValue(String data, char separator, uint8_t index);
@ -32,68 +32,110 @@ void saveConfig(void);
String xmlNode(String tags, String data); String xmlNode(String tags, String data);
void setupWebServer(void); void setupWebServer(void);
void connectToMqtt(void); void checkMqttAvailability(void);
const String firmware{"1.26"}; void mqttPublish(const char *topic, const char *payload, bool retained);
String espnowNetName{"DEFAULT"}; typedef enum : uint8_t
{
ESP_NOW,
ESP_NOW_WIFI,
ESP_NOW_LAN
} work_mode_t;
struct deviceConfig
{
#if defined(ESP8266) #if defined(ESP8266)
String deviceName = "ESP-NOW gateway " + String(ESP.getChipId(), HEX); String deviceName = "ESP-NOW gateway " + String(ESP.getChipId(), HEX);
#endif #endif
#if defined(ESP32) #if defined(ESP32)
String deviceName = "ESP-NOW gateway " + String(ESP.getEfuseMac(), HEX); String deviceName = "ESP-NOW gateway " + String(ESP.getEfuseMac(), HEX);
#endif #endif
String espnowNetName{"DEFAULT"};
uint8_t workMode{ESP_NOW};
String ssid{"SSID"};
String password{"PASSWORD"};
String mqttHostName{"MQTT"};
uint16_t mqttHostPort{1883};
String mqttUserLogin{""};
String mqttUserPassword{""};
String topicPrefix{"homeassistant"};
String ntpHostName{"NTP"};
uint16_t gmtOffset{10800};
} config;
String ssid{"SSID"}; const String firmware{"1.6"};
String password{"PASSWORD"};
String mqttHostName{"MQTT"}; const char *mqttUserID{"ESP"};
uint16_t mqttHostPort{1883};
String mqttUserLogin{""}; uint8_t w5500Mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; // Change it if necessary.
String mqttUserPassword{""};
String topicPrefix{"homeassistant"};
ZHNetwork myNet; ZHNetwork myNet;
AsyncWebServer webServer(80); AsyncWebServer webServer(80);
AsyncMqttClient mqttClient;
Ticker mqttReconnectTimer; EthernetClient ethClient;
bool mqttReconnectTimerSemaphore{false}; WiFiClient wifiClient;
void mqttReconnectTimerCallback(void);
PubSubClient mqttEthClient(ethClient);
PubSubClient mqttWifiClient(wifiClient);
WiFiUDP udpWiFiClient;
EthernetUDP udpEthClient;
NTPClient ntpWiFiClient(udpWiFiClient, config.ntpHostName.c_str(), config.gmtOffset);
NTPClient ntpEthClient(udpEthClient, config.ntpHostName.c_str(), config.gmtOffset);
Ticker mqttAvailabilityCheckTimer;
bool mqttAvailabilityCheckTimerSemaphore{true};
bool isMqttAvailable{false};
void mqttAvailabilityCheckTimerCallback(void);
Ticker keepAliveMessageTimer; Ticker keepAliveMessageTimer;
bool keepAliveMessageTimerSemaphore{true}; bool keepAliveMessageTimerSemaphore{true};
void keepAliveMessageTimerCallback(void); void keepAliveMessageTimerCallback(void);
Ticker attributesMessageTimer; Ticker attributesMessageTimer;
bool attributesMessageTimerSemaphore{false}; bool attributesMessageTimerSemaphore{true};
void attributesMessageTimerCallback(void); void attributesMessageTimerCallback(void);
void setup() void setup()
{ {
#if defined(ESP8266)
LittleFS.begin(); LittleFS.begin();
#endif
#if defined(ESP32)
LittleFS.begin(true);
#endif
loadConfig(); loadConfig();
WiFi.onEvent(onWifiEvent); if (config.workMode == ESP_NOW_LAN)
{
Ethernet.init(5);
Ethernet.begin(w5500Mac);
}
if (config.workMode == ESP_NOW_WIFI)
{
#if defined(ESP8266) #if defined(ESP8266)
WiFi.setSleepMode(WIFI_NONE_SLEEP); WiFi.setSleepMode(WIFI_NONE_SLEEP);
#endif #endif
#if defined(ESP32) #if defined(ESP32)
WiFi.setSleep(WIFI_PS_NONE); WiFi.setSleep(WIFI_PS_NONE);
#endif #endif
WiFi.persistent(false);
WiFi.setAutoConnect(false);
WiFi.setAutoReconnect(true);
}
WiFi.persistent(false); myNet.begin(config.espnowNetName.c_str(), true);
WiFi.setAutoConnect(false);
WiFi.setAutoReconnect(true);
myNet.begin(espnowNetName.c_str(), true); if (config.workMode)
// myNet.setCryptKey("VERY_LONG_CRYPT_KEY"); // If encryption is used, the key must be set same of all another ESP-NOW devices in network. {
// myNet.setCryptKey("VERY_LONG_CRYPT_KEY"); // If encryption is used, the key must be set same of all another ESP-NOW devices in network.
myNet.setOnBroadcastReceivingCallback(onEspnowMessage); myNet.setOnBroadcastReceivingCallback(onEspnowMessage);
myNet.setOnUnicastReceivingCallback(onEspnowMessage); myNet.setOnUnicastReceivingCallback(onEspnowMessage);
}
#if defined(ESP8266) #if defined(ESP8266)
WiFi.softAP(("ESP-NOW gateway " + String(ESP.getChipId(), HEX)).c_str(), "12345678"); WiFi.softAP(("ESP-NOW gateway " + String(ESP.getChipId(), HEX)).c_str(), "12345678");
@ -102,255 +144,295 @@ void setup()
WiFi.softAP(("ESP-NOW gateway " + String(ESP.getEfuseMac(), HEX)).c_str(), "12345678"); WiFi.softAP(("ESP-NOW gateway " + String(ESP.getEfuseMac(), HEX)).c_str(), "12345678");
#endif #endif
uint8_t scan = WiFi.scanNetworks(false, false, 1); if (config.workMode == ESP_NOW_WIFI)
String name;
int32_t rssi;
uint8_t encryption;
uint8_t *bssid;
int32_t channel;
bool hidden;
for (int8_t i = 0; i < scan; i++)
{ {
uint8_t scan = WiFi.scanNetworks(false, false);
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) #if defined(ESP8266)
WiFi.getNetworkInfo(i, name, encryption, rssi, bssid, channel, hidden); WiFi.getNetworkInfo(i, name, encryption, rssi, bssid, channel, hidden);
#endif #endif
#if defined(ESP32) #if defined(ESP32)
WiFi.getNetworkInfo(i, name, encryption, rssi, bssid, channel); WiFi.getNetworkInfo(i, name, encryption, rssi, bssid, channel);
#endif #endif
if (name == ssid) if (name == config.ssid)
WiFi.begin(ssid.c_str(), password.c_str()); WiFi.begin(config.ssid.c_str(), config.password.c_str());
}
} }
mqttClient.onConnect(onMqttConnect); if (config.workMode == ESP_NOW_WIFI)
mqttClient.onDisconnect(onMqttDisconnect); {
mqttClient.onMessage(onMqttMessage); ntpWiFiClient.begin();
mqttClient.setServer(mqttHostName.c_str(), mqttHostPort); mqttWifiClient.setBufferSize(2048);
mqttClient.setCredentials(mqttUserLogin.c_str(), mqttUserPassword.c_str()); mqttWifiClient.setServer(config.mqttHostName.c_str(), config.mqttHostPort);
mqttWifiClient.setCallback(onMqttMessage);
}
if (config.workMode == ESP_NOW_LAN)
{
ntpEthClient.begin();
mqttEthClient.setBufferSize(2048);
mqttEthClient.setServer(config.mqttHostName.c_str(), config.mqttHostPort);
mqttEthClient.setCallback(onMqttMessage);
}
setupWebServer(); setupWebServer();
ArduinoOTA.begin(); ArduinoOTA.begin();
keepAliveMessageTimer.attach(10, keepAliveMessageTimerCallback); keepAliveMessageTimer.attach(10, keepAliveMessageTimerCallback);
mqttAvailabilityCheckTimer.attach(5, mqttAvailabilityCheckTimerCallback);
attributesMessageTimer.attach(60, attributesMessageTimerCallback);
} }
void loop() void loop()
{ {
if (mqttReconnectTimerSemaphore) if (mqttAvailabilityCheckTimerSemaphore)
connectToMqtt(); checkMqttAvailability();
if (keepAliveMessageTimerSemaphore) if (keepAliveMessageTimerSemaphore)
sendKeepAliveMessage(); sendKeepAliveMessage();
if (attributesMessageTimerSemaphore) if (attributesMessageTimerSemaphore)
sendAttributesMessage(); sendAttributesMessage();
if (config.workMode == ESP_NOW_WIFI)
mqttWifiClient.loop();
if (config.workMode == ESP_NOW_LAN)
mqttEthClient.loop();
myNet.maintenance(); myNet.maintenance();
ArduinoOTA.handle(); ArduinoOTA.handle();
} }
void onWifiEvent(WiFiEvent_t event)
{
#if defined(ESP8266)
if (event == WIFI_EVENT_STAMODE_GOT_IP)
#endif
#if defined(ESP32)
if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP)
#endif
mqttClient.connect();
}
void onEspnowMessage(const char *data, const uint8_t *sender) void onEspnowMessage(const char *data, const uint8_t *sender)
{ {
if (!mqttClient.connected()) if (!isMqttAvailable)
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));
if (incomingData.payloadsType == ENPT_ATTRIBUTES) if (incomingData.payloadsType == ENPT_ATTRIBUTES)
mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, incomingData.message); mqttPublish((config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), incomingData.message, true);
if (incomingData.payloadsType == ENPT_KEEP_ALIVE) if (incomingData.payloadsType == ENPT_KEEP_ALIVE)
mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, "online"); mqttPublish((config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), "online", true);
if (incomingData.payloadsType == ENPT_STATE) if (incomingData.payloadsType == ENPT_STATE)
mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, incomingData.message); mqttPublish((config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), incomingData.message, true);
if (incomingData.payloadsType == ENPT_CONFIG) if (incomingData.payloadsType == ENPT_CONFIG)
{ {
if (incomingData.deviceType == ENDT_SWITCH) if (incomingData.deviceType == ENDT_SWITCH)
{ {
esp_now_payload_data_t configData; esp_now_payload_data_t configData;
memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message)); memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json; DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message));
deserializeJson(json, configData.message); deserializeJson(json, configData.message);
uint8_t unit = json["unit"].as<uint8_t>(); uint8_t unit = json[MCMT_DEVICE_UNIT].as<uint8_t>();
ha_component_type_t type = json["type"].as<ha_component_type_t>(); DynamicJsonDocument jsonConfig(2048); // Same as PubSubClient buffer size.
StaticJsonDocument<2048> jsonConfig;
jsonConfig["platform"] = "mqtt"; jsonConfig["platform"] = "mqtt";
jsonConfig["name"] = json["name"]; jsonConfig["name"] = json[MCMT_DEVICE_NAME];
jsonConfig["unique_id"] = myNet.macToString(sender) + "-" + unit; jsonConfig["unique_id"] = myNet.macToString(sender) + "-" + unit;
jsonConfig["device_class"] = getValueName(json["class"].as<ha_switch_device_class_t>()); jsonConfig["device_class"] = getValueName(json[MCMT_DEVICE_CLASS].as<ha_switch_device_class_t>());
jsonConfig["state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state"; jsonConfig["state_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
jsonConfig["value_template"] = "{{ value_json." + json["template"].as<String>() + " }}"; jsonConfig["value_template"] = "{{ value_json." + json[MCMT_VALUE_TEMPLATE].as<String>() + " }}";
jsonConfig["command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/set"; jsonConfig["command_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/set";
jsonConfig["json_attributes_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/attributes"; jsonConfig["json_attributes_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/attributes";
jsonConfig["availability_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/status"; jsonConfig["availability_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/status";
jsonConfig["payload_on"] = json["payload_on"]; if (json[MCMT_PAYLOAD_ON])
jsonConfig["payload_off"] = json["payload_off"]; jsonConfig["payload_on"] = json[MCMT_PAYLOAD_ON];
if (json[MCMT_PAYLOAD_OFF])
jsonConfig["payload_off"] = json[MCMT_PAYLOAD_OFF];
jsonConfig["optimistic"] = "false"; jsonConfig["optimistic"] = "false";
jsonConfig["qos"] = 2;
jsonConfig["retain"] = "true"; jsonConfig["retain"] = "true";
char buffer[2048]{0}; char buffer[2048]{0};
serializeJsonPretty(jsonConfig, buffer); serializeJsonPretty(jsonConfig, buffer);
mqttClient.publish((topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), 2, true, buffer); mqttPublish((config.topicPrefix + "/" + getValueName(json[MCMT_COMPONENT_TYPE].as<ha_component_type_t>()) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), buffer, true);
} }
if (incomingData.deviceType == ENDT_LED) if (incomingData.deviceType == ENDT_LED)
{ {
esp_now_payload_data_t configData; esp_now_payload_data_t configData;
memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message)); memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json; DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message));
deserializeJson(json, configData.message); deserializeJson(json, configData.message);
uint8_t unit = json["unit"].as<uint8_t>(); uint8_t unit = json[MCMT_DEVICE_UNIT].as<uint8_t>();
ha_component_type_t type = json["type"].as<ha_component_type_t>(); esp_now_led_type_t ledClass = json[MCMT_DEVICE_CLASS];
esp_now_led_type_t ledClass = json["class"]; DynamicJsonDocument jsonConfig(2048); // Same as PubSubClient buffer size.
StaticJsonDocument<2048> jsonConfig;
jsonConfig["platform"] = "mqtt"; jsonConfig["platform"] = "mqtt";
jsonConfig["name"] = json["name"]; jsonConfig["name"] = json[MCMT_DEVICE_NAME];
jsonConfig["unique_id"] = myNet.macToString(sender) + "-" + unit; jsonConfig["unique_id"] = myNet.macToString(sender) + "-" + unit;
jsonConfig["state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state"; jsonConfig["state_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
jsonConfig["state_value_template"] = "{{ value_json.state }}"; jsonConfig["state_value_template"] = "{{ value_json.state }}";
jsonConfig["command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/set"; jsonConfig["command_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/set";
jsonConfig["brightness_state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state"; jsonConfig["brightness_state_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
jsonConfig["brightness_value_template"] = "{{ value_json.brightness }}"; jsonConfig["brightness_value_template"] = "{{ value_json.brightness }}";
jsonConfig["brightness_command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/brightness"; jsonConfig["brightness_command_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/brightness";
if (ledClass == ENLT_RGB || ledClass == ENLT_RGBW || ledClass == ENLT_RGBWW) if (ledClass == ENLT_RGB || ledClass == ENLT_RGBW || ledClass == ENLT_RGBWW)
{ {
jsonConfig["rgb_state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state"; jsonConfig["rgb_state_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
jsonConfig["rgb_value_template"] = "{{ value_json.rgb | join(',') }}"; jsonConfig["rgb_value_template"] = "{{ value_json.rgb | join(',') }}";
jsonConfig["rgb_command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/rgb"; jsonConfig["rgb_command_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/rgb";
} }
if (ledClass == ENLT_WW || ledClass == ENLT_RGBWW) if (ledClass == ENLT_WW || ledClass == ENLT_RGBWW)
{ {
jsonConfig["color_temp_state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state"; jsonConfig["color_temp_state_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
jsonConfig["color_temp_value_template"] = "{{ value_json.temperature }}"; jsonConfig["color_temp_value_template"] = "{{ value_json.temperature }}";
jsonConfig["color_temp_command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/temperature"; jsonConfig["color_temp_command_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/temperature";
} }
jsonConfig["json_attributes_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/attributes"; jsonConfig["json_attributes_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/attributes";
jsonConfig["availability_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/status"; jsonConfig["availability_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/status";
jsonConfig["payload_on"] = json["payload_on"]; if (json[MCMT_PAYLOAD_ON])
jsonConfig["payload_off"] = json["payload_off"]; jsonConfig["payload_on"] = json[MCMT_PAYLOAD_ON];
if (json[MCMT_PAYLOAD_OFF])
jsonConfig["payload_off"] = json[MCMT_PAYLOAD_OFF];
jsonConfig["optimistic"] = "false"; jsonConfig["optimistic"] = "false";
jsonConfig["qos"] = 2;
jsonConfig["retain"] = "true"; jsonConfig["retain"] = "true";
char buffer[2048]{0}; char buffer[2048]{0};
serializeJsonPretty(jsonConfig, buffer); serializeJsonPretty(jsonConfig, buffer);
mqttClient.publish((topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), 2, true, buffer); mqttPublish((config.topicPrefix + "/" + getValueName(json[MCMT_COMPONENT_TYPE].as<ha_component_type_t>()) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), buffer, true);
} }
if (incomingData.deviceType == ENDT_SENSOR) if (incomingData.deviceType == ENDT_SENSOR)
{ {
esp_now_payload_data_t configData; esp_now_payload_data_t configData;
memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message)); memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json; DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message));
deserializeJson(json, configData.message); deserializeJson(json, configData.message);
uint8_t unit = json["unit"].as<uint8_t>(); uint8_t unit = json[MCMT_DEVICE_UNIT].as<uint8_t>();
ha_component_type_t type = json["type"].as<ha_component_type_t>(); ha_component_type_t type = json[MCMT_COMPONENT_TYPE].as<ha_component_type_t>();
StaticJsonDocument<2048> jsonConfig; DynamicJsonDocument jsonConfig(2048); // Same as PubSubClient buffer size.
jsonConfig["platform"] = "mqtt"; jsonConfig["platform"] = "mqtt";
jsonConfig["name"] = json["name"]; jsonConfig["name"] = json[MCMT_DEVICE_NAME];
jsonConfig["unique_id"] = myNet.macToString(sender) + "-" + unit; jsonConfig["unique_id"] = myNet.macToString(sender) + "-" + unit;
jsonConfig["state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state"; jsonConfig["state_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
jsonConfig["value_template"] = "{{ value_json." + json["template"].as<String>() + " }}"; jsonConfig["value_template"] = "{{ value_json." + json[MCMT_VALUE_TEMPLATE].as<String>() + " }}";
jsonConfig["json_attributes_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/attributes"; jsonConfig["json_attributes_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/attributes";
jsonConfig["force_update"] = "true"; jsonConfig["force_update"] = "true";
jsonConfig["qos"] = 2;
jsonConfig["retain"] = "true"; jsonConfig["retain"] = "true";
if (type == HACT_SENSOR) if (type == HACT_SENSOR)
jsonConfig["device_class"] = getValueName(json["class"].as<ha_sensor_device_class_t>());
if (type == HACT_BINARY_SENSOR)
{ {
jsonConfig["device_class"] = getValueName(json["class"].as<ha_binary_sensor_device_class_t>()); jsonConfig["device_class"] = getValueName(json[MCMT_DEVICE_CLASS].as<ha_sensor_device_class_t>());
jsonConfig["payload_on"] = json["payload_on"]; jsonConfig["unit_of_measurement"] = json[MCMT_UNIT_OF_MEASUREMENT];
jsonConfig["payload_off"] = json["payload_off"];
} }
if (type == HACT_BINARY_SENSOR)
jsonConfig["device_class"] = getValueName(json[MCMT_DEVICE_CLASS].as<ha_binary_sensor_device_class_t>());
if (json[MCMT_EXPIRE_AFTER])
jsonConfig["expire_after"] = json[MCMT_EXPIRE_AFTER];
if (json[MCMT_OFF_DELAY])
jsonConfig["off_delay"] = json[MCMT_OFF_DELAY];
if (json[MCMT_PAYLOAD_ON])
jsonConfig["payload_on"] = json[MCMT_PAYLOAD_ON];
if (json[MCMT_PAYLOAD_OFF])
jsonConfig["payload_off"] = json[MCMT_PAYLOAD_OFF];
char buffer[2048]{0}; char buffer[2048]{0};
serializeJsonPretty(jsonConfig, buffer); serializeJsonPretty(jsonConfig, buffer);
mqttClient.publish((topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), 2, true, buffer); mqttPublish((config.topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), buffer, true);
}
if (incomingData.deviceType == ENDT_RF_SENSOR)
{
esp_now_payload_data_t configData;
memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message));
deserializeJson(json, configData.message);
uint8_t unit = json[MCMT_DEVICE_UNIT].as<uint8_t>();
ha_component_type_t haComponentType = json[MCMT_COMPONENT_TYPE].as<ha_component_type_t>();
rf_sensor_type_t rfSensorType = json[MCMT_RF_SENSOR_TYPE].as<rf_sensor_type_t>();
uint16_t rfSensorId = json[MCMT_RF_SENSOR_ID].as<uint16_t>();
String valueTemplate = json[MCMT_VALUE_TEMPLATE].as<String>();
DynamicJsonDocument jsonConfig(2048); // Same as PubSubClient buffer size.
jsonConfig["platform"] = "mqtt";
jsonConfig["name"] = getValueName(rfSensorType) + " " + rfSensorId + " " + valueTemplate;
jsonConfig["unique_id"] = String(rfSensorId) + "-" + unit;
jsonConfig["state_topic"] = config.topicPrefix + "/rf_sensor/" + getValueName(rfSensorType) + "/" + rfSensorId + "/state";
jsonConfig["value_template"] = "{{ value_json." + valueTemplate + " }}";
jsonConfig["force_update"] = "true";
jsonConfig["retain"] = "true";
if (haComponentType == HACT_SENSOR)
{
jsonConfig["device_class"] = getValueName(json[MCMT_DEVICE_CLASS].as<ha_sensor_device_class_t>());
jsonConfig["unit_of_measurement"] = json[MCMT_UNIT_OF_MEASUREMENT];
}
if (haComponentType == HACT_BINARY_SENSOR)
jsonConfig["device_class"] = getValueName(json[MCMT_DEVICE_CLASS].as<ha_binary_sensor_device_class_t>());
if (json[MCMT_EXPIRE_AFTER])
jsonConfig["expire_after"] = json[MCMT_EXPIRE_AFTER];
if (json[MCMT_OFF_DELAY])
jsonConfig["off_delay"] = json[MCMT_OFF_DELAY];
if (json[MCMT_PAYLOAD_ON])
jsonConfig["payload_on"] = json[MCMT_PAYLOAD_ON];
if (json[MCMT_PAYLOAD_OFF])
jsonConfig["payload_off"] = json[MCMT_PAYLOAD_OFF];
char buffer[2048]{0};
serializeJsonPretty(jsonConfig, buffer);
mqttPublish((config.topicPrefix + "/" + getValueName(haComponentType) + "/" + rfSensorId + "-" + unit + "/config").c_str(), buffer, true);
}
if (incomingData.deviceType == ENDT_RF_GATEWAY)
{
esp_now_payload_data_t configData;
memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message));
deserializeJson(json, configData.message);
uint8_t unit = json[MCMT_DEVICE_UNIT].as<uint8_t>();
DynamicJsonDocument jsonConfig(2048); // Same as PubSubClient buffer size.
jsonConfig["platform"] = "mqtt";
jsonConfig["name"] = json[MCMT_DEVICE_NAME];
jsonConfig["unique_id"] = myNet.macToString(sender) + "-" + unit;
jsonConfig["device_class"] = getValueName(json[MCMT_DEVICE_CLASS].as<ha_binary_sensor_device_class_t>());
jsonConfig["state_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/status";
jsonConfig["json_attributes_topic"] = config.topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/attributes";
jsonConfig["payload_on"] = json[MCMT_PAYLOAD_ON];
jsonConfig["expire_after"] = json[MCMT_EXPIRE_AFTER];
jsonConfig["force_update"] = "true";
jsonConfig["retain"] = "true";
char buffer[2048]{0};
serializeJsonPretty(jsonConfig, buffer);
mqttPublish((config.topicPrefix + "/" + getValueName(json[MCMT_COMPONENT_TYPE].as<ha_component_type_t>()) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), buffer, true);
} }
} }
if (incomingData.payloadsType == ENPT_FORWARD) if (incomingData.payloadsType == ENPT_FORWARD)
{ {
esp_now_payload_data_t forwardData; esp_now_payload_data_t forwardData;
memcpy(&forwardData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message)); memcpy(&forwardData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json; DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message));
deserializeJson(json, forwardData.message); 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); if (incomingData.deviceType == ENDT_RF_GATEWAY)
mqttPublish((config.topicPrefix + "/rf_sensor/" + getValueName(json["type"].as<rf_sensor_type_t>()) + "/" + json["id"].as<uint16_t>() + "/state").c_str(), incomingData.message, false);
} }
} }
void onMqttConnect(bool sessionPresent) void onMqttMessage(char *topic, byte *payload, unsigned int length)
{
mqttClient.subscribe((topicPrefix + "/espnow_gateway/#").c_str(), 2);
mqttClient.subscribe((topicPrefix + "/espnow_switch/#").c_str(), 2);
mqttClient.subscribe((topicPrefix + "/espnow_led/#").c_str(), 2);
StaticJsonDocument<1024> json;
json["platform"] = "mqtt";
json["name"] = deviceName;
json["unique_id"] = myNet.getNodeMac() + "-1";
json["device_class"] = "connectivity";
json["state_topic"] = topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/status";
json["json_attributes_topic"] = topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/attributes";
json["payload_on"] = "online";
json["expire_after"] = 30;
json["force_update"] = "true";
json["qos"] = 2;
json["retain"] = "true";
char buffer[1024]{0};
serializeJsonPretty(json, buffer);
mqttClient.publish((topicPrefix + "/binary_sensor/" + myNet.getNodeMac() + "-1" + "/config").c_str(), 2, true, buffer);
sendKeepAliveMessage();
sendAttributesMessage();
attributesMessageTimer.attach(60, attributesMessageTimerCallback);
}
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
{
mqttReconnectTimer.once(5, mqttReconnectTimerCallback);
sendKeepAliveMessage();
attributesMessageTimer.detach();
}
void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
{ {
String mac = getValue(String(topic).substring(0, String(topic).length()), '/', 2); String mac = getValue(String(topic).substring(0, String(topic).length()), '/', 2);
String message; String message;
bool flag{false}; bool flag{false};
for (uint16_t i = 0; i < len; ++i) for (uint16_t i = 0; i < length; ++i)
{ {
message += (char)payload[i]; message += (char)payload[i];
} }
esp_now_payload_data_t outgoingData; esp_now_payload_data_t outgoingData;
outgoingData.deviceType = ENDT_GATEWAY; outgoingData.deviceType = ENDT_GATEWAY;
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json; outgoingData.payloadsType = ENPT_SET;
DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message));
if (message == "update" || message == "restart") if (message == "update" || message == "restart")
{ {
mqttClient.publish(topic, 2, true, "");
mqttClient.publish((String(topic) + "/status").c_str(), 2, true, "offline");
if (mac == myNet.getNodeMac() && message == "restart") if (mac == myNet.getNodeMac() && message == "restart")
ESP.restart(); ESP.restart();
flag = true; flag = true;
} }
if (String(topic) == topicPrefix + "/espnow_switch/" + mac + "/set" || String(topic) == topicPrefix + "/espnow_led/" + mac + "/set") if (String(topic) == config.topicPrefix + "/espnow_switch/" + mac + "/set" || String(topic) == config.topicPrefix + "/espnow_led/" + mac + "/set")
{ {
flag = true; flag = true;
json["set"] = message; json["set"] = message;
} }
if (String(topic) == topicPrefix + "/espnow_led/" + mac + "/brightness") if (String(topic) == config.topicPrefix + "/espnow_led/" + mac + "/brightness")
{ {
flag = true; flag = true;
json["brightness"] = message; json["brightness"] = message;
} }
if (String(topic) == topicPrefix + "/espnow_led/" + mac + "/temperature") if (String(topic) == config.topicPrefix + "/espnow_led/" + mac + "/temperature")
{ {
flag = true; flag = true;
json["temperature"] = message; json["temperature"] = message;
} }
if (String(topic) == topicPrefix + "/espnow_led/" + mac + "/rgb") if (String(topic) == config.topicPrefix + "/espnow_led/" + mac + "/rgb")
{ {
flag = true; flag = true;
json["rgb"] = message; json["rgb"] = message;
@ -359,11 +441,9 @@ void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties
{ {
if (message == "restart") if (message == "restart")
outgoingData.payloadsType = ENPT_RESTART; outgoingData.payloadsType = ENPT_RESTART;
else if (message == "update")
outgoingData.payloadsType = message == "update" ? ENPT_UPDATE : ENPT_SET; outgoingData.payloadsType = ENPT_UPDATE;
char buffer[sizeof(esp_now_payload_data_t::message)]{0}; serializeJsonPretty(json, outgoingData.message);
serializeJsonPretty(json, buffer);
memcpy(&outgoingData.message, &buffer, sizeof(esp_now_payload_data_t::message));
char temp[sizeof(esp_now_payload_data_t)]{0}; char temp[sizeof(esp_now_payload_data_t)]{0};
memcpy(&temp, &outgoingData, sizeof(esp_now_payload_data_t)); memcpy(&temp, &outgoingData, sizeof(esp_now_payload_data_t));
uint8_t target[6]; uint8_t target[6];
@ -375,14 +455,30 @@ void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties
void sendKeepAliveMessage() void sendKeepAliveMessage()
{ {
keepAliveMessageTimerSemaphore = false; keepAliveMessageTimerSemaphore = false;
if (mqttClient.connected()) if (isMqttAvailable)
mqttClient.publish((topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/status").c_str(), 2, true, "online"); mqttPublish((config.topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/status").c_str(), "online", true);
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; DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message));
json["MQTT"] = mqttClient.connected() ? "online" : "offline"; json["MQTT"] = isMqttAvailable ? "online" : "offline";
json["frequency"] = 10; // For compatibility with the previous version. Will be removed in future releases. json["frequency"] = 10; // For compatibility with the previous version. Will be removed in future releases.
if (config.workMode == ESP_NOW_WIFI && WiFi.isConnected())
{
ntpWiFiClient.update();
uint64_t epochTime = ntpWiFiClient.getEpochTime();
struct tm *time = gmtime((time_t *)&epochTime);
json["time"] = ntpWiFiClient.getFormattedTime();
json["date"] = String(time->tm_mday) + "." + String(time->tm_mon + 1) + "." + String(time->tm_year + 1900);
}
if (config.workMode == ESP_NOW_LAN && Ethernet.linkStatus() == LinkON)
{
ntpEthClient.update();
uint64_t epochTime = ntpEthClient.getEpochTime();
struct tm *time = gmtime((time_t *)&epochTime);
json["time"] = ntpEthClient.getFormattedTime();
json["date"] = String(time->tm_mday) + "." + String(time->tm_mon + 1) + "." + String(time->tm_year + 1900);
}
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));
@ -393,12 +489,14 @@ void sendKeepAliveMessage()
void sendAttributesMessage() void sendAttributesMessage()
{ {
if (!isMqttAvailable)
return;
attributesMessageTimerSemaphore = false; attributesMessageTimerSemaphore = false;
uint32_t secs = millis() / 1000; uint32_t secs = millis() / 1000;
uint32_t mins = secs / 60; uint32_t mins = secs / 60;
uint32_t hours = mins / 60; uint32_t hours = mins / 60;
uint32_t days = hours / 24; uint32_t days = hours / 24;
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json; DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message));
json["Type"] = "ESP-NOW gateway"; json["Type"] = "ESP-NOW gateway";
#if defined(ESP8266) #if defined(ESP8266)
json["MCU"] = "ESP8266"; json["MCU"] = "ESP8266";
@ -409,11 +507,32 @@ void sendAttributesMessage()
json["MAC"] = myNet.getNodeMac(); json["MAC"] = myNet.getNodeMac();
json["Firmware"] = firmware; json["Firmware"] = firmware;
json["Library"] = myNet.getFirmwareVersion(); json["Library"] = myNet.getFirmwareVersion();
json["IP"] = WiFi.localIP().toString(); if (config.workMode == ESP_NOW_WIFI)
json["IP"] = WiFi.localIP().toString();
if (config.workMode == ESP_NOW_LAN)
json["IP"] = Ethernet.localIP().toString();
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 + "/espnow_gateway/" + myNet.getNodeMac() + "/attributes").c_str(), 2, true, buffer); mqttPublish((config.topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/attributes").c_str(), buffer, true);
}
void sendConfigMessage()
{
DynamicJsonDocument json(2048); // Same as PubSubClient buffer size.
json["platform"] = "mqtt";
json["name"] = config.deviceName;
json["unique_id"] = myNet.getNodeMac() + "-1";
json["device_class"] = "connectivity";
json["state_topic"] = config.topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/status";
json["json_attributes_topic"] = config.topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/attributes";
json["payload_on"] = "online";
json["expire_after"] = 30;
json["force_update"] = "true";
json["retain"] = "true";
char buffer[1024]{0};
serializeJsonPretty(json, buffer);
mqttPublish((config.topicPrefix + "/binary_sensor/" + myNet.getNodeMac() + "-1" + "/config").c_str(), buffer, true);
} }
String getValue(String data, char separator, uint8_t index) String getValue(String data, char separator, uint8_t index)
@ -433,41 +552,27 @@ String getValue(String data, char separator, uint8_t index)
void loadConfig() void loadConfig()
{ {
if (!LittleFS.exists("/config.json")) EEPROM.begin(4096);
if (EEPROM.read(4095) == 254)
{
EEPROM.get(0, config);
EEPROM.end();
}
else
{
EEPROM.end();
saveConfig(); saveConfig();
File file = LittleFS.open("/config.json", "r"); }
String jsonFile = file.readString(); delay(50);
StaticJsonDocument<1024> json;
deserializeJson(json, jsonFile);
espnowNetName = json["espnowNetName"].as<String>();
deviceName = json["deviceName"].as<String>();
ssid = json["ssid"].as<String>();
password = json["password"].as<String>();
mqttHostName = json["mqttHostName"].as<String>();
mqttHostPort = json["mqttHostPort"];
mqttUserLogin = json["mqttUserLogin"].as<String>();
mqttUserPassword = json["mqttUserPassword"].as<String>();
topicPrefix = json["topicPrefix"].as<String>();
file.close();
} }
void saveConfig() void saveConfig()
{ {
StaticJsonDocument<1024> json; EEPROM.begin(4096);
json["firmware"] = firmware; EEPROM.write(4095, 254);
json["espnowNetName"] = espnowNetName; EEPROM.put(0, config);
json["deviceName"] = deviceName; EEPROM.end();
json["ssid"] = ssid; delay(50);
json["password"] = password;
json["mqttHostName"] = mqttHostName;
json["mqttHostPort"] = mqttHostPort;
json["mqttUserLogin"] = mqttUserLogin;
json["mqttUserPassword"] = mqttUserPassword;
json["topicPrefix"] = topicPrefix;
json["system"] = "empty";
File file = LittleFS.open("/config.json", "w");
serializeJsonPretty(json, file);
file.close();
} }
String xmlNode(String tags, String data) String xmlNode(String tags, String data)
@ -489,7 +594,7 @@ void setupWebServer()
ssdpHeader = xmlNode("specVersion", ssdpHeader); ssdpHeader = xmlNode("specVersion", ssdpHeader);
ssdpHeader += xmlNode("URLBase", "http://" + WiFi.localIP().toString()); ssdpHeader += xmlNode("URLBase", "http://" + WiFi.localIP().toString());
String ssdpDescription = xmlNode("deviceType", "upnp:rootdevice"); String ssdpDescription = xmlNode("deviceType", "upnp:rootdevice");
ssdpDescription += xmlNode("friendlyName", deviceName); ssdpDescription += xmlNode("friendlyName", config.deviceName);
ssdpDescription += xmlNode("presentationURL", "/"); ssdpDescription += xmlNode("presentationURL", "/");
ssdpDescription += xmlNode("serialNumber", "0000000" + String(random(1000))); ssdpDescription += xmlNode("serialNumber", "0000000" + String(random(1000)));
ssdpDescription += xmlNode("modelName", "ESP-NOW gateway"); ssdpDescription += xmlNode("modelName", "ESP-NOW gateway");
@ -507,47 +612,116 @@ void setupWebServer()
webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{ request->send(LittleFS, "/index.htm"); }); { request->send(LittleFS, "/index.htm"); });
webServer.on("/function.js", HTTP_GET, [](AsyncWebServerRequest *request)
{ request->send(LittleFS, "/function.js"); });
webServer.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request)
{ request->send(LittleFS, "/style.css"); });
webServer.on("/setting", HTTP_GET, [](AsyncWebServerRequest *request) webServer.on("/setting", HTTP_GET, [](AsyncWebServerRequest *request)
{ {
ssid = request->getParam("ssid")->value(); config.ssid = request->getParam("ssid")->value();
password = request->getParam("password")->value(); config.password = request->getParam("password")->value();
mqttHostName = request->getParam("host")->value(); config.mqttHostName = request->getParam("mqttHostName")->value();
mqttHostPort = request->getParam("port")->value().toInt(); config.mqttHostPort = request->getParam("mqttHostPort")->value().toInt();
mqttUserLogin = request->getParam("login")->value(); config.mqttUserLogin = request->getParam("mqttUserLogin")->value();
mqttUserPassword = request->getParam("pass")->value(); config.mqttUserPassword = request->getParam("mqttUserPassword")->value();
topicPrefix = request->getParam("prefix")->value(); config.topicPrefix = request->getParam("topicPrefix")->value();
deviceName = request->getParam("name")->value(); config.deviceName = request->getParam("deviceName")->value();
espnowNetName = request->getParam("net")->value(); config.espnowNetName = request->getParam("espnowNetName")->value();
config.workMode = request->getParam("workMode")->value().toInt();
config.ntpHostName = request->getParam("ntpHostName")->value();
config.gmtOffset = request->getParam("gmtOffset")->value().toInt();
request->send(200); request->send(200);
saveConfig(); }); saveConfig(); });
webServer.on("/restart", HTTP_GET, [](AsyncWebServerRequest *request) webServer.on("/config", HTTP_GET, [](AsyncWebServerRequest *request)
{ {
request->send(200); String configJson;
DynamicJsonDocument json(2048); // For overflow protection.
json["firmware"] = firmware;
json["espnowNetName"] = config.espnowNetName;
json["deviceName"] = config.deviceName;
json["ssid"] = config.ssid;
json["password"] = config.password;
json["mqttHostName"] = config.mqttHostName;
json["mqttHostPort"] = config.mqttHostPort;
json["mqttUserLogin"] = config.mqttUserLogin;
json["mqttUserPassword"] = config.mqttUserPassword;
json["topicPrefix"] = config.topicPrefix;
json["workMode"] = config.workMode;
json["ntpHostName"] = config.ntpHostName;
json["gmtOffset"] = config.gmtOffset;
serializeJsonPretty(json, configJson);
request->send(200, "application/json", configJson); });
webServer.on("/restart", HTTP_GET, [](AsyncWebServerRequest *request)
{request->send(200);
ESP.restart(); }); ESP.restart(); });
webServer.onNotFound([](AsyncWebServerRequest *request) webServer.onNotFound([](AsyncWebServerRequest *request)
{ { request->send(404, "text/plain", "File Not Found"); });
if (LittleFS.exists(request->url()))
request->send(LittleFS, request->url()); if (config.workMode == ESP_NOW_WIFI)
else SSDP.begin();
{
request->send(404, "text/plain", "File Not Found");
} });
SSDP.begin();
webServer.begin(); webServer.begin();
} }
void connectToMqtt() void checkMqttAvailability()
{ {
mqttReconnectTimerSemaphore = false; mqttAvailabilityCheckTimerSemaphore = false;
mqttClient.connect();
if (config.workMode == ESP_NOW_WIFI)
if (WiFi.isConnected())
if (!mqttWifiClient.connected())
{
isMqttAvailable = false;
if (mqttWifiClient.connect(mqttUserID, config.mqttUserLogin.c_str(), config.mqttUserPassword.c_str()))
{
isMqttAvailable = true;
mqttWifiClient.subscribe((config.topicPrefix + "/espnow_gateway/#").c_str());
mqttWifiClient.subscribe((config.topicPrefix + "/espnow_switch/#").c_str());
mqttWifiClient.subscribe((config.topicPrefix + "/espnow_led/#").c_str());
sendConfigMessage();
sendAttributesMessage();
sendKeepAliveMessage();
}
}
if (config.workMode == ESP_NOW_LAN)
if (Ethernet.linkStatus() == LinkON)
if (!mqttEthClient.connected())
{
isMqttAvailable = false;
if (mqttEthClient.connect(mqttUserID, config.mqttUserLogin.c_str(), config.mqttUserPassword.c_str()))
{
isMqttAvailable = true;
mqttEthClient.subscribe((config.topicPrefix + "/espnow_gateway/#").c_str());
mqttEthClient.subscribe((config.topicPrefix + "/espnow_switch/#").c_str());
mqttEthClient.subscribe((config.topicPrefix + "/espnow_led/#").c_str());
sendConfigMessage();
sendAttributesMessage();
sendKeepAliveMessage();
}
}
} }
void mqttReconnectTimerCallback() void mqttPublish(const char *topic, const char *payload, bool retained)
{ {
mqttReconnectTimerSemaphore = true; if (config.workMode == ESP_NOW_WIFI)
mqttWifiClient.publish(topic, payload, retained);
if (config.workMode == ESP_NOW_LAN)
mqttEthClient.publish(topic, payload, retained);
}
void mqttAvailabilityCheckTimerCallback()
{
mqttAvailabilityCheckTimerSemaphore = true;
} }
void keepAliveMessageTimerCallback() void keepAliveMessageTimerCallback()