diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40433be --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.pio +.vscode +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index e83b38b..d170018 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,42 @@ -# RF-Gateway +# RF gateway for ESP8266 +Gateway for data exchange between nRF24 devices and ESP-NOW network. + +## Features + +1. After turn on (or after rebooting) creates an access point named "RF gateway 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) and availability status (every 10 seconds) to the gateway. +3. Automatically adds gateway configuration to Home Assistan via MQTT discovery as a binary_sensor. +4. Automatically adds supported nRF24 sensors configurations to Home Assistan via MQTT discovery. +5. Possibility firmware update over OTA. +6. Web interface for settings. + +## Notes + +1. ESP-NOW mesh network based on the library [ZHNetwork](https://github.com/aZholtikov/ZHNetwork). +2. Regardless of the status of connection to gateway the device perform ESP-NOW node function. +3. For show the access point for setting or firmware update, send the command "update" to the device's root topic (example - "homeassistant/espnow_rf_gateway/E8DB849CA148"). Access point will be shown during 5 minutes. Similarly, for restart send the command "restart". +4. nRF24 connection: + +```text +GPIO04 - CE, GPIO15 - CSN, GPIO14 - SCK, GPIO12 - MISO, GPIO13 - MOSI. +``` + +## Attention + +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. +3. If encryption is used, the key must be set same of all another ESP-NOW devices in network. +4. Upload the "data" folder (with web interface) into the filesystem before flashing. + +## Supported devices + +1. [nRF24 Climate Sensor (BME280)](https://github.com/aZholtikov/RF-Climate-Sensor-BME280) +2. [nRF24 Climate Sensor (BMP280)](https://github.com/aZholtikov/RF-Climate-Sensor-BMP280) +3. nRF24 Climate Sensor (BME680) Coming soon. +4. [nRF24 Open/Close Sensor](https://github.com/aZholtikov/RF-Open-Close-Sensor) +5. nRF24 Plant Humidity Sensor Coming soon. +6. [nRF24 Touch Switch](https://github.com/aZholtikov/RF-Touch-Switch) +7. [nRF24 Water Leakage Sensor](https://github.com/aZholtikov/RF-Water-Leakage-Sensor) + +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 new file mode 100755 index 0000000..3b8888e --- /dev/null +++ b/data/function.js @@ -0,0 +1,60 @@ +var xmlHttp = createXmlHttpObject(); +function createXmlHttpObject() { + if (window.XMLHttpRequest) { + xmlHttp = new XMLHttpRequest(); + } else { + xmlHttp = new ActiveXObject('Microsoft.XMLHTTP'); + } + return xmlHttp; +} + +function load() { + if (xmlHttp.readyState == 0 || xmlHttp.readyState == 4) { + xmlHttp.open('GET', '/config', true); + xmlHttp.send(null); + xmlHttp.onload = function () { + jsonResponse = JSON.parse(xmlHttp.responseText); + loadBlock(); + } + } +} + +function loadBlock() { + newData = JSON.parse(xmlHttp.responseText); + data = document.getElementsByTagName('body')[0].innerHTML; + var newString; + for (var key in newData) { + newString = data.replace(new RegExp('{{' + key + '}}', 'g'), newData[key]); + data = newString; + } + document.getElementsByTagName('body')[0].innerHTML = newString; + setFirmvareValue('version', 'firmware'); + handleServerResponse(); +} + +function getValue(id) { + var value = document.getElementById(id).value; + return value; +} + +function sendRequest(submit, server) { + request = new XMLHttpRequest(); + request.open("GET", server, true); + request.send(); +} + +function saveSetting(submit) { + server = "/setting?deviceName=" + getValue('deviceName') + + "&espnowNetName=" + getValue('espnowNetName'); + sendRequest(submit, server); + alert("Please restart device for changes apply."); +} + +function restart(submit) { + server = "/restart"; + sendRequest(submit, server); +} + +function setFirmvareValue(id, value) { + document.getElementById(id).innerHTML = document.getElementById(value).value; +} \ No newline at end of file diff --git a/data/index.htm b/data/index.htm new file mode 100644 index 0000000..eb9c1af --- /dev/null +++ b/data/index.htm @@ -0,0 +1,39 @@ + + + + + + + + RF Gateway + + + +
+

RF Gateway

+
+

Firmware:

+

+ +
+ +
+

Device name:

+ +
+ +
+

ESP-NOW network name:

+ +
+ +
+ + +
+
+ + + \ No newline at end of file diff --git a/data/style.css b/data/style.css new file mode 100644 index 0000000..6d2831f --- /dev/null +++ b/data/style.css @@ -0,0 +1,88 @@ +p{ + margin: 0 0; +} + +body { + font-family: "Gill Sans", sans-serif; + background: rgb(255, 255, 255); +} + +.box { + width: 400px; + padding: 20px 20px; + margin: 20px auto; + background: #e0f5fb; + box-shadow: 4px 4px 30px rgba(0, 0, 0, 0.2); + border-radius: 10px; +} + +h1 { + color: rgb(65, 125, 238); + text-align: center; +} + +.text { + font-weight: 600; + flex-shrink: 0; + margin-right: 10px; + margin-left: 10px; + margin: 10px 0; +} + +.wrapper { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +input { + width: 48%; + min-height: 30px; + border-radius: 5px; + border: none; + margin-bottom: 10px; + margin-left: 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); + color: white; + transition: .5s; + margin-left: 0; + margin-top: 8px; +} + +.btn:hover { + background: rgb(65, 125, 238); + opacity: 0.5; + transform: translatey(-3px); +} + +#deviceName, +#espnowNetName { + width: 100%; +} + +#espnowNetName { + margin-bottom: 15px; +} + +.wrapper.wrapper--end { + align-items: baseline; +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..5d9cf28 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,29 @@ +[env:ESP-12E] +platform = espressif8266 +board = esp12e +framework = arduino +build_flags = -D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305 +board_build.filesystem = littlefs +board_build.ldscript = eagle.flash.4m1m.ld +lib_deps = + https://github.com/aZholtikov/ZHNetwork + https://github.com/aZholtikov/ZHConfig + https://github.com/aZholtikov/Async-Web-Server + https://github.com/bblanchon/ArduinoJson + https://github.com/nrf24/RF24 + +[env:ESP-12E-OTA] +platform = espressif8266 +board = esp12e +framework = arduino +build_flags = -D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305 +board_build.filesystem = littlefs +board_build.ldscript = eagle.flash.4m1m.ld +upload_port = 192.168.4.1 +upload_protocol = espota +lib_deps = + https://github.com/aZholtikov/ZHNetwork + https://github.com/aZholtikov/ZHConfig + https://github.com/aZholtikov/Async-Web-Server + https://github.com/bblanchon/ArduinoJson + https://github.com/nrf24/RF24 \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..29251bb --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,439 @@ +#include "ArduinoJson.h" +#include "ArduinoOTA.h" +#include "ESPAsyncWebServer.h" // https://github.com/aZholtikov/Async-Web-Server +#include "LittleFS.h" +#include "EEPROM.h" +#include "Ticker.h" +#include "RF24.h" +#include "ZHNetwork.h" +#include "ZHConfig.h" + +void onBroadcastReceiving(const char *data, const uint8_t *sender); +void onUnicastReceiving(const char *data, const uint8_t *sender); +void onConfirmReceiving(const uint8_t *target, const uint16_t id, const bool status); + +void loadConfig(void); +void saveConfig(void); +void setupWebServer(void); + +void sendAttributesMessage(void); +void sendKeepAliveMessage(void); +void sendConfigMessage(void); +void sendSensorConfigMessage(uint8_t unit, uint8_t haComponentType, uint8_t rfSensorType, uint16_t rfSensorId, uint8_t haSensorDeviceClass, String valueTemplate, + String unitOfMeasurement = "", uint16_t expireAfter = 0, String payloadOn = "", String payloadOff = ""); + +void checkRadioDataAvailability(void); + +typedef struct +{ + uint16_t id{0}; + char message[200]{0}; +} espnow_message_t; + +struct deviceConfig +{ + String espnowNetName{"DEFAULT"}; + String deviceName = "RF gateway " + String(ESP.getChipId(), HEX); +} config; + +std::vector espnowMessage; +std::vector configMessage; + +const String firmware{"1.0"}; + +bool wasMqttAvailable{false}; + +uint8_t gatewayMAC[6]{0}; + +ZHNetwork myNet; +AsyncWebServer webServer(80); +RF24 radio(4, 15); + +Ticker gatewayAvailabilityCheckTimer; +bool isGatewayAvailable{false}; +void gatewayAvailabilityCheckTimerCallback(void); + +Ticker apModeHideTimer; +void apModeHideTimerCallback(void); + +Ticker attributesMessageTimer; +bool attributesMessageTimerSemaphore{true}; +void attributesMessageTimerCallback(void); + +Ticker keepAliveMessageTimer; +bool keepAliveMessageTimerSemaphore{true}; +void keepAliveMessageTimerCallback(void); + +void setup() +{ + LittleFS.begin(); + + Serial.begin(115200); + + loadConfig(); + + radio.begin(); + radio.setChannel(120); + radio.setDataRate(RF24_250KBPS); + radio.setPALevel(RF24_PA_MAX); + radio.setPayloadSize(14); + radio.setAddressWidth(3); + radio.setCRCLength(RF24_CRC_8); + radio.openReadingPipe(0, 0xDDEEFF); + radio.startListening(); + + WiFi.setSleepMode(WIFI_NONE_SLEEP); + myNet.begin(config.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.setOnUnicastReceivingCallback(onUnicastReceiving); + myNet.setOnConfirmReceivingCallback(onConfirmReceiving); + + WiFi.mode(WIFI_AP_STA); + WiFi.softAP(("RF gateway " + String(ESP.getChipId(), HEX)).c_str(), "12345678"); + apModeHideTimer.once(300, apModeHideTimerCallback); + + setupWebServer(); + + ArduinoOTA.begin(); + + attributesMessageTimer.attach(60, attributesMessageTimerCallback); + keepAliveMessageTimer.attach(10, keepAliveMessageTimerCallback); +} + +void loop() +{ + if (attributesMessageTimerSemaphore) + sendAttributesMessage(); + if (keepAliveMessageTimerSemaphore) + sendKeepAliveMessage(); + if (isGatewayAvailable) + checkRadioDataAvailability(); + myNet.maintenance(); + ArduinoOTA.handle(); +} + +void onBroadcastReceiving(const char *data, const byte *sender) +{ + esp_now_payload_data_t incomingData; + memcpy(&incomingData, data, sizeof(esp_now_payload_data_t)); + if (incomingData.deviceType != ENDT_GATEWAY) + return; + if (myNet.macToString(gatewayMAC) != myNet.macToString(sender) && incomingData.payloadsType == ENPT_KEEP_ALIVE) + memcpy(&gatewayMAC, sender, 6); + if (myNet.macToString(gatewayMAC) == myNet.macToString(sender) && incomingData.payloadsType == ENPT_KEEP_ALIVE) + { + isGatewayAvailable = true; + DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message)); + deserializeJson(json, incomingData.message); + bool temp = json["MQTT"] == "online" ? true : false; + if (wasMqttAvailable != temp) + { + wasMqttAvailable = temp; + if (temp) + { + sendConfigMessage(); + sendAttributesMessage(); + sendKeepAliveMessage(); + } + } + gatewayAvailabilityCheckTimer.once(15, gatewayAvailabilityCheckTimerCallback); + } +} + +void onUnicastReceiving(const char *data, const byte *sender) +{ + esp_now_payload_data_t incomingData; + memcpy(&incomingData, data, sizeof(esp_now_payload_data_t)); + if (incomingData.deviceType != ENDT_GATEWAY || myNet.macToString(gatewayMAC) != myNet.macToString(sender)) + return; + if (incomingData.payloadsType == ENPT_UPDATE) + { + WiFi.softAP(("RF gateway " + String(ESP.getChipId(), HEX)).c_str(), "12345678", 1, 0); + webServer.begin(); + apModeHideTimer.once(300, apModeHideTimerCallback); + } + if (incomingData.payloadsType == ENPT_RESTART) + ESP.restart(); +} + +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) + espnowMessage.erase(espnowMessage.begin() + i); + else + { + message.id = myNet.sendUnicastMessage(message.message, gatewayMAC, true); + espnowMessage.at(i) = message; + } + } + } +} + +void loadConfig() +{ + ETS_GPIO_INTR_DISABLE(); + EEPROM.begin(4096); + if (EEPROM.read(4095) == 254) + { + EEPROM.get(0, config); + EEPROM.end(); + } + else + { + EEPROM.end(); + saveConfig(); + } + delay(50); + ETS_GPIO_INTR_ENABLE(); +} + +void saveConfig() +{ + ETS_GPIO_INTR_DISABLE(); + EEPROM.begin(4096); + EEPROM.write(4095, 254); + EEPROM.put(0, config); + EEPROM.end(); + delay(50); + ETS_GPIO_INTR_ENABLE(); +} + +void setupWebServer() +{ + webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(LittleFS, "/index.htm"); }); + + webServer.on("/function.js", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(LittleFS, "/function.js"); }); + + webServer.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(LittleFS, "/style.css"); }); + + webServer.on("/setting", HTTP_GET, [](AsyncWebServerRequest *request) + { + config.deviceName = request->getParam("deviceName")->value(); + config.espnowNetName = request->getParam("espnowNetName")->value(); + request->send(200); + saveConfig(); }); + + webServer.on("/config", HTTP_GET, [](AsyncWebServerRequest *request) + { + String configJson; + DynamicJsonDocument json(192); // To calculate the buffer size uses https://arduinojson.org/v6/assistant. + json["firmware"] = firmware; + json["espnowNetName"] = config.espnowNetName; + json["deviceName"] = config.deviceName; + serializeJsonPretty(json, configJson); + request->send(200, "application/json", configJson); }); + + webServer.on("/restart", HTTP_GET, [](AsyncWebServerRequest *request) + {request->send(200); + ESP.restart(); }); + + webServer.onNotFound([](AsyncWebServerRequest *request) + { request->send(404, "text/plain", "File Not Found"); }); + + webServer.begin(); +} + +void sendAttributesMessage() +{ + if (!isGatewayAvailable) + return; + attributesMessageTimerSemaphore = false; + uint32_t secs = millis() / 1000; + uint32_t mins = secs / 60; + uint32_t hours = mins / 60; + uint32_t days = hours / 24; + esp_now_payload_data_t outgoingData{ENDT_RF_GATEWAY, ENPT_ATTRIBUTES}; + espnow_message_t message; + DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message)); + json["Type"] = "RF gateway"; + json["MCU"] = "ESP8266"; + json["MAC"] = myNet.getNodeMac(); + json["Firmware"] = firmware; + json["Library"] = myNet.getFirmwareVersion(); + json["Uptime"] = "Days:" + String(days) + " Hours:" + String(hours - (days * 24)) + " Mins:" + String(mins - (hours * 60)); + serializeJsonPretty(json, outgoingData.message); + memcpy(&message.message, &outgoingData, sizeof(esp_now_payload_data_t)); + message.id = myNet.sendUnicastMessage(message.message, gatewayMAC, true); + + espnowMessage.push_back(message); +} + +void sendKeepAliveMessage() +{ + if (!isGatewayAvailable) + return; + keepAliveMessageTimerSemaphore = false; + esp_now_payload_data_t outgoingData{ENDT_RF_GATEWAY, ENPT_KEEP_ALIVE}; + espnow_message_t message; + memcpy(&message.message, &outgoingData, sizeof(esp_now_payload_data_t)); + message.id = myNet.sendUnicastMessage(message.message, gatewayMAC, true); + + espnowMessage.push_back(message); +} + +void sendConfigMessage() +{ + if (!isGatewayAvailable) + return; + esp_now_payload_data_t outgoingData{ENDT_RF_GATEWAY, ENPT_CONFIG}; + espnow_message_t message; + DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message)); + json[MCMT_DEVICE_NAME] = config.deviceName; + json[MCMT_DEVICE_UNIT] = 1; + json[MCMT_COMPONENT_TYPE] = HACT_BINARY_SENSOR; + json[MCMT_DEVICE_CLASS] = HABSDC_CONNECTIVITY; + json[MCMT_PAYLOAD_ON] = "online"; + json[MCMT_EXPIRE_AFTER] = 30; + serializeJsonPretty(json, outgoingData.message); + memcpy(&message.message, &outgoingData, sizeof(esp_now_payload_data_t)); + message.id = myNet.sendUnicastMessage(message.message, gatewayMAC, true); + + espnowMessage.push_back(message); +} + +void sendSensorConfigMessage(uint8_t unit, uint8_t haComponentType, uint8_t rfSensorType, uint16_t rfSensorId, uint8_t haSensorDeviceClass, String valueTemplate, + String unitOfMeasurement, uint16_t expireAfter, String payloadOn, String payloadOff) +{ + esp_now_payload_data_t outgoingData{ENDT_RF_SENSOR, ENPT_CONFIG}; + espnow_message_t message; + DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message)); + json[MCMT_DEVICE_UNIT] = unit; + json[MCMT_COMPONENT_TYPE] = haComponentType; + json[MCMT_RF_SENSOR_TYPE] = rfSensorType; + json[MCMT_RF_SENSOR_ID] = rfSensorId; + json[MCMT_DEVICE_CLASS] = haSensorDeviceClass; + json[MCMT_VALUE_TEMPLATE] = valueTemplate; + if (unitOfMeasurement != "") + json[MCMT_UNIT_OF_MEASUREMENT] = unitOfMeasurement; + if (expireAfter) + json[MCMT_EXPIRE_AFTER] = expireAfter; + if (payloadOn != "") + json[MCMT_PAYLOAD_ON] = payloadOn; + if (payloadOff != "") + json[MCMT_PAYLOAD_OFF] = payloadOff; + serializeJsonPretty(json, outgoingData.message); + memcpy(&message.message, &outgoingData, sizeof(esp_now_payload_data_t)); + message.id = myNet.sendUnicastMessage(message.message, gatewayMAC, true); + + espnowMessage.push_back(message); +} + +void checkRadioDataAvailability() +{ + if (radio.available()) + { + rf_transmitted_data_t receivedData; + radio.read(&receivedData, sizeof(rf_transmitted_data_t)); + + bool flag{false}; + for (uint16_t i{0}; i < configMessage.size(); ++i) + if (configMessage[i] == receivedData.sensor_id) + flag = true; + + if (!flag) + { + configMessage.push_back(receivedData.sensor_id); + if (receivedData.sensor_type == RFST_BME280) + { + sendSensorConfigMessage(1, HACT_SENSOR, RFST_BME280, receivedData.sensor_id, HASDC_VOLTAGE, "battery", "V", 375); + sendSensorConfigMessage(2, HACT_SENSOR, RFST_BME280, receivedData.sensor_id, HASDC_HUMIDITY, "humidity", "%", 375); + sendSensorConfigMessage(3, HACT_SENSOR, RFST_BME280, receivedData.sensor_id, HASDC_TEMPERATURE, "temperature", "°C", 375); + sendSensorConfigMessage(4, HACT_SENSOR, RFST_BME280, receivedData.sensor_id, HASDC_PRESSURE, "pressure", "мм", 375); + } + if (receivedData.sensor_type == RFST_BMP280) + { + sendSensorConfigMessage(1, HACT_SENSOR, RFST_BMP280, receivedData.sensor_id, HASDC_VOLTAGE, "battery", "V", 375); + sendSensorConfigMessage(2, HACT_SENSOR, RFST_BMP280, receivedData.sensor_id, HASDC_TEMPERATURE, "temperature", "°C", 375); + sendSensorConfigMessage(3, HACT_SENSOR, RFST_BMP280, receivedData.sensor_id, HASDC_PRESSURE, "pressure", "мм", 375); + } + if (receivedData.sensor_type == RFST_BME680) // Coming soon. + { + } + if (receivedData.sensor_type == RFST_TOUCH_SWITCH) + sendSensorConfigMessage(1, HACT_SENSOR, RFST_TOUCH_SWITCH, receivedData.sensor_id, HASDC_VOLTAGE, "battery", "V"); + if (receivedData.sensor_type == RFST_WATER_LEAKAGE) + { + sendSensorConfigMessage(1, HACT_SENSOR, RFST_WATER_LEAKAGE, receivedData.sensor_id, HASDC_VOLTAGE, "battery", "V"); + sendSensorConfigMessage(2, HACT_BINARY_SENSOR, RFST_WATER_LEAKAGE, receivedData.sensor_id, HABSDC_MOISTURE, "state", "", 4500, "ALARM", "DRY"); + } + if (receivedData.sensor_type == RFST_PLANT_HUMIDITY) // Coming soon. + { + } + if (receivedData.sensor_type == RFST_OPEN_CLOSE) + { + sendSensorConfigMessage(1, HACT_SENSOR, RFST_OPEN_CLOSE, receivedData.sensor_id, HASDC_VOLTAGE, "battery", "V"); + sendSensorConfigMessage(2, HACT_BINARY_SENSOR, RFST_OPEN_CLOSE, receivedData.sensor_id, HABSDC_DOOR, "state", "", 0, "OPEN", "CLOSE"); + } + } + + esp_now_payload_data_t outgoingData{ENDT_RF_GATEWAY, ENPT_FORWARD}; + espnow_message_t message; + DynamicJsonDocument json(sizeof(esp_now_payload_data_t::message)); + if (receivedData.sensor_type == RFST_BME280) + { + json["humidity"] = receivedData.value_2; + json["temperature"] = receivedData.value_3; + json["pressure"] = receivedData.value_4; + } + if (receivedData.sensor_type == RFST_BMP280) + { + json["temperature"] = receivedData.value_2; + json["pressure"] = receivedData.value_3; + } + if (receivedData.sensor_type == RFST_BME680) + { + json["humidity"] = receivedData.value_2; + json["temperature"] = receivedData.value_3; + json["pressure"] = receivedData.value_4; + json["quality"] = receivedData.value_5; + } + if (receivedData.sensor_type == RFST_WATER_LEAKAGE) + json["state"] = receivedData.value_2 == ALARM ? "ALARM" : "DRY"; + if (receivedData.sensor_type == RFST_PLANT_HUMIDITY) + json["humidity"] = receivedData.value_2; + if (receivedData.sensor_type == RFST_OPEN_CLOSE) + json["state"] = receivedData.value_2 == OPEN ? "OPEN" : "CLOSE"; + json["type"] = receivedData.sensor_type; + json["id"] = receivedData.sensor_id; + json["battery"] = double(receivedData.value_1) / 100; + serializeJsonPretty(json, outgoingData.message); + memcpy(&message.message, &outgoingData, sizeof(esp_now_payload_data_t)); + message.id = myNet.sendUnicastMessage(message.message, gatewayMAC, true); + + espnowMessage.push_back(message); + } +} + +void gatewayAvailabilityCheckTimerCallback() +{ + isGatewayAvailable = false; + memset(&gatewayMAC, 0, 6); + espnowMessage.clear(); + configMessage.clear(); +} + +void apModeHideTimerCallback() +{ + WiFi.softAP(("RF gateway " + String(ESP.getChipId(), HEX)).c_str(), "12345678", 1, 1); + webServer.end(); +} + +void attributesMessageTimerCallback() +{ + attributesMessageTimerSemaphore = true; +} + +void keepAliveMessageTimerCallback() +{ + keepAliveMessageTimerSemaphore = true; +} \ No newline at end of file