7 Commits
v1.0 ... v1.13

Author SHA1 Message Date
bcda1c9ea5 Version 1.13
Added encrypting messages.
2023-01-22 12:38:53 +03:00
7f51f05eb8 Version 1.12
Main code refactoring.
2023-01-14 10:57:25 +03:00
dfabdd080a Version 1.11
Web interface minor redesign.
Main code minor changes.
2023-01-12 12:27:38 +03:00
e551dfa0fa Minor changes 2023-01-10 10:07:37 +03:00
1231650fb8 Updated "Tested on" 2023-01-09 18:56:08 +03:00
d3de91d81e Version 1.1
Fixed one minor bug.
2023-01-06 13:03:50 +03:00
bf5b59c091 Minor changes 2023-01-04 18:01:50 +03:00
15 changed files with 101 additions and 109 deletions

View File

@ -4,7 +4,7 @@ ESP-NOW based light/led strip controller for ESP8266. Alternate firmware for Tuy
## Features ## Features
1. After turn on (or after rebooting) creates an access point named "ESP-NOW Light XXXXXXXXXXXX" with password "12345678" (IP 192.168.4.1). Access point will be shown during 5 minutes. The rest of the time access point is a hidden. 1. After turn on (or after rebooting) creates an access point named "ESP-NOW light XXXXXXXXXXXX" with password "12345678" (IP 192.168.4.1). Access point will be shown during 5 minutes. The rest of the time access point is a hidden.
2. Periodically transmission of system information (every 60 seconds), availability status (every 10 seconds) and state status (every 300 seconds) to the gateway. 2. Periodically transmission of system information (every 60 seconds), availability status (every 10 seconds) and state status (every 300 seconds) to the gateway.
3. Saves the last state when the power is turned off. Goes to the last state when the power is turned on. 3. Saves the last state when the power is turned off. Goes to the last state when the power is turned on.
4. Automatically adds light/led strip configuration to Home Assistan via MQTT discovery as a light. 4. Automatically adds light/led strip configuration to Home Assistan via MQTT discovery as a light.
@ -19,11 +19,12 @@ ESP-NOW based light/led strip controller for ESP8266. Alternate firmware for Tuy
## Tested on ## Tested on
Coming soon. See [here](https://github.com/aZholtikov/ESP-NOW-Light-Led-Strip/tree/main/hardware).
## Attention ## Attention
1. A gateway is required. For details see [ESP-NOW Gateway](https://github.com/aZholtikov/ESP-NOW-Gateway). 1. A gateway is required. For details see [ESP-NOW Gateway](https://github.com/aZholtikov/ESP-NOW-Gateway).
2. ESP-NOW network name must be set same of all another ESP-NOW devices in network. 2. ESP-NOW network name 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. If encryption is used, the key must be set same of all another ESP-NOW devices in network.
4. For using this firmware on Tuya/SmartLife WiFi light/led strip controllers, the WiFi module must be replaced with an ESP8266 compatible module (if necessary). 4. Upload the "data" folder (with web interface) into the filesystem before flashing.
5. For using this firmware on Tuya/SmartLife WiFi light/led strip controllers, the WiFi module must be replaced with an ESP8266 compatible module (if necessary).

View File

@ -5,12 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=0.9"> <meta name="viewport" content="width=device-width, initial-scale=0.9">
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script type="text/javascript" src="function.js"></script> <script type="text/javascript" src="function.js"></script>
<title>ESP-NOW Led/Light Strip</title> <title>ESP-NOW Light</title>
</head> </head>
<body onload="load();"> <body onload="load();">
<form class="box"> <form class="box">
<h1>ESP-NOW Led/Light Strip </h1> <h1>ESP-NOW Light </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>

View File

@ -1,3 +1,7 @@
p{
margin: 0 0;
}
body { body {
font-family: "Gill Sans", sans-serif; font-family: "Gill Sans", sans-serif;
background: rgb(255, 255, 255); background: rgb(255, 255, 255);
@ -21,6 +25,8 @@ h1 {
font-weight: 600; font-weight: 600;
flex-shrink: 0; flex-shrink: 0;
margin-right: 10px; margin-right: 10px;
margin-left: 10px;
margin: 10px 0;
} }
.text-select { .text-select {
@ -42,6 +48,7 @@ input {
border-radius: 5px; border-radius: 5px;
border: none; border: none;
margin-bottom: 10px; margin-bottom: 10px;
margin-left: 10px;
padding: 0 10px; padding: 0 10px;
color: rgb(0, 0, 0); color: rgb(0, 0, 0);
background: #a3e0f1; background: #a3e0f1;
@ -76,6 +83,8 @@ select:hover {
background: rgb(65, 125, 238); background: rgb(65, 125, 238);
color: white; color: white;
transition: .5s; transition: .5s;
margin-left: 0;
margin-top: 8px;
} }
.btn:hover { .btn:hover {
@ -89,6 +98,10 @@ select:hover {
width: 100%; width: 100%;
} }
#espnowNetName {
margin-bottom: 15px;
}
.wrapper.wrapper--end { .wrapper.wrapper--end {
align-items: baseline; align-items: baseline;
} }

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

13
hardware/README.md Normal file
View File

@ -0,0 +1,13 @@
# Tested on
1. AP-FUTURE RGB Led controller. Built on Tuya WiFi module WB3S (BK7231N chip). Replacement required. Performed replacement with ESP-12E. [Photo](https://github.com/aZholtikov/ESP-NOW-Light-Led-Strip/tree/main/hardware/AP-FUTURE_RGB). A connect EN to VCC is required. A pull-up to GND of GPIO15 is required (i used a 0805 10K SMD resistor on top of the module pins).
```text
Device type RGB
Red GPIO GPIO04
Green GPIO GPIO12
Blue GPIO GPIO14
```
2. FLYIDEA RGBW Light E27 15W (Coming soon)
3. FLYIDEA RGBWW Light E14 7W (Coming soon)

View File

@ -1,4 +1,4 @@
[env:esp8266] [env:ESP-12E]
platform = espressif8266 platform = espressif8266
board = esp12e board = esp12e
framework = arduino framework = arduino
@ -8,7 +8,7 @@ lib_deps =
bblanchon/ArduinoJson@^6.19.4 bblanchon/ArduinoJson@^6.19.4
me-no-dev/ESP Async WebServer@^1.2.3 me-no-dev/ESP Async WebServer@^1.2.3
[env:esp8266-ota] [env:ESP-12E-OTA]
platform = espressif8266 platform = espressif8266
board = esp12e board = esp12e
framework = arduino framework = arduino
@ -19,25 +19,3 @@ lib_deps =
https://github.com/aZholtikov/ZHConfig https://github.com/aZholtikov/ZHConfig
bblanchon/ArduinoJson@^6.19.4 bblanchon/ArduinoJson@^6.19.4
me-no-dev/ESP Async WebServer@^1.2.3 me-no-dev/ESP Async WebServer@^1.2.3
[env:esp8285]
platform = espressif8266
board = esp8285
framework = arduino
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
[env:esp8285-ota]
platform = espressif8266
board = esp8285
framework = arduino
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

View File

@ -7,7 +7,7 @@
void onBroadcastReceiving(const char *data, const uint8_t *sender); void onBroadcastReceiving(const char *data, const uint8_t *sender);
void onUnicastReceiving(const char *data, const uint8_t *sender); void onUnicastReceiving(const char *data, const uint8_t *sender);
void onConfirmReceiving(const uint8_t *target, const bool status); void onConfirmReceiving(const uint8_t *target, const uint16_t id, const bool status);
void loadConfig(void); void loadConfig(void);
void saveConfig(void); void saveConfig(void);
@ -18,15 +18,23 @@ void sendKeepAliveMessage(void);
void sendConfigMessage(void); void sendConfigMessage(void);
void sendStatusMessage(void); void sendStatusMessage(void);
String getValue(String data, char separator, byte index); String getValue(String data, char separator, uint8_t index);
void changeLedState(void); void changeLedState(void);
const String firmware{"1.0"}; typedef struct
{
uint16_t id{0};
char message[200]{0};
} espnow_message_t;
std::vector<espnow_message_t> espnowMessage;
const String firmware{"1.13"};
String espnowNetName{"DEFAULT"}; String espnowNetName{"DEFAULT"};
String deviceName{"ESP-NOW Light/Led Strip"}; String deviceName = "ESP-NOW light " + String(ESP.getChipId(), HEX);
uint8_t ledType{ENLT_NONE}; uint8_t ledType{ENLT_NONE};
bool ledStatus{false}; bool ledStatus{false};
@ -46,6 +54,9 @@ bool wasMqttAvailable{false};
uint8_t gatewayMAC[6]{0}; uint8_t gatewayMAC[6]{0};
const String payloadOn{"ON"};
const String payloadOff{"OFF"};
ZHNetwork myNet; ZHNetwork myNet;
AsyncWebServer webServer(80); AsyncWebServer webServer(80);
@ -59,23 +70,14 @@ void apModeHideTimerCallback(void);
Ticker attributesMessageTimer; Ticker attributesMessageTimer;
bool attributesMessageTimerSemaphore{true}; bool attributesMessageTimerSemaphore{true};
void attributesMessageTimerCallback(void); void attributesMessageTimerCallback(void);
Ticker attributesMessageResendTimer;
bool attributesMessageResendTimerSemaphore{false};
Ticker keepAliveMessageTimer; Ticker keepAliveMessageTimer;
bool keepAliveMessageTimerSemaphore{true}; bool keepAliveMessageTimerSemaphore{true};
void keepAliveMessageTimerCallback(void); void keepAliveMessageTimerCallback(void);
Ticker keepAliveMessageResendTimer;
bool keepAliveMessageResendTimerSemaphore{false};
Ticker configMessageResendTimer;
bool configMessageResendTimerSemaphore{false};
Ticker statusMessageTimer; Ticker statusMessageTimer;
bool statusMessageTimerSemaphore{true}; bool statusMessageTimerSemaphore{true};
void statusMessageTimerCallback(void); void statusMessageTimerCallback(void);
Ticker statusMessageResendTimer;
bool statusMessageResendTimerSemaphore{false};
void setup() void setup()
{ {
@ -100,13 +102,14 @@ void setup()
WiFi.setSleepMode(WIFI_NONE_SLEEP); WiFi.setSleepMode(WIFI_NONE_SLEEP);
myNet.begin(espnowNetName.c_str()); myNet.begin(espnowNetName.c_str());
// 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(onBroadcastReceiving); myNet.setOnBroadcastReceivingCallback(onBroadcastReceiving);
myNet.setOnUnicastReceivingCallback(onUnicastReceiving); myNet.setOnUnicastReceivingCallback(onUnicastReceiving);
myNet.setOnConfirmReceivingCallback(onConfirmReceiving); myNet.setOnConfirmReceivingCallback(onConfirmReceiving);
WiFi.mode(WIFI_AP_STA); WiFi.mode(WIFI_AP_STA);
WiFi.softAP(("ESP-NOW Light " + myNet.getNodeMac()).c_str(), "12345678", 1, 0); WiFi.softAP(("ESP-NOW light " + String(ESP.getChipId(), HEX)).c_str(), "12345678", 1, 0);
apModeHideTimer.once(300, apModeHideTimerCallback); apModeHideTimer.once(300, apModeHideTimerCallback);
setupWebServer(); setupWebServer();
@ -130,14 +133,14 @@ void loop()
ArduinoOTA.handle(); ArduinoOTA.handle();
} }
void onBroadcastReceiving(const char *data, const byte *sender) void onBroadcastReceiving(const char *data, const uint8_t *sender)
{ {
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.deviceType != ENDT_GATEWAY) if (incomingData.deviceType != ENDT_GATEWAY)
return; return;
if (myNet.macToString(gatewayMAC) != myNet.macToString(sender) && incomingData.payloadsType == ENPT_KEEP_ALIVE) if (myNet.macToString(gatewayMAC) != myNet.macToString(sender) && incomingData.payloadsType == ENPT_KEEP_ALIVE)
memcpy(gatewayMAC, sender, 6); memcpy(&gatewayMAC, sender, 6);
if (myNet.macToString(gatewayMAC) == myNet.macToString(sender) && incomingData.payloadsType == ENPT_KEEP_ALIVE) if (myNet.macToString(gatewayMAC) == myNet.macToString(sender) && incomingData.payloadsType == ENPT_KEEP_ALIVE)
{ {
isGatewayAvailable = true; isGatewayAvailable = true;
@ -154,7 +157,7 @@ void onBroadcastReceiving(const char *data, const byte *sender)
} }
} }
void onUnicastReceiving(const char *data, const byte *sender) void onUnicastReceiving(const char *data, const uint8_t *sender)
{ {
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));
@ -165,7 +168,7 @@ void onUnicastReceiving(const char *data, const byte *sender)
{ {
deserializeJson(json, incomingData.message); deserializeJson(json, incomingData.message);
if (json["set"]) if (json["set"])
ledStatus = json["set"] == "ON" ? true : false; ledStatus = json["set"] == payloadOn ? true : false;
if (json["brightness"]) if (json["brightness"])
brightness = json["brightness"]; brightness = json["brightness"];
if (json["temperature"]) if (json["temperature"])
@ -181,36 +184,26 @@ void onUnicastReceiving(const char *data, const byte *sender)
} }
if (incomingData.payloadsType == ENPT_UPDATE) if (incomingData.payloadsType == ENPT_UPDATE)
{ {
WiFi.softAP(("ESP-NOW Light " + myNet.getNodeMac()).c_str(), "12345678", 1, 0); WiFi.softAP(("ESP-NOW light " + String(ESP.getChipId(), HEX)).c_str(), "12345678", 1, 0);
webServer.begin();
apModeHideTimer.once(300, apModeHideTimerCallback); apModeHideTimer.once(300, apModeHideTimerCallback);
} }
if (incomingData.payloadsType == ENPT_RESTART) if (incomingData.payloadsType == ENPT_RESTART)
ESP.restart(); ESP.restart();
} }
void onConfirmReceiving(const uint8_t *target, const bool status) void onConfirmReceiving(const uint8_t *target, const uint16_t id, const bool status)
{ {
for (uint16_t i{0}; i < espnowMessage.size(); ++i)
{
espnow_message_t message = espnowMessage[i];
if (message.id == id)
if (status) if (status)
espnowMessage.erase(espnowMessage.begin() + i);
else
{ {
if (attributesMessageResendTimerSemaphore) message.id = myNet.sendUnicastMessage(message.message, gatewayMAC, true);
{ espnowMessage.at(i) = message;
attributesMessageResendTimerSemaphore = false;
attributesMessageResendTimer.detach();
}
if (keepAliveMessageResendTimerSemaphore)
{
keepAliveMessageResendTimerSemaphore = false;
keepAliveMessageResendTimer.detach();
}
if (configMessageResendTimerSemaphore)
{
configMessageResendTimerSemaphore = false;
configMessageResendTimer.detach();
}
if (statusMessageResendTimerSemaphore)
{
statusMessageResendTimerSemaphore = false;
statusMessageResendTimer.detach();
} }
} }
} }
@ -309,22 +302,19 @@ void sendAttributesMessage()
uint32_t hours = mins / 60; uint32_t hours = mins / 60;
uint32_t days = hours / 24; uint32_t days = hours / 24;
esp_now_payload_data_t outgoingData{ENDT_LED, ENPT_ATTRIBUTES}; esp_now_payload_data_t outgoingData{ENDT_LED, ENPT_ATTRIBUTES};
espnow_message_t message;
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json; StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
json["Type"] = "ESP-NOW Led/Light Strip"; json["Type"] = "ESP-NOW light";
json["MCU"] = "ESP8266"; json["MCU"] = "ESP8266";
json["MAC"] = myNet.getNodeMac(); json["MAC"] = myNet.getNodeMac();
json["Firmware"] = firmware; json["Firmware"] = firmware;
json["Library"] = myNet.getFirmwareVersion(); json["Library"] = myNet.getFirmwareVersion();
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}; serializeJsonPretty(json, outgoingData.message);
serializeJsonPretty(json, buffer); memcpy(&message.message, &outgoingData, sizeof(esp_now_payload_data_t));
memcpy(outgoingData.message, buffer, sizeof(esp_now_payload_data_t::message)); message.id = myNet.sendUnicastMessage(message.message, gatewayMAC, true);
char temp[sizeof(esp_now_payload_data_t)]{0};
memcpy(&temp, &outgoingData, sizeof(esp_now_payload_data_t));
myNet.sendUnicastMessage(temp, gatewayMAC, true);
attributesMessageResendTimerSemaphore = true; espnowMessage.push_back(message);
attributesMessageResendTimer.once(1, sendAttributesMessage);
} }
void sendKeepAliveMessage() void sendKeepAliveMessage()
@ -333,12 +323,11 @@ void sendKeepAliveMessage()
return; return;
keepAliveMessageTimerSemaphore = false; keepAliveMessageTimerSemaphore = false;
esp_now_payload_data_t outgoingData{ENDT_LED, ENPT_KEEP_ALIVE}; esp_now_payload_data_t outgoingData{ENDT_LED, ENPT_KEEP_ALIVE};
char temp[sizeof(esp_now_payload_data_t)]{0}; espnow_message_t message;
memcpy(&temp, &outgoingData, sizeof(esp_now_payload_data_t)); memcpy(&message.message, &outgoingData, sizeof(esp_now_payload_data_t));
myNet.sendUnicastMessage(temp, gatewayMAC, true); message.id = myNet.sendUnicastMessage(message.message, gatewayMAC, true);
keepAliveMessageResendTimerSemaphore = true; espnowMessage.push_back(message);
keepAliveMessageResendTimer.once(1, sendKeepAliveMessage);
} }
void sendConfigMessage() void sendConfigMessage()
@ -346,20 +335,19 @@ void sendConfigMessage()
if (!isGatewayAvailable) if (!isGatewayAvailable)
return; return;
esp_now_payload_data_t outgoingData{ENDT_LED, ENPT_CONFIG}; esp_now_payload_data_t outgoingData{ENDT_LED, ENPT_CONFIG};
espnow_message_t message;
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json; StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
json["name"] = deviceName; json["name"] = deviceName;
json["unit"] = 1; json["unit"] = 1;
json["type"] = getValueName(HACT_LIGHT); json["type"] = HACT_LIGHT;
json["class"] = ledType; json["class"] = ledType;
char buffer[sizeof(esp_now_payload_data_t::message)]{0}; json["payload_on"] = payloadOn;
serializeJsonPretty(json, buffer); json["payload_off"] = payloadOff;
memcpy(outgoingData.message, buffer, sizeof(esp_now_payload_data_t::message)); serializeJsonPretty(json, outgoingData.message);
char temp[sizeof(esp_now_payload_data_t)]{0}; memcpy(&message.message, &outgoingData, sizeof(esp_now_payload_data_t));
memcpy(&temp, &outgoingData, sizeof(esp_now_payload_data_t)); message.id = myNet.sendUnicastMessage(message.message, gatewayMAC, true);
myNet.sendUnicastMessage(temp, gatewayMAC, true);
configMessageResendTimerSemaphore = true; espnowMessage.push_back(message);
configMessageResendTimer.once(5, sendConfigMessage);
} }
void sendStatusMessage() void sendStatusMessage()
@ -368,28 +356,25 @@ void sendStatusMessage()
return; return;
statusMessageTimerSemaphore = false; statusMessageTimerSemaphore = false;
esp_now_payload_data_t outgoingData{ENDT_LED, ENPT_STATE}; esp_now_payload_data_t outgoingData{ENDT_LED, ENPT_STATE};
espnow_message_t message;
StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json; StaticJsonDocument<sizeof(esp_now_payload_data_t::message)> json;
json["state"] = ledStatus ? "ON" : "OFF"; json["state"] = ledStatus ? payloadOn : payloadOff;
json["brightness"] = brightness; json["brightness"] = brightness;
json["temperature"] = temperature; json["temperature"] = temperature;
json["rgb"] = String(red) + "," + String(green) + "," + String(blue); json["rgb"] = String(red) + "," + String(green) + "," + String(blue);
char buffer[sizeof(esp_now_payload_data_t::message)]{0}; serializeJsonPretty(json, outgoingData.message);
serializeJsonPretty(json, buffer); memcpy(&message.message, &outgoingData, sizeof(esp_now_payload_data_t));
memcpy(&outgoingData.message, &buffer, sizeof(esp_now_payload_data_t::message)); message.id = myNet.sendUnicastMessage(message.message, gatewayMAC, true);
char temp[sizeof(esp_now_payload_data_t)]{0};
memcpy(&temp, &outgoingData, sizeof(esp_now_payload_data_t));
myNet.sendUnicastMessage(temp, gatewayMAC, true);
statusMessageResendTimerSemaphore = true; espnowMessage.push_back(message);
statusMessageResendTimer.once(1, sendStatusMessage);
} }
String getValue(String data, char separator, byte index) String getValue(String data, char separator, uint8_t index)
{ {
byte found{0}; uint8_t found{0};
int strIndex[]{0, -1}; int strIndex[]{0, -1};
int maxIndex = data.length() - 1; int maxIndex = data.length() - 1;
for (byte i{0}; i <= maxIndex && found <= index; i++) for (uint8_t i{0}; i <= maxIndex && found <= index; i++)
if (data.charAt(i) == separator || i == maxIndex) if (data.charAt(i) == separator || i == maxIndex)
{ {
found++; found++;
@ -465,12 +450,14 @@ void changeLedState(void)
void gatewayAvailabilityCheckTimerCallback() void gatewayAvailabilityCheckTimerCallback()
{ {
isGatewayAvailable = false; isGatewayAvailable = false;
memset(gatewayMAC, 0, 6); memset(&gatewayMAC, 0, 6);
espnowMessage.clear();
} }
void apModeHideTimerCallback() void apModeHideTimerCallback()
{ {
WiFi.softAP(("ESP-NOW Light " + myNet.getNodeMac()).c_str(), "12345678", 1, 1); WiFi.softAP(("ESP-NOW light " + String(ESP.getChipId(), HEX)).c_str(), "12345678", 1, 1);
webServer.end();
} }
void attributesMessageTimerCallback() void attributesMessageTimerCallback()