5 Commits
v1.23 ... v1.3

Author SHA1 Message Date
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
24ba0a09f4 Version 1.26
Changed FS from SPIFFS to LittleFS.
2023-02-04 10:52:52 +03:00
eebaca38b7 Minor changes 2023-01-28 14:07:38 +03:00
8e9082f05e Version 1.25
Fixed bug with ESP-NOW sensors not being added to HA via MQTT discovery.
2023-01-28 12:15:09 +03:00
1c306e031b Version 1.24
Added encrypting messages.
2023-01-22 12:46:58 +03:00
6 changed files with 347 additions and 154 deletions

View File

@ -1,33 +1,49 @@
# 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
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.
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).
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 (at ESP_NOW_WIFI mode).
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.
5. Automatically adds supported ESP-NOW devices configurations to Home Assistan via MQTT discovery.
6. Possibility firmware update over OTA.
7. Web interface for settings.
6. Possibility firmware update over OTA (at ESP_NOW_LAN mode via access point only).
7. Web interface for settings (at ESP_NOW_LAN mode via access point only).
8. 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
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.
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
1. ESP-NOW network name must be set same of all another ESP-NOW devices in network.
2. Upload the "data" folder (with web interface) into the filesystem before flashing.
3. WiFi router must be set on channel 1.
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.
4. At ESP_NOW_WIFI mode 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.
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.
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
@ -40,7 +56,9 @@ Gateway for data exchange between ESP-NOW devices and MQTT broker via WiFi.
## To Do
- [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").
- [ ] BLE device support (for ESP32).
- [ ] LoRa device support.
Any feedback via [e-mail](mailto:github@zh.com.ru) would be appreciated. Or... [Buy me a coffee](https://paypal.me/aZholtikov).

View File

@ -29,6 +29,7 @@ function loadBlock() {
}
document.getElementsByTagName('body')[0].innerHTML = newString;
setFirmvareValue('version', 'firmware');
setGpioValue('workModeSelect', 'workMode');
handleServerResponse();
}
@ -37,6 +38,12 @@ function getValue(id) {
return value;
}
function getSelectValue(id) {
var select = document.getElementById(id);
var value = select.value;
return value;
}
function sendRequest(submit, server) {
request = new XMLHttpRequest();
request.open("GET", server, true);
@ -49,7 +56,10 @@ function saveSetting(submit) {
+ "&login=" + getValue('mqttUserLogin') + "&pass=" + encodeURIComponent(getValue('mqttUserPassword'))
+ "&prefix=" + getValue('topicPrefix')
+ "&name=" + getValue('deviceName')
+ "&net=" + getValue('espnowNetName');
+ "&net=" + getValue('espnowNetName')
+ "&mode=" + getSelectValue('workModeSelect')
+ "&ntp=" + getValue('ntpHostName')
+ "&zone=" + getValue('gmtOffset');
sendRequest(submit, server);
alert("Please restart device for changes apply.");
}
@ -62,3 +72,8 @@ function restart(submit) {
function setFirmvareValue(id, 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

@ -29,6 +29,16 @@
title="ESP-NOW network name (1 to 20 characters)" />
</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>
<div class="wrapper">
<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" />
</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>
<div class="wrapper">
<input class="text-inp" id="mqttHostName" value="{{mqttHostName}}" placeholder="URL or IP" label

View File

@ -29,6 +29,13 @@ h1 {
margin: 10px 0;
}
.text-select {
width: 35%;
font-weight: 600;
flex-shrink: 0;
margin-right: 10px;
}
.text-inp {
width: 48%;
min-height: 30px;
@ -61,11 +68,28 @@ input {
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 {
background: white;
cursor: pointer;
}
select:hover {
background: white;
cursor: pointer;
}
.btn {
width: 48%;
background: rgb(65, 125, 238);
@ -88,7 +112,7 @@ input:hover {
}
#espnowNetName {
margin-bottom: 10px;
margin-bottom: 15px;
}
.wrapper.wrapper--end {

View File

@ -1,49 +1,61 @@
[env:ESP8266]
platform = espressif8266
board = nodemcuv2
board = esp12e
framework = arduino
board_build.filesystem = littlefs
lib_deps =
https://github.com/aZholtikov/ZHNetwork
https://github.com/aZholtikov/ZHConfig
bblanchon/ArduinoJson@^6.19.4
me-no-dev/ESP Async WebServer@^1.2.3
marvinroger/AsyncMqttClient@^0.9.0
https://github.com/aZholtikov/Async-Web-Server
https://github.com/bblanchon/ArduinoJson
https://github.com/arduino-libraries/Ethernet
https://github.com/knolleary/pubsubclient
https://github.com/arduino-libraries/NTPClient
[env:ESP8266-OTA]
platform = espressif8266
board = nodemcuv2
board = esp12e
framework = arduino
upload_port = 192.168.1.113
board_build.filesystem = littlefs
upload_port = 192.168.4.1
upload_protocol = espota
lib_deps =
https://github.com/aZholtikov/ZHNetwork
https://github.com/aZholtikov/ZHConfig
bblanchon/ArduinoJson@^6.19.4
me-no-dev/ESP Async WebServer@^1.2.3
marvinroger/AsyncMqttClient@^0.9.0
https://github.com/aZholtikov/Async-Web-Server
https://github.com/bblanchon/ArduinoJson
https://github.com/arduino-libraries/Ethernet
https://github.com/knolleary/pubsubclient
https://github.com/arduino-libraries/NTPClient
[env:ESP32]
platform = espressif32
board = az-delivery-devkit-v4
board = esp32dev
framework = arduino
board_build.filesystem = littlefs
lib_deps =
https://github.com/aZholtikov/ZHNetwork
https://github.com/aZholtikov/ZHConfig
bblanchon/ArduinoJson@^6.19.4
me-no-dev/ESP Async WebServer@^1.2.3
marvinroger/AsyncMqttClient@^0.9.0
luc-github/ESP32SSDP@^1.2.0
https://github.com/aZholtikov/Async-Web-Server
https://github.com/bblanchon/ArduinoJson
https://github.com/arduino-libraries/Ethernet
https://github.com/knolleary/pubsubclient
https://github.com/arduino-libraries/NTPClient
https://github.com/luc-github/ESP32SSDP
[env:ESP32-OTA]
platform = espressif32
board = az-delivery-devkit-v4
board = esp32dev
framework = arduino
upload_port = 192.168.1.110
board_build.filesystem = littlefs
upload_port = 192.168.4.1
upload_protocol = espota
lib_deps =
https://github.com/aZholtikov/ZHNetwork
https://github.com/aZholtikov/ZHConfig
bblanchon/ArduinoJson@^6.19.4
me-no-dev/ESP Async WebServer@^1.2.3
marvinroger/AsyncMqttClient@^0.9.0
luc-github/ESP32SSDP@^1.2.0
https://github.com/aZholtikov/Async-Web-Server
https://github.com/bblanchon/ArduinoJson
https://github.com/arduino-libraries/Ethernet
https://github.com/knolleary/pubsubclient
https://github.com/arduino-libraries/NTPClient
https://github.com/luc-github/ESP32SSDP

View File

@ -1,28 +1,34 @@
#include "ArduinoJson.h"
#include "ArduinoOTA.h"
#include "ESPAsyncWebServer.h"
#include "AsyncMQTTClient.h"
#include "ESPAsyncWebServer.h" // https://github.com/aZholtikov/Async-Web-Server
#include "Ethernet.h" // https://github.com/arduino-libraries/Ethernet
#include "PubSubClient.h"
#include "LittleFS.h"
#include "Ticker.h"
#include "NTPClient.h"
#include "ZHNetwork.h"
#include "ZHConfig.h"
#if defined(ESP8266)
#include "ESP8266SSDP.h"
#endif
#if defined(ESP32)
#include "SPIFFS.h"
#include "ESP32SSDP.h"
#endif
void onWifiEvent(WiFiEvent_t event);
typedef enum : uint8_t
{
ESP_NOW,
ESP_NOW_WIFI,
ESP_NOW_LAN
} work_mode_t;
void onEspnowMessage(const char *data, const uint8_t *sender);
void onMqttConnect(bool sessionPresent);
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason);
void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total);
void onMqttMessage(char *topic, byte *payload, unsigned int length);
void sendKeepAliveMessage(void);
void sendAttributesMessage(void);
void sendConfigMessage(void);
String getValue(String data, char separator, uint8_t index);
@ -32,12 +38,16 @@ void saveConfig(void);
String xmlNode(String tags, String data);
void setupWebServer(void);
void connectToMqtt(void);
void checkMqttAvailability(void);
const String firmware{"1.23"};
void mqttPublish(const char *topic, const char *payload, bool retained);
const String firmware{"1.3"};
String espnowNetName{"DEFAULT"};
uint8_t workMode{ESP_NOW};
#if defined(ESP8266)
String deviceName = "ESP-NOW gateway " + String(ESP.getChipId(), HEX);
#endif
@ -53,46 +63,79 @@ uint16_t mqttHostPort{1883};
String mqttUserLogin{""};
String mqttUserPassword{""};
String topicPrefix{"homeassistant"};
const char *mqttUserID{"ESP32"};
String ntpHostName{"NTP"};
uint16_t gmtOffset{10800};
uint8_t w5500Mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; // Change it if necessary.
ZHNetwork myNet;
AsyncWebServer webServer(80);
AsyncMqttClient mqttClient;
Ticker mqttReconnectTimer;
bool mqttReconnectTimerSemaphore{false};
void mqttReconnectTimerCallback(void);
EthernetClient ethClient;
WiFiClient wifiClient;
PubSubClient mqttEthClient(ethClient);
PubSubClient mqttWifiClient(wifiClient);
WiFiUDP udpWiFiClient;
EthernetUDP udpEthClient;
NTPClient ntpWiFiClient(udpWiFiClient, ntpHostName.c_str(), gmtOffset);
NTPClient ntpEthClient(udpEthClient, ntpHostName.c_str(), gmtOffset);
Ticker mqttAvailabilityCheckTimer;
bool mqttAvailabilityCheckTimerSemaphore{true};
bool isMqttAvailable{false};
void mqttAvailabilityCheckTimerCallback(void);
Ticker keepAliveMessageTimer;
bool keepAliveMessageTimerSemaphore{true};
void keepAliveMessageTimerCallback(void);
Ticker attributesMessageTimer;
bool attributesMessageTimerSemaphore{false};
bool attributesMessageTimerSemaphore{true};
void attributesMessageTimerCallback(void);
void setup()
{
SPIFFS.begin();
#if defined(ESP8266)
LittleFS.begin();
#endif
#if defined(ESP32)
LittleFS.begin(true);
#endif
loadConfig();
WiFi.onEvent(onWifiEvent);
if (workMode == ESP_NOW_LAN)
{
Ethernet.init(5);
Ethernet.begin(w5500Mac);
}
if (workMode == ESP_NOW_WIFI)
{
#if defined(ESP8266)
WiFi.setSleepMode(WIFI_NONE_SLEEP);
#endif
#if defined(ESP32)
WiFi.setSleep(WIFI_PS_NONE);
#endif
WiFi.persistent(false);
WiFi.setAutoConnect(false);
WiFi.setAutoReconnect(true);
}
myNet.begin(espnowNetName.c_str(), true);
if (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.setOnBroadcastReceivingCallback(onEspnowMessage);
myNet.setOnUnicastReceivingCallback(onEspnowMessage);
}
#if defined(ESP8266)
WiFi.softAP(("ESP-NOW gateway " + String(ESP.getChipId(), HEX)).c_str(), "12345678");
@ -101,7 +144,9 @@ void setup()
WiFi.softAP(("ESP-NOW gateway " + String(ESP.getEfuseMac(), HEX)).c_str(), "12345678");
#endif
uint8_t scan = WiFi.scanNetworks(false, false, 1);
if (workMode == ESP_NOW_WIFI)
{
uint8_t scan = WiFi.scanNetworks(false, false);
String name;
int32_t rssi;
uint8_t encryption;
@ -119,55 +164,61 @@ void setup()
if (name == ssid)
WiFi.begin(ssid.c_str(), password.c_str());
}
}
mqttClient.onConnect(onMqttConnect);
mqttClient.onDisconnect(onMqttDisconnect);
mqttClient.onMessage(onMqttMessage);
mqttClient.setServer(mqttHostName.c_str(), mqttHostPort);
mqttClient.setCredentials(mqttUserLogin.c_str(), mqttUserPassword.c_str());
if (workMode == ESP_NOW_WIFI)
{
ntpWiFiClient.begin();
mqttWifiClient.setBufferSize(2048);
mqttWifiClient.setServer(mqttHostName.c_str(), mqttHostPort);
mqttWifiClient.setCallback(onMqttMessage);
}
if (workMode == ESP_NOW_LAN)
{
ntpEthClient.begin();
mqttEthClient.setBufferSize(2048);
mqttEthClient.setServer(mqttHostName.c_str(), mqttHostPort);
mqttEthClient.setCallback(onMqttMessage);
}
setupWebServer();
ArduinoOTA.begin();
keepAliveMessageTimer.attach(10, keepAliveMessageTimerCallback);
mqttAvailabilityCheckTimer.attach(5, mqttAvailabilityCheckTimerCallback);
attributesMessageTimer.attach(60, attributesMessageTimerCallback);
}
void loop()
{
if (mqttReconnectTimerSemaphore)
connectToMqtt();
if (mqttAvailabilityCheckTimerSemaphore)
checkMqttAvailability();
if (keepAliveMessageTimerSemaphore)
sendKeepAliveMessage();
if (attributesMessageTimerSemaphore)
sendAttributesMessage();
if (workMode == ESP_NOW_WIFI)
mqttWifiClient.loop();
if (workMode == ESP_NOW_LAN)
mqttEthClient.loop();
myNet.maintenance();
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)
{
if (!mqttClient.connected())
if (!isMqttAvailable)
return;
esp_now_payload_data_t incomingData;
memcpy(&incomingData, data, sizeof(esp_now_payload_data_t));
if (incomingData.payloadsType == ENPT_ATTRIBUTES)
mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, incomingData.message);
mqttPublish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), incomingData.message, true);
if (incomingData.payloadsType == ENPT_KEEP_ALIVE)
mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, "online");
mqttPublish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), "online", true);
if (incomingData.payloadsType == ENPT_STATE)
mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, incomingData.message);
mqttPublish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), incomingData.message, true);
if (incomingData.payloadsType == ENPT_CONFIG)
{
if (incomingData.deviceType == ENDT_SWITCH)
@ -191,11 +242,10 @@ void onEspnowMessage(const char *data, const uint8_t *sender)
jsonConfig["payload_on"] = json["payload_on"];
jsonConfig["payload_off"] = json["payload_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);
mqttPublish((topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), buffer, true);
}
if (incomingData.deviceType == ENDT_LED)
{
@ -233,11 +283,10 @@ void onEspnowMessage(const char *data, const uint8_t *sender)
jsonConfig["payload_on"] = json["payload_on"];
jsonConfig["payload_off"] = json["payload_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);
mqttPublish((topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), buffer, true);
}
if (incomingData.deviceType == ENDT_SENSOR)
{
@ -255,8 +304,9 @@ void onEspnowMessage(const char *data, const uint8_t *sender)
jsonConfig["value_template"] = "{{ value_json." + json["template"].as<String>() + " }}";
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_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>());
@ -265,7 +315,7 @@ void onEspnowMessage(const char *data, const uint8_t *sender)
}
char buffer[2048]{0};
serializeJsonPretty(jsonConfig, buffer);
mqttClient.publish((topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), 2, true, buffer);
mqttPublish((topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), buffer, true);
}
}
if (incomingData.payloadsType == ENPT_FORWARD)
@ -274,50 +324,16 @@ void onEspnowMessage(const char *data, const uint8_t *sender)
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);
mqttPublish((topicPrefix + "/rf_sensor/" + getValueName(json["type"].as<rf_sensor_type_t>()) + "/" + json["id"].as<uint16_t>()).c_str(), incomingData.message, false);
}
}
void onMqttConnect(bool sessionPresent)
{
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)
void onMqttMessage(char *topic, byte *payload, unsigned int length)
{
String mac = getValue(String(topic).substring(0, String(topic).length()), '/', 2);
String message;
bool flag{false};
for (uint16_t i = 0; i < len; ++i)
for (uint16_t i = 0; i < length; ++i)
{
message += (char)payload[i];
}
@ -326,8 +342,8 @@ void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
if (message == "update" || message == "restart")
{
mqttClient.publish(topic, 2, true, "");
mqttClient.publish((String(topic) + "/status").c_str(), 2, true, "offline");
mqttPublish(topic, "", true);
mqttPublish((String(topic) + "/status").c_str(), "offline", true);
if (mac == myNet.getNodeMac() && message == "restart")
ESP.restart();
flag = true;
@ -372,14 +388,30 @@ void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties
void sendKeepAliveMessage()
{
keepAliveMessageTimerSemaphore = false;
if (mqttClient.connected())
mqttClient.publish((topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/status").c_str(), 2, true, "online");
if (isMqttAvailable)
mqttPublish((topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/status").c_str(), "online", true);
esp_now_payload_data_t outgoingData;
outgoingData.deviceType = ENDT_GATEWAY;
outgoingData.payloadsType = ENPT_KEEP_ALIVE;
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
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.
if (workMode == ESP_NOW_WIFI)
{
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 (workMode == ESP_NOW_LAN)
{
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};
serializeJsonPretty(json, buffer);
memcpy(&outgoingData.message, &buffer, sizeof(esp_now_payload_data_t::message));
@ -390,6 +422,8 @@ void sendKeepAliveMessage()
void sendAttributesMessage()
{
if (!isMqttAvailable)
return;
attributesMessageTimerSemaphore = false;
uint32_t secs = millis() / 1000;
uint32_t mins = secs / 60;
@ -406,11 +440,32 @@ void sendAttributesMessage()
json["MAC"] = myNet.getNodeMac();
json["Firmware"] = firmware;
json["Library"] = myNet.getFirmwareVersion();
if (workMode == ESP_NOW_WIFI)
json["IP"] = WiFi.localIP().toString();
if (workMode == ESP_NOW_LAN)
json["IP"] = Ethernet.localIP().toString();
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};
serializeJsonPretty(json, buffer);
mqttClient.publish((topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/attributes").c_str(), 2, true, buffer);
mqttPublish((topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/attributes").c_str(), buffer, true);
}
void sendConfigMessage()
{
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["retain"] = "true";
char buffer[1024]{0};
serializeJsonPretty(json, buffer);
mqttPublish((topicPrefix + "/binary_sensor/" + myNet.getNodeMac() + "-1" + "/config").c_str(), buffer, true);
}
String getValue(String data, char separator, uint8_t index)
@ -430,9 +485,9 @@ String getValue(String data, char separator, uint8_t index)
void loadConfig()
{
if (!SPIFFS.exists("/config.json"))
if (!LittleFS.exists("/config.json"))
saveConfig();
File file = SPIFFS.open("/config.json", "r");
File file = LittleFS.open("/config.json", "r");
String jsonFile = file.readString();
StaticJsonDocument<1024> json;
deserializeJson(json, jsonFile);
@ -445,6 +500,9 @@ void loadConfig()
mqttUserLogin = json["mqttUserLogin"].as<String>();
mqttUserPassword = json["mqttUserPassword"].as<String>();
topicPrefix = json["topicPrefix"].as<String>();
workMode = json["workMode"];
ntpHostName = json["ntpHostName"].as<String>();
gmtOffset = json["gmtOffset"];
file.close();
}
@ -461,8 +519,11 @@ void saveConfig()
json["mqttUserLogin"] = mqttUserLogin;
json["mqttUserPassword"] = mqttUserPassword;
json["topicPrefix"] = topicPrefix;
json["workMode"] = workMode;
json["ntpHostName"] = ntpHostName;
json["gmtOffset"] = gmtOffset;
json["system"] = "empty";
File file = SPIFFS.open("/config.json", "w");
File file = LittleFS.open("/config.json", "w");
serializeJsonPretty(json, file);
file.close();
}
@ -502,7 +563,7 @@ void setupWebServer()
request->send(200, "text/xml", ssdpSend); });
webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{ request->send(SPIFFS, "/index.htm"); });
{ request->send(LittleFS, "/index.htm"); });
webServer.on("/setting", HTTP_GET, [](AsyncWebServerRequest *request)
{
@ -515,6 +576,9 @@ void setupWebServer()
topicPrefix = request->getParam("prefix")->value();
deviceName = request->getParam("name")->value();
espnowNetName = request->getParam("net")->value();
workMode = request->getParam("mode")->value().toInt();
ntpHostName = request->getParam("ntp")->value();
gmtOffset = request->getParam("zone")->value().toInt();
request->send(200);
saveConfig(); });
@ -525,26 +589,69 @@ void setupWebServer()
webServer.onNotFound([](AsyncWebServerRequest *request)
{
if (SPIFFS.exists(request->url()))
request->send(SPIFFS, request->url());
if (LittleFS.exists(request->url()))
request->send(LittleFS, request->url());
else
{
request->send(404, "text/plain", "File Not Found");
} });
if (workMode == ESP_NOW_WIFI)
SSDP.begin();
webServer.begin();
}
void connectToMqtt()
void checkMqttAvailability()
{
mqttReconnectTimerSemaphore = false;
mqttClient.connect();
mqttAvailabilityCheckTimerSemaphore = false;
if (workMode == ESP_NOW_WIFI)
if (WiFi.isConnected())
if (!mqttWifiClient.connected())
{
isMqttAvailable = false;
if (mqttWifiClient.connect(mqttUserID, mqttUserLogin.c_str(), mqttUserPassword.c_str()))
{
isMqttAvailable = true;
mqttWifiClient.subscribe((topicPrefix + "/espnow_gateway/#").c_str());
mqttWifiClient.subscribe((topicPrefix + "/espnow_switch/#").c_str());
mqttWifiClient.subscribe((topicPrefix + "/espnow_led/#").c_str());
sendConfigMessage();
}
}
if (workMode == ESP_NOW_LAN)
if (Ethernet.linkStatus() == LinkON)
if (!mqttEthClient.connected())
{
isMqttAvailable = false;
if (mqttEthClient.connect(mqttUserID, mqttUserLogin.c_str(), mqttUserPassword.c_str()))
{
isMqttAvailable = true;
mqttEthClient.subscribe((topicPrefix + "/espnow_gateway/#").c_str());
mqttEthClient.subscribe((topicPrefix + "/espnow_switch/#").c_str());
mqttEthClient.subscribe((topicPrefix + "/espnow_led/#").c_str());
sendConfigMessage();
}
}
}
void mqttReconnectTimerCallback()
void mqttPublish(const char *topic, const char *payload, bool retained)
{
mqttReconnectTimerSemaphore = true;
if (workMode == ESP_NOW_WIFI)
mqttWifiClient.publish(topic, payload, retained);
if (workMode == ESP_NOW_LAN)
mqttEthClient.publish(topic, payload, retained);
}
void mqttAvailabilityCheckTimerCallback()
{
mqttAvailabilityCheckTimerSemaphore = true;
}
void keepAliveMessageTimerCallback()