From 7a74c4f2dfe181824e02a88b82fd844944a37174 Mon Sep 17 00:00:00 2001 From: Alexey Zholtikov Date: Thu, 9 Feb 2023 18:22:15 +0300 Subject: [PATCH] Version 1.3 Changed library for MQTT connection. Added support for LAN connection. Added getting data from NTP server. --- README.md | 37 ++++-- data/function.js | 17 ++- data/index.htm | 17 +++ data/style.css | 26 +++- platformio.ini | 30 +++-- src/main.cpp | 328 +++++++++++++++++++++++++++++++---------------- 6 files changed, 320 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index c629fa7..91f6600 100644 --- a/README.md +++ b/README.md @@ -1,34 +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). +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. 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. WiFi router must be set on channel 1. +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 @@ -41,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). diff --git a/data/function.js b/data/function.js index 221ec8a..e037c42 100755 --- a/data/function.js +++ b/data/function.js @@ -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; +} \ No newline at end of file diff --git a/data/index.htm b/data/index.htm index 4d9c186..63865c3 100644 --- a/data/index.htm +++ b/data/index.htm @@ -29,6 +29,16 @@ title="ESP-NOW network name (1 to 20 characters)" /> +
+

Work mode:

+ +

+
+

WiFi settings

@@ -36,6 +46,13 @@ autocomplete="off" label title="WiFi password" />
+

NTP settings

+
+ + +
+

MQTT settings

() + " }}"; 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()); @@ -268,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) @@ -277,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 json; deserializeJson(json, forwardData.message); - mqttClient.publish((topicPrefix + "/rf_sensor/" + getValueName(json["type"].as()) + "/" + json["id"].as()).c_str(), 2, false, incomingData.message); + mqttPublish((topicPrefix + "/rf_sensor/" + getValueName(json["type"].as()) + "/" + json["id"].as()).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]; } @@ -329,8 +342,8 @@ void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties StaticJsonDocument 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; @@ -375,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 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)); @@ -393,6 +422,8 @@ void sendKeepAliveMessage() void sendAttributesMessage() { + if (!isMqttAvailable) + return; attributesMessageTimerSemaphore = false; uint32_t secs = millis() / 1000; uint32_t mins = secs / 60; @@ -409,11 +440,32 @@ void sendAttributesMessage() json["MAC"] = myNet.getNodeMac(); json["Firmware"] = firmware; json["Library"] = myNet.getFirmwareVersion(); - json["IP"] = WiFi.localIP().toString(); + 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) @@ -448,6 +500,9 @@ void loadConfig() mqttUserLogin = json["mqttUserLogin"].as(); mqttUserPassword = json["mqttUserPassword"].as(); topicPrefix = json["topicPrefix"].as(); + workMode = json["workMode"]; + ntpHostName = json["ntpHostName"].as(); + gmtOffset = json["gmtOffset"]; file.close(); } @@ -464,6 +519,9 @@ 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 = LittleFS.open("/config.json", "w"); serializeJsonPretty(json, file); @@ -518,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(); }); @@ -535,19 +596,62 @@ void setupWebServer() request->send(404, "text/plain", "File Not Found"); } }); - SSDP.begin(); + 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()