2022-12-28 21:44:16 +03:00
|
|
|
#include "ArduinoJson.h"
|
|
|
|
#include "ArduinoOTA.h"
|
|
|
|
#include "ESPAsyncWebServer.h"
|
|
|
|
#include "AsyncMQTTClient.h"
|
|
|
|
#include "Ticker.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);
|
|
|
|
|
|
|
|
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 sendKeepAliveMessage(void);
|
|
|
|
void sendAttributesMessage(void);
|
|
|
|
|
|
|
|
String getValue(String data, char separator, uint8_t index);
|
|
|
|
|
|
|
|
void loadConfig(void);
|
|
|
|
void saveConfig(void);
|
|
|
|
|
|
|
|
String xmlNode(String tags, String data);
|
|
|
|
void setupWebServer(void);
|
|
|
|
|
|
|
|
void connectToMqtt(void);
|
|
|
|
|
2023-01-28 12:15:09 +03:00
|
|
|
const String firmware{"1.25"};
|
2022-12-28 21:44:16 +03:00
|
|
|
|
|
|
|
String espnowNetName{"DEFAULT"};
|
|
|
|
|
2023-01-12 12:24:53 +03:00
|
|
|
#if defined(ESP8266)
|
|
|
|
String deviceName = "ESP-NOW gateway " + String(ESP.getChipId(), HEX);
|
|
|
|
#endif
|
|
|
|
#if defined(ESP32)
|
|
|
|
String deviceName = "ESP-NOW gateway " + String(ESP.getEfuseMac(), HEX);
|
|
|
|
#endif
|
2022-12-28 21:44:16 +03:00
|
|
|
|
|
|
|
String ssid{"SSID"};
|
|
|
|
String password{"PASSWORD"};
|
|
|
|
|
|
|
|
String mqttHostName{"MQTT"};
|
|
|
|
uint16_t mqttHostPort{1883};
|
|
|
|
String mqttUserLogin{""};
|
|
|
|
String mqttUserPassword{""};
|
|
|
|
String topicPrefix{"homeassistant"};
|
|
|
|
|
|
|
|
ZHNetwork myNet;
|
|
|
|
AsyncWebServer webServer(80);
|
|
|
|
AsyncMqttClient mqttClient;
|
|
|
|
|
|
|
|
Ticker mqttReconnectTimer;
|
|
|
|
bool mqttReconnectTimerSemaphore{false};
|
|
|
|
void mqttReconnectTimerCallback(void);
|
|
|
|
|
|
|
|
Ticker keepAliveMessageTimer;
|
2023-01-03 21:23:43 +03:00
|
|
|
bool keepAliveMessageTimerSemaphore{true};
|
2022-12-28 21:44:16 +03:00
|
|
|
void keepAliveMessageTimerCallback(void);
|
|
|
|
|
|
|
|
Ticker attributesMessageTimer;
|
|
|
|
bool attributesMessageTimerSemaphore{false};
|
|
|
|
void attributesMessageTimerCallback(void);
|
|
|
|
|
|
|
|
void setup()
|
|
|
|
{
|
|
|
|
SPIFFS.begin();
|
2023-01-03 21:23:43 +03:00
|
|
|
|
2022-12-28 21:44:16 +03:00
|
|
|
loadConfig();
|
2023-01-03 21:23:43 +03:00
|
|
|
|
|
|
|
WiFi.onEvent(onWifiEvent);
|
2023-01-12 12:24:53 +03:00
|
|
|
|
2022-12-28 21:44:16 +03:00
|
|
|
#if defined(ESP8266)
|
|
|
|
WiFi.setSleepMode(WIFI_NONE_SLEEP);
|
|
|
|
#endif
|
|
|
|
#if defined(ESP32)
|
|
|
|
WiFi.setSleep(WIFI_PS_NONE);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
WiFi.persistent(false);
|
|
|
|
WiFi.setAutoConnect(false);
|
2023-01-03 21:23:43 +03:00
|
|
|
WiFi.setAutoReconnect(true);
|
2022-12-28 21:44:16 +03:00
|
|
|
|
2023-01-03 21:23:43 +03:00
|
|
|
myNet.begin(espnowNetName.c_str(), true);
|
2023-01-22 12:46:58 +03:00
|
|
|
// myNet.setCryptKey("VERY_LONG_CRYPT_KEY"); // If encryption is used, the key must be set same of all another ESP-NOW devices in network.
|
2022-12-28 21:44:16 +03:00
|
|
|
|
|
|
|
myNet.setOnBroadcastReceivingCallback(onEspnowMessage);
|
|
|
|
myNet.setOnUnicastReceivingCallback(onEspnowMessage);
|
|
|
|
|
2023-01-12 12:24:53 +03:00
|
|
|
#if defined(ESP8266)
|
|
|
|
WiFi.softAP(("ESP-NOW gateway " + String(ESP.getChipId(), HEX)).c_str(), "12345678");
|
|
|
|
#endif
|
|
|
|
#if defined(ESP32)
|
|
|
|
WiFi.softAP(("ESP-NOW gateway " + String(ESP.getEfuseMac(), HEX)).c_str(), "12345678");
|
|
|
|
#endif
|
|
|
|
|
2023-01-03 21:23:43 +03:00
|
|
|
uint8_t scan = WiFi.scanNetworks(false, false, 1);
|
|
|
|
String name;
|
|
|
|
int32_t rssi;
|
|
|
|
uint8_t encryption;
|
|
|
|
uint8_t *bssid;
|
|
|
|
int32_t channel;
|
|
|
|
bool hidden;
|
|
|
|
for (int8_t i = 0; i < scan; i++)
|
|
|
|
{
|
|
|
|
#if defined(ESP8266)
|
|
|
|
WiFi.getNetworkInfo(i, name, encryption, rssi, bssid, channel, hidden);
|
|
|
|
#endif
|
|
|
|
#if defined(ESP32)
|
|
|
|
WiFi.getNetworkInfo(i, name, encryption, rssi, bssid, channel);
|
|
|
|
#endif
|
|
|
|
if (name == ssid)
|
|
|
|
WiFi.begin(ssid.c_str(), password.c_str());
|
|
|
|
}
|
|
|
|
|
2022-12-28 21:44:16 +03:00
|
|
|
mqttClient.onConnect(onMqttConnect);
|
|
|
|
mqttClient.onDisconnect(onMqttDisconnect);
|
|
|
|
mqttClient.onMessage(onMqttMessage);
|
|
|
|
mqttClient.setServer(mqttHostName.c_str(), mqttHostPort);
|
|
|
|
mqttClient.setCredentials(mqttUserLogin.c_str(), mqttUserPassword.c_str());
|
|
|
|
|
|
|
|
setupWebServer();
|
|
|
|
|
|
|
|
ArduinoOTA.begin();
|
|
|
|
|
|
|
|
keepAliveMessageTimer.attach(10, keepAliveMessageTimerCallback);
|
|
|
|
}
|
|
|
|
|
|
|
|
void loop()
|
|
|
|
{
|
|
|
|
if (mqttReconnectTimerSemaphore)
|
|
|
|
connectToMqtt();
|
|
|
|
if (keepAliveMessageTimerSemaphore)
|
|
|
|
sendKeepAliveMessage();
|
|
|
|
if (attributesMessageTimerSemaphore)
|
|
|
|
sendAttributesMessage();
|
|
|
|
myNet.maintenance();
|
|
|
|
ArduinoOTA.handle();
|
|
|
|
}
|
|
|
|
|
|
|
|
void onWifiEvent(WiFiEvent_t event)
|
|
|
|
{
|
|
|
|
#if defined(ESP8266)
|
2023-01-03 21:23:43 +03:00
|
|
|
if (event == WIFI_EVENT_STAMODE_GOT_IP)
|
2022-12-28 21:44:16 +03:00
|
|
|
#endif
|
|
|
|
#if defined(ESP32)
|
2023-01-03 21:23:43 +03:00
|
|
|
if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP)
|
2022-12-28 21:44:16 +03:00
|
|
|
#endif
|
2023-01-03 21:23:43 +03:00
|
|
|
mqttClient.connect();
|
2022-12-28 21:44:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void onEspnowMessage(const char *data, const uint8_t *sender)
|
|
|
|
{
|
|
|
|
if (!mqttClient.connected())
|
|
|
|
return;
|
|
|
|
esp_now_payload_data_t incomingData;
|
|
|
|
memcpy(&incomingData, data, sizeof(esp_now_payload_data_t));
|
2023-01-03 21:23:43 +03:00
|
|
|
if (incomingData.payloadsType == ENPT_ATTRIBUTES)
|
2022-12-28 21:44:16 +03:00
|
|
|
mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, incomingData.message);
|
2023-01-03 21:23:43 +03:00
|
|
|
if (incomingData.payloadsType == ENPT_KEEP_ALIVE)
|
2022-12-28 21:44:16 +03:00
|
|
|
mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, "online");
|
2023-01-03 21:23:43 +03:00
|
|
|
if (incomingData.payloadsType == ENPT_STATE)
|
2022-12-28 21:44:16 +03:00
|
|
|
mqttClient.publish((topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/" + getValueName(incomingData.payloadsType)).c_str(), 2, true, incomingData.message);
|
2023-01-03 21:23:43 +03:00
|
|
|
if (incomingData.payloadsType == ENPT_CONFIG)
|
2023-01-04 14:02:05 +03:00
|
|
|
{
|
2023-01-03 21:23:43 +03:00
|
|
|
if (incomingData.deviceType == ENDT_SWITCH)
|
|
|
|
{
|
|
|
|
esp_now_payload_data_t configData;
|
|
|
|
memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
|
|
|
|
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
|
|
|
|
deserializeJson(json, configData.message);
|
|
|
|
uint8_t unit = json["unit"].as<uint8_t>();
|
2023-01-06 13:09:21 +03:00
|
|
|
ha_component_type_t type = json["type"].as<ha_component_type_t>();
|
2023-01-04 14:02:05 +03:00
|
|
|
StaticJsonDocument<2048> jsonConfig;
|
2023-01-03 21:23:43 +03:00
|
|
|
jsonConfig["platform"] = "mqtt";
|
|
|
|
jsonConfig["name"] = json["name"];
|
|
|
|
jsonConfig["unique_id"] = myNet.macToString(sender) + "-" + unit;
|
2023-01-06 13:09:21 +03:00
|
|
|
jsonConfig["device_class"] = getValueName(json["class"].as<ha_switch_device_class_t>());
|
2023-01-03 21:23:43 +03:00
|
|
|
jsonConfig["state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
|
2023-01-15 11:58:40 +03:00
|
|
|
jsonConfig["value_template"] = "{{ value_json." + json["template"].as<String>() + " }}";
|
2023-01-03 21:23:43 +03:00
|
|
|
jsonConfig["command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/set";
|
|
|
|
jsonConfig["json_attributes_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/attributes";
|
|
|
|
jsonConfig["availability_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/status";
|
2023-01-12 12:24:53 +03:00
|
|
|
jsonConfig["payload_on"] = json["payload_on"];
|
|
|
|
jsonConfig["payload_off"] = json["payload_off"];
|
2023-01-03 21:23:43 +03:00
|
|
|
jsonConfig["optimistic"] = "false";
|
|
|
|
jsonConfig["qos"] = 2;
|
|
|
|
jsonConfig["retain"] = "true";
|
2023-01-04 14:02:05 +03:00
|
|
|
char buffer[2048]{0};
|
|
|
|
serializeJsonPretty(jsonConfig, buffer);
|
2023-01-06 13:09:21 +03:00
|
|
|
mqttClient.publish((topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), 2, true, buffer);
|
2023-01-04 14:02:05 +03:00
|
|
|
}
|
|
|
|
if (incomingData.deviceType == ENDT_LED)
|
|
|
|
{
|
|
|
|
esp_now_payload_data_t configData;
|
|
|
|
memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
|
|
|
|
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
|
|
|
|
deserializeJson(json, configData.message);
|
|
|
|
uint8_t unit = json["unit"].as<uint8_t>();
|
2023-01-06 13:09:21 +03:00
|
|
|
ha_component_type_t type = json["type"].as<ha_component_type_t>();
|
2023-01-04 14:02:05 +03:00
|
|
|
esp_now_led_type_t ledClass = json["class"];
|
|
|
|
StaticJsonDocument<2048> jsonConfig;
|
|
|
|
jsonConfig["platform"] = "mqtt";
|
|
|
|
jsonConfig["name"] = json["name"];
|
|
|
|
jsonConfig["unique_id"] = myNet.macToString(sender) + "-" + unit;
|
|
|
|
jsonConfig["state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
|
|
|
|
jsonConfig["state_value_template"] = "{{ value_json.state }}";
|
|
|
|
jsonConfig["command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/set";
|
|
|
|
jsonConfig["brightness_state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
|
|
|
|
jsonConfig["brightness_value_template"] = "{{ value_json.brightness }}";
|
|
|
|
jsonConfig["brightness_command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/brightness";
|
|
|
|
if (ledClass == ENLT_RGB || ledClass == ENLT_RGBW || ledClass == ENLT_RGBWW)
|
|
|
|
{
|
|
|
|
jsonConfig["rgb_state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
|
|
|
|
jsonConfig["rgb_value_template"] = "{{ value_json.rgb | join(',') }}";
|
|
|
|
jsonConfig["rgb_command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/rgb";
|
|
|
|
}
|
|
|
|
if (ledClass == ENLT_WW || ledClass == ENLT_RGBWW)
|
|
|
|
{
|
|
|
|
jsonConfig["color_temp_state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
|
|
|
|
jsonConfig["color_temp_value_template"] = "{{ value_json.temperature }}";
|
|
|
|
jsonConfig["color_temp_command_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/temperature";
|
|
|
|
}
|
|
|
|
jsonConfig["json_attributes_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/attributes";
|
|
|
|
jsonConfig["availability_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/status";
|
2023-01-12 12:24:53 +03:00
|
|
|
jsonConfig["payload_on"] = json["payload_on"];
|
|
|
|
jsonConfig["payload_off"] = json["payload_off"];
|
2023-01-04 14:02:05 +03:00
|
|
|
jsonConfig["optimistic"] = "false";
|
|
|
|
jsonConfig["qos"] = 2;
|
|
|
|
jsonConfig["retain"] = "true";
|
|
|
|
char buffer[2048]{0};
|
2023-01-03 21:23:43 +03:00
|
|
|
serializeJsonPretty(jsonConfig, buffer);
|
2023-01-06 13:09:21 +03:00
|
|
|
mqttClient.publish((topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), 2, true, buffer);
|
|
|
|
}
|
|
|
|
if (incomingData.deviceType == ENDT_SENSOR)
|
|
|
|
{
|
|
|
|
esp_now_payload_data_t configData;
|
|
|
|
memcpy(&configData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
|
|
|
|
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
|
|
|
|
deserializeJson(json, configData.message);
|
|
|
|
uint8_t unit = json["unit"].as<uint8_t>();
|
|
|
|
ha_component_type_t type = json["type"].as<ha_component_type_t>();
|
|
|
|
StaticJsonDocument<2048> jsonConfig;
|
|
|
|
jsonConfig["platform"] = "mqtt";
|
|
|
|
jsonConfig["name"] = json["name"];
|
|
|
|
jsonConfig["unique_id"] = myNet.macToString(sender) + "-" + unit;
|
|
|
|
jsonConfig["state_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/state";
|
2023-01-15 11:58:40 +03:00
|
|
|
jsonConfig["value_template"] = "{{ value_json." + json["template"].as<String>() + " }}";
|
2023-01-06 13:09:21 +03:00
|
|
|
jsonConfig["json_attributes_topic"] = topicPrefix + "/" + getValueName(incomingData.deviceType) + "/" + myNet.macToString(sender) + "/attributes";
|
|
|
|
jsonConfig["force_update"] = "true";
|
|
|
|
jsonConfig["qos"] = 2;
|
|
|
|
jsonConfig["retain"] = "true";
|
2023-01-28 12:15:09 +03:00
|
|
|
if (type == HACT_SENSOR)
|
|
|
|
jsonConfig["device_class"] = getValueName(json["class"].as<ha_sensor_device_class_t>());
|
2023-01-06 13:09:21 +03:00
|
|
|
if (type == HACT_BINARY_SENSOR)
|
|
|
|
{
|
2023-01-15 11:58:40 +03:00
|
|
|
jsonConfig["device_class"] = getValueName(json["class"].as<ha_binary_sensor_device_class_t>());
|
2023-01-06 13:09:21 +03:00
|
|
|
jsonConfig["payload_on"] = json["payload_on"];
|
|
|
|
jsonConfig["payload_off"] = json["payload_off"];
|
|
|
|
}
|
|
|
|
char buffer[2048]{0};
|
|
|
|
serializeJsonPretty(jsonConfig, buffer);
|
|
|
|
mqttClient.publish((topicPrefix + "/" + getValueName(type) + "/" + myNet.macToString(sender) + "-" + unit + "/config").c_str(), 2, true, buffer);
|
2023-01-03 21:23:43 +03:00
|
|
|
}
|
2023-01-04 14:02:05 +03:00
|
|
|
}
|
2023-01-03 21:23:43 +03:00
|
|
|
if (incomingData.payloadsType == ENPT_FORWARD)
|
|
|
|
{
|
|
|
|
esp_now_payload_data_t forwardData;
|
|
|
|
memcpy(&forwardData.message, &incomingData.message, sizeof(esp_now_payload_data_t::message));
|
|
|
|
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
|
|
|
|
deserializeJson(json, forwardData.message);
|
2022-12-28 21:44:16 +03:00
|
|
|
mqttClient.publish((topicPrefix + "/rf_sensor/" + getValueName(json["type"].as<rf_sensor_type_t>()) + "/" + json["id"].as<uint16_t>()).c_str(), 2, false, incomingData.message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void onMqttConnect(bool sessionPresent)
|
|
|
|
{
|
2023-01-03 21:23:43 +03:00
|
|
|
mqttClient.subscribe((topicPrefix + "/espnow_gateway/#").c_str(), 2);
|
2022-12-28 21:44:16 +03:00
|
|
|
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";
|
2023-01-03 21:23:43 +03:00
|
|
|
json["state_topic"] = topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/status";
|
|
|
|
json["json_attributes_topic"] = topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/attributes";
|
2022-12-28 21:44:16 +03:00
|
|
|
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 message;
|
|
|
|
bool flag{false};
|
|
|
|
for (uint16_t i = 0; i < len; ++i)
|
|
|
|
{
|
|
|
|
message += (char)payload[i];
|
|
|
|
}
|
|
|
|
esp_now_payload_data_t outgoingData;
|
|
|
|
outgoingData.deviceType = ENDT_GATEWAY;
|
|
|
|
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");
|
|
|
|
if (mac == myNet.getNodeMac() && message == "restart")
|
|
|
|
ESP.restart();
|
|
|
|
flag = true;
|
|
|
|
}
|
|
|
|
if (String(topic) == topicPrefix + "/espnow_switch/" + mac + "/set" || String(topic) == topicPrefix + "/espnow_led/" + mac + "/set")
|
|
|
|
{
|
|
|
|
flag = true;
|
2023-01-12 12:24:53 +03:00
|
|
|
json["set"] = message;
|
2022-12-28 21:44:16 +03:00
|
|
|
}
|
|
|
|
if (String(topic) == topicPrefix + "/espnow_led/" + mac + "/brightness")
|
|
|
|
{
|
|
|
|
flag = true;
|
|
|
|
json["brightness"] = message;
|
|
|
|
}
|
|
|
|
if (String(topic) == topicPrefix + "/espnow_led/" + mac + "/temperature")
|
|
|
|
{
|
|
|
|
flag = true;
|
|
|
|
json["temperature"] = message;
|
|
|
|
}
|
|
|
|
if (String(topic) == topicPrefix + "/espnow_led/" + mac + "/rgb")
|
|
|
|
{
|
|
|
|
flag = true;
|
|
|
|
json["rgb"] = message;
|
|
|
|
}
|
|
|
|
if (flag)
|
|
|
|
{
|
|
|
|
if (message == "restart")
|
|
|
|
outgoingData.payloadsType = ENPT_RESTART;
|
|
|
|
else
|
|
|
|
outgoingData.payloadsType = message == "update" ? ENPT_UPDATE : ENPT_SET;
|
|
|
|
char buffer[sizeof(esp_now_payload_data_t::message)]{0};
|
|
|
|
serializeJsonPretty(json, buffer);
|
|
|
|
memcpy(&outgoingData.message, &buffer, sizeof(esp_now_payload_data_t::message));
|
|
|
|
char temp[sizeof(esp_now_payload_data_t)]{0};
|
|
|
|
memcpy(&temp, &outgoingData, sizeof(esp_now_payload_data_t));
|
|
|
|
uint8_t target[6];
|
|
|
|
myNet.stringToMac(mac, target);
|
|
|
|
myNet.sendUnicastMessage(temp, target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void sendKeepAliveMessage()
|
|
|
|
{
|
|
|
|
keepAliveMessageTimerSemaphore = false;
|
|
|
|
if (mqttClient.connected())
|
2023-01-03 21:23:43 +03:00
|
|
|
mqttClient.publish((topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/status").c_str(), 2, true, "online");
|
2022-12-28 21:44:16 +03:00
|
|
|
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";
|
2023-01-04 14:02:05 +03:00
|
|
|
json["frequency"] = 10; // For compatibility with the previous version. Will be removed in future releases.
|
2022-12-28 21:44:16 +03:00
|
|
|
char buffer[sizeof(esp_now_payload_data_t::message)]{0};
|
|
|
|
serializeJsonPretty(json, buffer);
|
|
|
|
memcpy(&outgoingData.message, &buffer, sizeof(esp_now_payload_data_t::message));
|
|
|
|
char temp[sizeof(esp_now_payload_data_t)]{0};
|
|
|
|
memcpy(&temp, &outgoingData, sizeof(esp_now_payload_data_t));
|
|
|
|
myNet.sendBroadcastMessage(temp);
|
|
|
|
}
|
|
|
|
|
|
|
|
void sendAttributesMessage()
|
|
|
|
{
|
|
|
|
attributesMessageTimerSemaphore = false;
|
|
|
|
uint32_t secs = millis() / 1000;
|
|
|
|
uint32_t mins = secs / 60;
|
|
|
|
uint32_t hours = mins / 60;
|
|
|
|
uint32_t days = hours / 24;
|
|
|
|
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
|
2023-01-12 12:24:53 +03:00
|
|
|
json["Type"] = "ESP-NOW gateway";
|
2022-12-28 21:44:16 +03:00
|
|
|
#if defined(ESP8266)
|
|
|
|
json["MCU"] = "ESP8266";
|
|
|
|
#endif
|
|
|
|
#if defined(ESP32)
|
|
|
|
json["MCU"] = "ESP32";
|
|
|
|
#endif
|
|
|
|
json["MAC"] = myNet.getNodeMac();
|
|
|
|
json["Firmware"] = firmware;
|
|
|
|
json["Library"] = myNet.getFirmwareVersion();
|
|
|
|
json["IP"] = WiFi.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);
|
2023-01-03 21:23:43 +03:00
|
|
|
mqttClient.publish((topicPrefix + "/espnow_gateway/" + myNet.getNodeMac() + "/attributes").c_str(), 2, true, buffer);
|
2022-12-28 21:44:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
String getValue(String data, char separator, uint8_t index)
|
|
|
|
{
|
|
|
|
uint8_t found{0};
|
|
|
|
int8_t strIndex[]{0, -1};
|
|
|
|
uint8_t maxIndex = data.length() - 1;
|
|
|
|
for (uint8_t i{0}; i <= maxIndex && found <= index; i++)
|
|
|
|
if (data.charAt(i) == separator || i == maxIndex)
|
|
|
|
{
|
|
|
|
found++;
|
|
|
|
strIndex[0] = strIndex[1] + 1;
|
|
|
|
strIndex[1] = (i == maxIndex) ? i + 1 : i;
|
|
|
|
}
|
|
|
|
return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
|
|
|
|
}
|
|
|
|
|
|
|
|
void loadConfig()
|
|
|
|
{
|
|
|
|
if (!SPIFFS.exists("/config.json"))
|
|
|
|
saveConfig();
|
|
|
|
File file = SPIFFS.open("/config.json", "r");
|
|
|
|
String jsonFile = file.readString();
|
|
|
|
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()
|
|
|
|
{
|
|
|
|
StaticJsonDocument<1024> json;
|
|
|
|
json["firmware"] = firmware;
|
|
|
|
json["espnowNetName"] = espnowNetName;
|
|
|
|
json["deviceName"] = deviceName;
|
|
|
|
json["ssid"] = ssid;
|
|
|
|
json["password"] = password;
|
|
|
|
json["mqttHostName"] = mqttHostName;
|
|
|
|
json["mqttHostPort"] = mqttHostPort;
|
|
|
|
json["mqttUserLogin"] = mqttUserLogin;
|
|
|
|
json["mqttUserPassword"] = mqttUserPassword;
|
|
|
|
json["topicPrefix"] = topicPrefix;
|
|
|
|
json["system"] = "empty";
|
|
|
|
File file = SPIFFS.open("/config.json", "w");
|
|
|
|
serializeJsonPretty(json, file);
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
String xmlNode(String tags, String data)
|
|
|
|
{
|
|
|
|
String temp = "<" + tags + ">" + data + "</" + tags + ">";
|
|
|
|
return temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setupWebServer()
|
|
|
|
{
|
|
|
|
SSDP.setSchemaURL("description.xml");
|
|
|
|
SSDP.setDeviceType("upnp:rootdevice");
|
|
|
|
|
|
|
|
webServer.on("/description.xml", HTTP_GET, [](AsyncWebServerRequest *request)
|
|
|
|
{
|
|
|
|
String ssdpSend = "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">";
|
|
|
|
String ssdpHeader = xmlNode("major", "1");
|
|
|
|
ssdpHeader += xmlNode("minor", "0");
|
|
|
|
ssdpHeader = xmlNode("specVersion", ssdpHeader);
|
|
|
|
ssdpHeader += xmlNode("URLBase", "http://" + WiFi.localIP().toString());
|
|
|
|
String ssdpDescription = xmlNode("deviceType", "upnp:rootdevice");
|
|
|
|
ssdpDescription += xmlNode("friendlyName", deviceName);
|
|
|
|
ssdpDescription += xmlNode("presentationURL", "/");
|
|
|
|
ssdpDescription += xmlNode("serialNumber", "0000000" + String(random(1000)));
|
2023-01-12 12:24:53 +03:00
|
|
|
ssdpDescription += xmlNode("modelName", "ESP-NOW gateway");
|
2022-12-28 21:44:16 +03:00
|
|
|
ssdpDescription += xmlNode("modelNumber", firmware);
|
|
|
|
ssdpDescription += xmlNode("modelURL", "https://github.com/aZholtikov/ESP-NOW-Gateway");
|
|
|
|
ssdpDescription += xmlNode("manufacturer", "Alexey Zholtikov");
|
|
|
|
ssdpDescription += xmlNode("manufacturerURL", "https://github.com/aZholtikov");
|
|
|
|
ssdpDescription += xmlNode("UDN", "DAA26FA3-D2D4-4072-BC7A-" + myNet.getNodeMac());
|
|
|
|
ssdpDescription = xmlNode("device", ssdpDescription);
|
|
|
|
ssdpHeader += ssdpDescription;
|
|
|
|
ssdpSend += ssdpHeader;
|
|
|
|
ssdpSend += "</root>";
|
|
|
|
request->send(200, "text/xml", ssdpSend); });
|
|
|
|
|
|
|
|
webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
|
|
|
|
{ request->send(SPIFFS, "/index.htm"); });
|
|
|
|
|
|
|
|
webServer.on("/setting", HTTP_GET, [](AsyncWebServerRequest *request)
|
|
|
|
{
|
|
|
|
ssid = request->getParam("ssid")->value();
|
|
|
|
password = request->getParam("password")->value();
|
|
|
|
mqttHostName = request->getParam("host")->value();
|
|
|
|
mqttHostPort = request->getParam("port")->value().toInt();
|
|
|
|
mqttUserLogin = request->getParam("login")->value();
|
|
|
|
mqttUserPassword = request->getParam("pass")->value();
|
|
|
|
topicPrefix = request->getParam("prefix")->value();
|
|
|
|
deviceName = request->getParam("name")->value();
|
|
|
|
espnowNetName = request->getParam("net")->value();
|
|
|
|
request->send(200);
|
|
|
|
saveConfig(); });
|
|
|
|
|
|
|
|
webServer.on("/restart", HTTP_GET, [](AsyncWebServerRequest *request)
|
|
|
|
{
|
|
|
|
request->send(200);
|
|
|
|
ESP.restart(); });
|
|
|
|
|
|
|
|
webServer.onNotFound([](AsyncWebServerRequest *request)
|
|
|
|
{
|
|
|
|
if (SPIFFS.exists(request->url()))
|
|
|
|
request->send(SPIFFS, request->url());
|
|
|
|
else
|
|
|
|
{
|
|
|
|
request->send(404, "text/plain", "File Not Found");
|
|
|
|
} });
|
|
|
|
|
|
|
|
SSDP.begin();
|
|
|
|
webServer.begin();
|
|
|
|
}
|
|
|
|
|
|
|
|
void connectToMqtt()
|
|
|
|
{
|
|
|
|
mqttReconnectTimerSemaphore = false;
|
|
|
|
mqttClient.connect();
|
|
|
|
}
|
|
|
|
|
|
|
|
void mqttReconnectTimerCallback()
|
|
|
|
{
|
|
|
|
mqttReconnectTimerSemaphore = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void keepAliveMessageTimerCallback()
|
|
|
|
{
|
|
|
|
keepAliveMessageTimerSemaphore = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void attributesMessageTimerCallback()
|
|
|
|
{
|
|
|
|
attributesMessageTimerSemaphore = true;
|
|
|
|
}
|