Files
OpenMQTT-Gateway/main/ZwebUI.ino
T
FlorianandFlorian c234afa9c8 [SYS] Fix connection validation on unknown indexes and improve erase method (#2075)
Before this fix, connections with Index 1 and 2 were supposed valid when not present in the configuration, generating attempts to connect with those indexes.
Also improve the erase config by adding an erase when the config file is not present (ESP is connected to WiFi without the config file). This way the ESP will not reconnect to the WiFi AP.

Co-authored-by: Florian <1technophile@users.noreply.github.com>
2024-10-01 11:48:39 -05:00

2530 lines
88 KiB
Arduino

/*
Theengs OpenMQTTGateway - We Unite Sensors in One Open-Source Interface
Act as a gateway between your 433mhz, infrared IR, BLE, LoRa signal and one interface like an MQTT broker
Send and receiving command by MQTT
Copyright: (c)Florian ROBERT
This file is part of OpenMQTTGateway.
OpenMQTTGateway is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenMQTTGateway is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "User_config.h"
#if defined(ZwebUI) && defined(ESP32)
# include <ArduinoJson.h>
# include <SPIFFS.h>
# include <WebServer.h> // Docs for this are here - https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer
# include "ArduinoLog.h"
# include "config_WebContent.h"
# include "config_WebUI.h"
# if defined(ZgatewayCloud)
# include "config_Cloud.h"
# endif
# if defined(ZdisplaySSD1306)
# include "config_SSD1306.h"
# endif
uint32_t requestToken = 0;
QueueHandle_t webUIQueue;
WebServer server(80);
/*------------------- External functions ----------------------*/
extern void erase(bool restart);
extern unsigned long uptime();
/*------------------- Web Console Globals ----------------------*/
# define ROW_LENGTH 1024
const uint16_t LOG_BUFFER_SIZE = 6096;
uint32_t log_buffer_pointer;
void* log_buffer_mutex;
char log_buffer[LOG_BUFFER_SIZE]; // Log buffer in HEAP
const uint16_t MAX_LOGSZ = LOG_BUFFER_SIZE - 96;
const uint16_t TOPSZ = 151; // Max number of characters in topic string
uint8_t masterlog_level; // Master log level used to override set log level
bool reset_web_log_flag = false; // Reset web console log
const char* www_username = WEBUI_LOGIN;
String authFailResponse = "Authentication Failed";
bool webUISecure = WEBUI_AUTH;
boolean displayMetric = DISPLAY_METRIC;
/*********************************************************************************************\
* ESP32 AutoMutex
\*********************************************************************************************/
//////////////////////////////////////////
// automutex.
// create a mute in your driver with:
// void *mutex = nullptr;
//
// then protect any function with
// TasAutoMutex m(&mutex, "somename");
// - mutex is automatically initialised if not already intialised.
// - it will be automagically released when the function is over.
// - the same thread can take multiple times (recursive).
// - advanced options m.give() and m.take() allow you fine control within a function.
// - if take=false at creat, it will not be initially taken.
// - name is used in serial log of mutex deadlock.
// - maxWait in ticks is how long it will wait before failing in a deadlock scenario (and then emitting on serial)
class TasAutoMutex {
SemaphoreHandle_t mutex;
bool taken;
int maxWait;
const char* name;
public:
TasAutoMutex(SemaphoreHandle_t* mutex, const char* name = "", int maxWait = 40, bool take = true);
~TasAutoMutex();
void give();
void take();
static void init(SemaphoreHandle_t* ptr);
};
//////////////////////////////////////////
TasAutoMutex::TasAutoMutex(SemaphoreHandle_t* mutex, const char* name, int maxWait, bool take) {
if (mutex) {
if (!(*mutex)) {
TasAutoMutex::init(mutex);
}
this->mutex = *mutex;
this->maxWait = maxWait;
this->name = name;
if (take) {
this->taken = xSemaphoreTakeRecursive(this->mutex, this->maxWait);
// if (!this->taken){
// Serial.printf("\r\nMutexfail %s\r\n", this->name);
// }
}
} else {
this->mutex = (SemaphoreHandle_t) nullptr;
}
}
TasAutoMutex::~TasAutoMutex() {
if (this->mutex) {
if (this->taken) {
xSemaphoreGiveRecursive(this->mutex);
this->taken = false;
}
}
}
void TasAutoMutex::init(SemaphoreHandle_t* ptr) {
SemaphoreHandle_t mutex = xSemaphoreCreateRecursiveMutex();
(*ptr) = mutex;
// needed, else for ESP8266 as we will initialis more than once in logging
// (*ptr) = (void *) 1;
}
void TasAutoMutex::give() {
if (this->mutex) {
if (this->taken) {
xSemaphoreGiveRecursive(this->mutex);
this->taken = false;
}
}
}
void TasAutoMutex::take() {
if (this->mutex) {
if (!this->taken) {
this->taken = xSemaphoreTakeRecursive(this->mutex, this->maxWait);
// if (!this->taken){
// Serial.printf("\r\nMutexfail %s\r\n", this->name);
// }
}
}
}
// Get span until single character in string
size_t strchrspn(const char* str1, int character) {
size_t ret = 0;
char* start = (char*)str1;
char* end = strchr(str1, character);
if (end) ret = end - start;
return ret;
}
int WifiGetRssiAsQuality(int rssi) {
int quality = 0;
if (rssi <= -100) {
quality = 0;
} else if (rssi >= -50) {
quality = 100;
} else {
quality = 2 * (rssi + 100);
}
return quality;
}
char* GetTextIndexed(char* destination, size_t destination_size, uint32_t index, const char* haystack) {
// Returns empty string if not found
// Returns text of found
char* write = destination;
const char* read = haystack;
index++;
while (index--) {
size_t size = destination_size - 1;
write = destination;
char ch = '.';
while ((ch != '\0') && (ch != '|')) {
ch = pgm_read_byte(read++);
if (size && (ch != '|')) {
*write++ = ch;
size--;
}
}
if (0 == ch) {
if (index) {
write = destination;
}
break;
}
}
*write = '\0';
return destination;
}
const char kUnescapeCode[] = "&><\"\'\\";
const char kEscapeCode[] PROGMEM = "&amp;|&gt;|&lt;|&quot;|&apos;|&#92;";
String HtmlEscape(const String unescaped) {
char escaped[10];
size_t ulen = unescaped.length();
String result;
result.reserve(ulen); // pre-reserve the required space to avoid mutiple reallocations
for (size_t i = 0; i < ulen; i++) {
char c = unescaped[i];
char* p = strchr(kUnescapeCode, c);
if (p != nullptr) {
result += GetTextIndexed(escaped, sizeof(escaped), p - kUnescapeCode, kEscapeCode);
} else {
result += c;
}
}
return result;
}
void AddLogData(uint32_t loglevel, const char* log_data, const char* log_data_payload = nullptr, const char* log_data_retained = nullptr) {
// Store log_data in buffer
// To lower heap usage log_data_payload may contain the payload data from MqttPublishPayload()
// and log_data_retained may contain optional retained message from MqttPublishPayload()
# ifdef ESP32
// this takes the mutex, and will be release when the class is destroyed -
// i.e. when the functon leaves You CAN call mutex.give() to leave early.
TasAutoMutex mutex((SemaphoreHandle_t*)&log_buffer_mutex);
# endif // ESP32
char empty[2] = {0};
if (!log_data_payload) {
log_data_payload = empty;
}
if (!log_data_retained) {
log_data_retained = empty;
}
if (!log_buffer) {
return;
} // Leave now if there is no buffer available
// Delimited, zero-terminated buffer of log lines.
// Each entry has this format: [index][loglevel][log data]['\1']
// Truncate log messages longer than MAX_LOGSZ which is the log buffer size minus 64 spare
uint32_t log_data_len = strlen(log_data) + strlen(log_data_payload) + strlen(log_data_retained);
char too_long[TOPSZ];
if (log_data_len > MAX_LOGSZ) {
snprintf_P(too_long, sizeof(too_long) - 20, PSTR("%s%s"), log_data, log_data_payload); // 20 = strlen("... 123456 truncated")
snprintf_P(too_long, sizeof(too_long), PSTR("%s... %d truncated"), too_long, log_data_len);
log_data = too_long;
log_data_payload = empty;
log_data_retained = empty;
}
log_buffer_pointer &= 0xFF;
if (!log_buffer_pointer) {
log_buffer_pointer++; // Index 0 is not allowed as it is the end of char string
}
while (log_buffer_pointer == log_buffer[0] || // If log already holds the next index, remove it
strlen(log_buffer) + strlen(log_data) + strlen(log_data_payload) + strlen(log_data_retained) + 4 > LOG_BUFFER_SIZE) // 4 = log_buffer_pointer + '\1' + '\0'
{
char* it = log_buffer;
it++; // Skip log_buffer_pointer
it += strchrspn(it, '\1'); // Skip log line
it++; // Skip delimiting "\1"
memmove(log_buffer, it, LOG_BUFFER_SIZE - (it - log_buffer)); // Move buffer forward to remove oldest log line
}
snprintf_P(log_buffer, LOG_BUFFER_SIZE, PSTR("%s%c%c%s%s%s%s\1"),
log_buffer, log_buffer_pointer++, '0' + loglevel, "", log_data, log_data_payload, log_data_retained);
log_buffer_pointer &= 0xFF;
if (!log_buffer_pointer) {
log_buffer_pointer++; // Index 0 is not allowed as it is the end of char string
}
}
bool NeedLogRefresh(uint32_t req_loglevel, uint32_t index) {
if (!log_buffer) {
return false;
} // Leave now if there is no buffer available
# ifdef ESP32
// this takes the mutex, and will be release when the class is destroyed -
// i.e. when the functon leaves You CAN call mutex.give() to leave early.
TasAutoMutex mutex((SemaphoreHandle_t*)&log_buffer_mutex);
# endif // ESP32
// Skip initial buffer fill
if (strlen(log_buffer) < LOG_BUFFER_SIZE / 2) {
return false;
}
char* line;
size_t len;
if (!GetLog(req_loglevel, &index, &line, &len)) {
return false;
}
return ((line - log_buffer) < LOG_BUFFER_SIZE / 4);
}
bool GetLog(uint32_t req_loglevel, uint32_t* index_p, char** entry_pp, size_t* len_p) {
if (!log_buffer) {
return false;
} // Leave now if there is no buffer available
if (uptime() < 3) {
return false;
} // Allow time to setup correct log level
uint32_t index = *index_p;
if (!req_loglevel || (index == log_buffer_pointer)) {
return false;
}
# ifdef ESP32
// this takes the mutex, and will be release when the class is destroyed -
// i.e. when the functon leaves You CAN call mutex.give() to leave early.
TasAutoMutex mutex((SemaphoreHandle_t*)&log_buffer_mutex);
# endif // ESP32
if (!index) { // Dump all
index = log_buffer[0];
}
do {
size_t len = 0;
uint32_t loglevel = 0;
char* entry_p = log_buffer;
do {
uint32_t cur_idx = *entry_p;
entry_p++;
size_t tmp = strchrspn(entry_p, '\1');
tmp++; // Skip terminating '\1'
if (cur_idx == index) { // Found the requested entry
loglevel = *entry_p - '0';
entry_p++; // Skip loglevel
len = tmp - 1;
break;
}
entry_p += tmp;
} while (entry_p < log_buffer + LOG_BUFFER_SIZE && *entry_p != '\0');
index++;
if (index > 255) {
index = 1;
} // Skip 0 as it is not allowed
*index_p = index;
if ((len > 0) &&
(loglevel <= req_loglevel) &&
(masterlog_level <= req_loglevel)) {
*entry_pp = entry_p;
*len_p = len;
return true;
}
delay(0);
} while (index != log_buffer_pointer);
return false;
}
/*------------------- Local functions ----------------------*/
# ifdef WEBUI_DEVELOPMENT
//format bytes
String formatBytes(size_t bytes) {
if (bytes < 1024) {
return String(bytes) + "B";
} else if (bytes < (1024 * 1024)) {
return String(bytes / 1024.0) + "KB";
} else if (bytes < (1024 * 1024 * 1024)) {
return String(bytes / 1024.0 / 1024.0) + "MB";
} else {
return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB";
}
}
bool exists(String path) {
bool yes = false;
File file = FILESYSTEM.open(path, "r");
if (!file.isDirectory()) {
yes = true;
}
file.close();
return yes;
}
# endif
/**
* @brief / - Page
*
*/
void handleRoot() {
WEBUI_TRACE_LOG(F("handleRoot: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
if (server.hasArg("m")) {
if (currentWebUIMessage) {
server.send(200, "application/json", "{t}{s}<b>" + String(currentWebUIMessage->title) + "</b>{e}{s}" + String(currentWebUIMessage->line1) + "{e}{s}" + String(currentWebUIMessage->line2) + "{e}{s}" + String(currentWebUIMessage->line3) + "{e}{s}" + String(currentWebUIMessage->line4) + "{e}</table>");
} else {
server.send(200, "application/json", "{t}{s}Uptime:{m}" + String(uptime()) + "{e}</table>");
}
} else if (server.hasArg("rst")) { // TODO: This should redirect to the RST page
Log.warning(F("[WebUI] Restart" CR));
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Restart").c_str());
String response = String(buffer);
response += String(restart_script);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, reset_body, jsonChar, gateway_name, "Restart");
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
delay(2000); // Wait for web page to be sent before
ESPRestart(5);
} else {
// WEBUI_TRACE_LOG(F("Arguments %s" CR), message);
server.send(200, "text/plain", "00:14:36.767 RSL: RESULT = {\"Topic\":\"topic\"}");
}
} else {
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Main Menu").c_str());
String response = String(buffer);
response += String(root_script);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, root_body, jsonChar, gateway_name);
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
}
/**
* @brief /CN - Configuration Page
*
*/
void handleCN() {
WEBUI_SECURE
WEBUI_TRACE_LOG(F("handleCN: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleCN Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
} else {
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configuration").c_str());
String response = String(buffer);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_body, jsonChar, gateway_name);
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
}
/**
* @brief /WU - Configuration Page
* T: handleWU: uri: /wu, args: 3, method: 1
* T: handleWU Arg: 0, dm=on - displayMetric
* T: handleWU Arg: 1, sw=on - webUISecure
* T: handleWU Arg: 2, save=
*/
void handleWU() {
WEBUI_TRACE_LOG(F("handleWU: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleWU Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
bool update = false;
if (displayMetric != server.hasArg("dm")) {
update = true;
}
displayMetric = server.hasArg("dm");
if (webUISecure != server.hasArg("sw")) {
update = true;
}
webUISecure = server.hasArg("sw");
if (server.hasArg("save") && update) {
WebUIConfig_save();
}
}
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure WebUI").c_str());
String response = String(buffer);
response += String(script);
response += String(style);
int logLevel = Log.getLevel();
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_webui_body, jsonChar, gateway_name, (displayMetric ? "checked" : ""), (webUISecure ? "checked" : ""));
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
/**
* @brief /WI - Configure WiFi Page
* T: handleWI: uri: /wi, args: 4, method: 1
* T: handleWI Arg: 0, s1=SSID
* T: handleWI Arg: 1, p1=xxxxxx
* T: handleWI Arg: 3, save=
*/
void handleWI() {
WEBUI_TRACE_LOG(F("handleWI: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
String WiFiScan = "";
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleWI Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
if (server.hasArg("scan")) {
bool limitScannedNetworks = true;
int n = WiFi.scanNetworks();
WEBUI_TRACE_LOG(F("handleWI scan: found %d" CR), n);
if (0 == n) {
// WSContentSend_P(PSTR(D_NO_NETWORKS_FOUND));
// limitScannedNetworks = false; // in order to show D_SCAN_FOR_WIFI_NETWORKS
} else {
//sort networks
int indices[n];
for (uint32_t i = 0; i < n; i++) {
indices[i] = i;
}
// RSSI SORT
for (uint32_t i = 0; i < n; i++) {
for (uint32_t j = i + 1; j < n; j++) {
if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) {
std::swap(indices[i], indices[j]);
}
}
}
uint32_t networksToShow = n;
if ((limitScannedNetworks) && (networksToShow > MAX_WIFI_NETWORKS_TO_SHOW)) {
networksToShow = MAX_WIFI_NETWORKS_TO_SHOW;
}
for (uint32_t i = 0; i < n; i++) {
if (-1 == indices[i]) {
continue;
}
String cssid = WiFi.SSID(indices[i]);
uint32_t cschn = WiFi.channel(indices[i]);
for (uint32_t j = i + 1; j < n; j++) {
if ((cssid == WiFi.SSID(indices[j])) && (cschn == WiFi.channel(indices[j]))) {
WEBUI_TRACE_LOG(F("handleWI scan: duplicate %s" CR), WiFi.SSID(indices[j]).c_str());
indices[j] = -1; // set dup aps to index -1
}
}
}
//display networks in page
for (uint32_t i = 0; i < networksToShow; i++) {
if (-1 == indices[i]) {
continue;
} // skip dups
int32_t rssi = WiFi.RSSI(indices[i]);
WEBUI_TRACE_LOG(F("D_LOG_WIFI D_SSID %s, D_BSSID %s, D_CHANNEL %d, D_RSSI %d" CR),
WiFi.SSID(indices[i]).c_str(), WiFi.BSSIDstr(indices[i]).c_str(), WiFi.channel(indices[i]), rssi);
int quality = WifiGetRssiAsQuality(rssi);
String ssid_copy = WiFi.SSID(indices[i]);
if (!ssid_copy.length()) {
ssid_copy = F("no_name");
}
WiFiScan += "<div><a href='#p' onclick='c(this)'>" + HtmlEscape(ssid_copy) + "</a>&nbsp;(" + WiFi.channel(indices[i]) + ")&nbsp<span class='q'>" + quality + "% (" + rssi + " dBm)</span></div>";
}
}
WEBUI_TRACE_LOG(F("handleWI scan: results %s" CR), WiFiScan.c_str());
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure WiFi").c_str());
String response = String(buffer);
response += String(wifi_script);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_wifi_body, jsonChar, gateway_name, WiFiScan.c_str(), WiFi.SSID().c_str());
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
return;
} else if (server.hasArg("save")) {
StaticJsonDocument<JSON_MSG_BUFFER> WEBtoSYSBuffer;
JsonObject WEBtoSYS = WEBtoSYSBuffer.to<JsonObject>();
bool update = false;
if (server.hasArg("s1")) {
WEBtoSYS["wifi_ssid"] = server.arg("s1");
if (strncmp((char*)WiFi.SSID().c_str(), server.arg("s1").c_str(), parameters_size)) {
update = true;
}
}
if (server.hasArg("p1")) {
WEBtoSYS["wifi_pass"] = server.arg("p1");
if (strncmp((char*)WiFi.psk().c_str(), server.arg("p1").c_str(), parameters_size)) {
update = true;
}
}
if (update) {
String topic = String(mqtt_topic) + String(gateway_name) + String(subjectMQTTtoSYSset);
Log.warning(F("[WebUI] Save WiFi and Restart" CR));
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Save WiFi and Restart").c_str());
String response = String(buffer);
response += String(restart_script);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, reset_body, jsonChar, gateway_name, "Save WiFi and Restart");
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
delay(2000); // Wait for web page to be sent before
XtoSYS((char*)topic.c_str(), WEBtoSYS);
return;
} else {
Log.warning(F("[WebUI] No changes" CR));
}
}
}
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure WiFi").c_str());
String response = String(buffer);
response += String(wifi_script);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_wifi_body, jsonChar, gateway_name, WiFiScan.c_str(), WiFi.SSID().c_str());
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
/**
* @brief /MQ - Configure MQTT Page
* T: handleMQ: uri: /mq, args: 8, method: 1
* T: handleMQ Arg: 0, mh=192.168.1.11
* T: handleMQ Arg: 1, ml=1883
* T: handleMQ Arg: 2, mu=1234
* T: handleMQ Arg: 3, mp=
* T: handleMQ Arg: 4, sc=on
* T: handleMQ Arg: 5, h=
* T: handleMQ Arg: 6, mt=home/
* T: handleMQ Arg: 7 dp=homeassistant (#ifdef ZmqttDiscovery)
* T: handleMQ Arg: 8, save=
*/
void handleMQ() {
WEBUI_TRACE_LOG(F("handleMQ: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleMQ Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
if (server.hasArg("save")) {
StaticJsonDocument<JSON_MSG_BUFFER> WEBtoSYSBuffer;
JsonObject WEBtoSYS = WEBtoSYSBuffer.to<JsonObject>();
bool update = false;
# if !MQTT_BROKER_MODE
if (server.hasArg("mh")) {
WEBtoSYS["mqtt_server"] = server.arg("mh");
if (strncmp(cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_server, server.arg("mh").c_str(), parameters_size)) {
update = true;
}
}
if (server.hasArg("ml")) {
WEBtoSYS["mqtt_port"] = server.arg("ml");
if (strncmp(cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_port, server.arg("ml").c_str(), 6)) {
update = true;
}
}
if (server.hasArg("mu")) {
WEBtoSYS["mqtt_user"] = server.arg("mu");
if (strncmp(cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_user, server.arg("mu").c_str(), parameters_size)) {
update = true;
}
}
if (server.hasArg("mp")) {
WEBtoSYS["mqtt_pass"] = server.arg("mp");
if (strncmp(cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_pass, server.arg("mp").c_str(), parameters_size)) {
update = true;
}
}
// SC - Secure Connection argument is only present when true
if (cnt_parameters_array[CNT_DEFAULT_INDEX].isConnectionSecure != server.hasArg("sc")) {
update = true;
}
WEBtoSYS["mqtt_secure"] = server.hasArg("sc");
if (!update) {
Log.warning(F("[WebUI] clearing" CR));
for (JsonObject::iterator it = WEBtoSYS.begin(); it != WEBtoSYS.end(); ++it) {
WEBtoSYS.remove(it);
}
}
# endif
if (server.hasArg("h")) {
WEBtoSYS["gateway_name"] = server.arg("h");
if (strncmp(gateway_name, server.arg("h").c_str(), parameters_size)) {
update = true;
}
}
if (server.hasArg("mt")) {
WEBtoSYS["mqtt_topic"] = server.arg("mt");
if (strncmp(mqtt_topic, server.arg("mt").c_str(), parameters_size)) {
update = true;
}
}
# ifdef ZmqttDiscovery
if (server.hasArg("dp")) {
WEBtoSYS["discovery_prefix"] = server.arg("dp");
if (strncmp(discovery_prefix, server.arg("dp").c_str(), parameters_size)) {
update = true;
}
}
# endif
# ifndef ESPWifiManualSetup
if (update) {
Log.warning(F("[WebUI] Save MQTT and Reconnect" CR));
WEBtoSYS["cnt_index"] = CNT_DEFAULT_INDEX;
WEBtoSYS["save_cnt"] = true;
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Save MQTT and Reconnect").c_str());
String response = String(buffer);
response += String(restart_script);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, reset_body, jsonChar, gateway_name, "Save MQTT and Reconnect");
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
delay(2000); // Wait for web page to be sent before
String topic = String(mqtt_topic) + String(gateway_name) + String(subjectMQTTtoSYSset);
XtoSYS((char*)topic.c_str(), WEBtoSYS);
return;
} else {
Log.warning(F("[WebUI] No changes" CR));
}
# endif
}
}
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure MQTT").c_str());
String response = String(buffer);
response += String(script);
response += String(style);
// mqtt server (mh), mqtt port (ml), mqtt username (mu), mqtt password (mp), secure connection (sc), server certificate (msc), mqtt topic (mt), discovery prefix (dp) (last one only #ifdef ZmqttDiscovery)
# if MQTT_BROKER_MODE
# ifdef ZmqttDiscovery
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_mqtt_body, jsonChar, gateway_name, "", "1883", "", "", gateway_name, mqtt_topic, discovery_prefix);
# else
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_mqtt_body, jsonChar, gateway_name, "", "1883", "", "", gateway_name, mqtt_topic);
# endif
# else
# ifdef ZmqttDiscovery
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_mqtt_body, jsonChar, gateway_name, cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_server, cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_port, cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_user, (cnt_parameters_array[CNT_DEFAULT_INDEX].isConnectionSecure ? "checked" : ""), gateway_name, mqtt_topic, discovery_prefix);
# else
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_mqtt_body, jsonChar, gateway_name, cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_server, cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_port, cnt_parameters_array[CNT_DEFAULT_INDEX].mqtt_user, (cnt_parameters_array[CNT_DEFAULT_INDEX].isConnectionSecure ? "checked" : ""), gateway_name, mqtt_topic);
# endif
# endif
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
# ifndef ESPWifiManualSetup
/**
* @brief /CG - Configure gateway Page
* T: handleCG: uri: /gw, args: 2, method: 1
* T: handleCG Arg: 0, gp=1234
* T: handleCG Arg: 1, save=
*/
void handleCG() {
WEBUI_TRACE_LOG(F("handleCG: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
bool update = false;
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
JsonObject WEBtoSYS = jsonBuffer.to<JsonObject>();
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleCG Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
if (server.hasArg("save") && server.hasArg("gp") && strcmp(ota_pass, server.arg("gp").c_str())) {
strncpy(ota_pass, server.arg("gp").c_str(), parameters_size);
WEBtoSYS["gw_pass"] = ota_pass;
update = true;
}
}
if (update) {
Log.warning(F("[WebUI] Save Password and Restart" CR));
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Save Password and Restart").c_str());
String response = String(buffer);
response += String(restart_script);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, reset_body, jsonChar, gateway_name, "Save Password and Restart");
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
delay(2000); // Wait for web page to be sent before
String topic = String(mqtt_topic) + String(gateway_name) + String(subjectMQTTtoSYSset);
XtoSYS((char*)topic.c_str(), WEBtoSYS);
} else {
Log.warning(F("[WebUI] No changes" CR));
}
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure gateway").c_str());
String response = String(buffer);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_gateway_body, jsonChar, gateway_name, ota_pass);
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
# endif
/**
* @brief /LO - Configure Logging Page
* T: handleLO: uri: /lo, args: 2, method: 1
* T: handleLO Arg: 0, lo=5
* T: handleLO Arg: 1, save=
*/
void handleLO() {
WEBUI_TRACE_LOG(F("handleLO: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleLO Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
if (server.hasArg("save") && server.hasArg("lo") && server.arg("lo").toInt() != Log.getLevel()) {
Log.fatal(F("[WebUI] Log level changed to: %d" CR), server.arg("lo").toInt());
Log.setLevel(server.arg("lo").toInt());
}
}
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure Logging").c_str());
String response = String(buffer);
response += String(script);
response += String(style);
int logLevel = Log.getLevel();
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_logging_body, jsonChar, gateway_name, (logLevel == 0 ? "selected" : ""), (logLevel == 1 ? "selected" : ""), (logLevel == 2 ? "selected" : ""), (logLevel == 3 ? "selected" : ""), (logLevel == 4 ? "selected" : ""), (logLevel == 5 ? "selected" : ""), (logLevel == 6 ? "selected" : ""));
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
# ifdef ZgatewayLORA
/**
* @brief /LA - Configure LORA Page
* T: handleLA: uri: /la, args: 11, method: 1
* T: handleLA Arg: 0, lf=868100000
* T: handleLA Arg: 1, lt=14
* T: handleLA Arg: 2, ls=12
* T: handleLA Arg: 3, lb=125
* T: handleLA Arg: 4, lc=5
* T: handleLA Arg: 5, ll=8
* T: handleLA Arg: 6, lw=0
* T: handleLA Arg: 7, lr=1
* T: handleLA Arg: 8, li=0
* T: handleLA Arg: 9, ok=0
* T: handleLA Arg: 10, save=
*/
void handleLA() {
WEBUI_TRACE_LOG(F("handleLA: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleLA Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
if (server.hasArg("save")) {
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
JsonObject WEBtoLORA = jsonBuffer.to<JsonObject>();
bool update = false;
if (server.hasArg("lf")) {
WEBtoLORA["frequency"] = server.arg("lf");
update = true;
}
if (server.hasArg("lt")) {
WEBtoLORA["txpower"] = server.arg("lt");
update = true;
}
if (server.hasArg("ls")) {
WEBtoLORA["spreadingfactor"] = server.arg("ls");
update = true;
}
if (server.hasArg("lb")) {
WEBtoLORA["signalbandwidth"] = server.arg("lb");
update = true;
}
if (server.hasArg("lc")) {
WEBtoLORA["codingrate"] = server.arg("lc");
update = true;
}
if (server.hasArg("ll")) {
WEBtoLORA["preamblelength"] = server.arg("ll");
update = true;
}
if (server.hasArg("lw")) {
WEBtoLORA["syncword"] = server.arg("lw");
update = true;
}
if (server.hasArg("lr")) {
WEBtoLORA["enablecrc"] = server.arg("lr");
update = true;
} else {
WEBtoLORA["enablecrc"] = false;
update = true;
}
if (server.hasArg("li")) {
WEBtoLORA["invertiq"] = server.arg("li");
update = true;
} else {
WEBtoLORA["invertiq"] = false;
update = true;
}
if (server.hasArg("ok")) {
WEBtoLORA["onlyknown"] = server.arg("ok");
update = true;
} else {
WEBtoLORA["onlyknown"] = false;
update = true;
}
if (update) {
Log.notice(F("[WebUI] Save data" CR));
WEBtoLORA["save"] = true;
LORAConfig_fromJson(WEBtoLORA);
stateLORAMeasures();
Log.trace(F("[WebUI] LORAConfig end" CR));
}
}
}
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure LORA").c_str());
String response = String(buffer);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_lora_body,
jsonChar,
gateway_name,
LORAConfig.frequency == 868000000 ? "selected" : "",
LORAConfig.frequency == 915000000 ? "selected" : "",
LORAConfig.frequency == 433000000 ? "selected" : "",
LORAConfig.txPower == 0 ? "selected" : "",
LORAConfig.txPower == 1 ? "selected" : "",
LORAConfig.txPower == 2 ? "selected" : "",
LORAConfig.txPower == 3 ? "selected" : "",
LORAConfig.txPower == 4 ? "selected" : "",
LORAConfig.txPower == 5 ? "selected" : "",
LORAConfig.txPower == 6 ? "selected" : "",
LORAConfig.txPower == 7 ? "selected" : "",
LORAConfig.txPower == 8 ? "selected" : "",
LORAConfig.txPower == 9 ? "selected" : "",
LORAConfig.txPower == 10 ? "selected" : "",
LORAConfig.txPower == 11 ? "selected" : "",
LORAConfig.txPower == 12 ? "selected" : "",
LORAConfig.txPower == 13 ? "selected" : "",
LORAConfig.txPower == 14 ? "selected" : "",
LORAConfig.spreadingFactor == 7 ? "selected" : "",
LORAConfig.spreadingFactor == 8 ? "selected" : "",
LORAConfig.spreadingFactor == 9 ? "selected" : "",
LORAConfig.spreadingFactor == 10 ? "selected" : "",
LORAConfig.spreadingFactor == 11 ? "selected" : "",
LORAConfig.spreadingFactor == 12 ? "selected" : "",
LORAConfig.signalBandwidth == 7800 ? "selected" : "",
LORAConfig.signalBandwidth == 10400 ? "selected" : "",
LORAConfig.signalBandwidth == 15600 ? "selected" : "",
LORAConfig.signalBandwidth == 20800 ? "selected" : "",
LORAConfig.signalBandwidth == 31250 ? "selected" : "",
LORAConfig.signalBandwidth == 41700 ? "selected" : "",
LORAConfig.signalBandwidth == 62500 ? "selected" : "",
LORAConfig.signalBandwidth == 125000 ? "selected" : "",
LORAConfig.signalBandwidth == 250000 ? "selected" : "",
LORAConfig.signalBandwidth == 500000 ? "selected" : "",
LORAConfig.codingRateDenominator == 5 ? "selected" : "",
LORAConfig.codingRateDenominator == 6 ? "selected" : "",
LORAConfig.codingRateDenominator == 7 ? "selected" : "",
LORAConfig.codingRateDenominator == 8 ? "selected" : "",
LORAConfig.preambleLength,
LORAConfig.syncWord,
LORAConfig.crc ? "checked" : "",
LORAConfig.invertIQ ? "checked" : "",
LORAConfig.onlyKnown ? "checked" : "");
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
# elif defined(ZgatewayRTL_433) || defined(ZgatewayPilight) || defined(ZgatewayRF) || defined(ZgatewayRF2) || defined(ZactuatorSomfy)
# include <map>
std::map<int, String> activeReceiverOptions = {
{0, "Inactive"},
# if defined(ZgatewayPilight) && !defined(ZradioSX127x)
{1, "PiLight"},
# endif
# if defined(ZgatewayRF) && !defined(ZradioSX127x)
{2, "RF"},
# endif
# ifdef ZgatewayRTL_433
{3, "RTL_433"},
# endif
# if defined(ZgatewayRF2) && !defined(ZradioSX127x)
{4, "RF2 (restart required)"}
# endif
};
bool isValidReceiver(int receiverId) {
// Check if the receiverId exists in the activeReceiverOptions map
return activeReceiverOptions.find(receiverId) != activeReceiverOptions.end();
}
String generateActiveReceiverOptions(int currentSelection) {
String optionsHtml = "";
for (const auto& option : activeReceiverOptions) {
optionsHtml += "<option value='" + String(option.first) + "'";
if (currentSelection == option.first) {
optionsHtml += " selected";
}
optionsHtml += ">" + option.second + "</option>";
}
return optionsHtml;
}
/**
* @brief /RF - Configure RF Page
* T: handleRF: uri: /rf, args: 2, method: 1
* T: handleRF Arg: 0, rf=868.30
* T: handleRF Arg: 1, oo=0
* T: handleRF Arg: 2, rs=0
* T: handleRF Arg: 3, dg=0
* T: handleRF Arg: 4, ar=0
* T: handleRF Arg: 4, save=
*/
void handleRF() {
WEBUI_TRACE_LOG(F("handleRF: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
bool update = false;
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
JsonObject WEBtoRF = jsonBuffer.to<JsonObject>();
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleRF Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
if (server.hasArg("save")) {
if (server.hasArg("rf")) {
String freqStr = server.arg("rf");
RFConfig.frequency = freqStr.toFloat();
if (validFrequency(RFConfig.frequency)) {
WEBtoRF["frequency"] = RFConfig.frequency;
update = true;
} else {
Log.warning(F("[WebUI] Invalid Frequency" CR));
}
}
if (server.hasArg("ar")) {
int selectedReceiver = server.arg("ar").toInt();
if (isValidReceiver(selectedReceiver)) { // Assuming isValidReceiver is a validation function
RFConfig.activeReceiver = selectedReceiver;
WEBtoRF["activereceiver"] = RFConfig.activeReceiver;
update = true;
} else {
Log.warning(F("[WebUI] Invalid Active Receiver" CR));
}
}
if (server.hasArg("oo")) {
RFConfig.newOokThreshold = server.arg("oo").toInt();
WEBtoRF["ookthreshold"] = RFConfig.newOokThreshold;
update = true;
}
if (server.hasArg("rs")) {
RFConfig.rssiThreshold = server.arg("rs").toInt();
WEBtoRF["rssithreshold"] = RFConfig.rssiThreshold;
update = true;
}
if (update) {
Log.notice(F("[WebUI] Save data" CR));
WEBtoRF["save"] = true;
RFConfig_fromJson(WEBtoRF);
stateRFMeasures();
Log.trace(F("[WebUI] RFConfig end" CR));
}
}
}
String activeReceiverHtml = generateActiveReceiverOptions(RFConfig.activeReceiver);
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure RF").c_str());
String response = String(buffer);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_rf_body, jsonChar, gateway_name, RFConfig.frequency, activeReceiverHtml.c_str());
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
# endif
/**
* @brief /RT - Reset configuration ( Erase and Restart ) from Configuration menu
*
*/
void handleRT() {
WEBUI_TRACE_LOG(F("handleRT: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleRT Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
}
if (server.hasArg("non")) {
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
Log.warning(F("[WebUI] Erase and Restart" CR));
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Erase and Restart").c_str());
String response = String(buffer);
response += String(restart_script);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, reset_body, jsonChar, gateway_name, "Erase and Restart");
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
erase(true);
} else {
handleCN();
}
}
# if defined(ZgatewayCloud)
/**
* @brief /CL - Cloud Configuration
*
*/
void handleCL() {
WEBUI_TRACE_LOG(F("handleCL: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleCL Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
}
if (server.hasArg("save")) {
// T: handleCL: uri: /cl, args: 2, method: 1
// T: handleCL Arg: 0, cl-en=on
// T: handleCL Arg: 1, save=
if (server.hasArg("save") && server.method() == 1) {
setCloudEnabled(server.hasArg("cl-en"));
}
}
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Configure Cloud").c_str());
String response = String(buffer);
response += String(script);
response += String(style);
char cloudEnabled[8] = {0};
if (isCloudEnabled()) {
strncpy(cloudEnabled, "checked", 8);
}
char deviceToken[5] = {0};
if (!isCloudDeviceTokenSupplied()) {
strncpy(deviceToken, " Not", 4);
}
requestToken = esp_random();
# ifdef ESP32_ETHERNET
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_cloud_body, jsonChar, gateway_name, " cloud checked", " Not", (String(CLOUDGATEWAY) + "token/start").c_str(), (char*)ETH.macAddress().c_str(), ("http://" + String(TheengsUtils::ip2CharArray(ETH.localIP())) + "/").c_str(), gateway_name, uptime(), requestToken);
# else
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_cloud_body, jsonChar, gateway_name, cloudEnabled, deviceToken, (String(CLOUDGATEWAY) + "token/start").c_str(), (char*)WiFi.macAddress().c_str(), ("http://" + String(TheengsUtils::ip2CharArray(WiFi.localIP())) + "/").c_str(), gateway_name, uptime(), requestToken);
# endif
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
/**
* @brief /TK - Receive Cloud Device Token
*
*/
void handleTK() {
WEBUI_TRACE_LOG(F("handleTK: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleTK Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
}
if (server.hasArg("deviceToken") && server.hasArg("uptime") && server.hasArg("RT")) {
String deviceToken = server.arg("deviceToken");
if (setCloudDeviceToken(deviceToken) && server.arg("RT").toInt() == requestToken && server.arg("uptime").toInt() + 600 > uptime()) {
setCloudEnabled(true);
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Received Device Token").c_str());
String response = String(buffer);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, token_body, jsonChar, gateway_name);
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
} else {
WEBUI_TRACE_LOG(F("handleTK: uptime: %u, uptime: %u, ok: %T" CR), server.arg("uptime").toInt(), uptime(), server.arg("uptime").toInt() + 600 > uptime());
WEBUI_TRACE_LOG(F("handleTK: RT: %d, RT: %d, ok: %T " CR), server.arg("RT").toInt(), requestToken, server.arg("RT").toInt() == requestToken);
Log.error(F("[WebUI] Invalid Token Response: RT: %T, uptime: %T" CR), server.arg("RT").toInt() == requestToken, server.arg("uptime").toInt() + 600 > uptime());
server.send(500, "text/html", "Internal ERROR - Invalid Token");
}
}
}
# endif
/**
* @brief /IN - Information Page
*
*/
void handleIN() {
WEBUI_TRACE_LOG(F("handleCN: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleIN Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
} else {
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
String informationDisplay = stateMeasures(); // .replace(",\"", "}1"); // .replace("\":", "=2")
// }1 json-oled }2 true } }1 Cloud }2 cloudEnabled}2true}1c
# if defined(ZgatewayBT)
informationDisplay += "1<BR>BT}2}1"; // }1 the bracket is not needed as the previous message ends with }
informationDisplay += stateBTMeasures(false);
# endif
# if defined(ZdisplaySSD1306)
informationDisplay += "1<BR>SSD1306}2}1"; // }1 the bracket is not needed as the previous message ends with }
informationDisplay += stateSSD1306Display();
# endif
# if defined(ZgatewayCloud)
informationDisplay += "1<BR>Cloud}2}1";
informationDisplay += stateCLOUDStatus();
# endif
# if defined(ZgatewayLORA)
informationDisplay += "1<BR>LORA}2}1";
informationDisplay += stateLORAMeasures();
# endif
# if defined(ZgatewayRF)
informationDisplay += "1<BR>RF}2}1";
informationDisplay += stateRFMeasures();
# endif
informationDisplay += "1<BR>WebUI}2}1";
informationDisplay += stateWebUIStatus();
// stateBTMeasures causes a Stack canary watchpoint triggered (loopTask)
// WEBUI_TRACE_LOG(F("[WebUI] informationDisplay before %s" CR), informationDisplay.c_str());
// TODO: need to fix display of modules array within SYStoMQTT
informationDisplay += "1}2";
informationDisplay.replace(",\"", "}1");
informationDisplay.replace("\":", "}2");
informationDisplay.replace("{\"", "");
informationDisplay.replace("\"", "\\\"");
// WEBUI_TRACE_LOG(F("[WebUI] informationDisplay after %s" CR), informationDisplay.c_str());
if (informationDisplay.length() > WEB_TEMPLATE_BUFFER_MAX_SIZE) {
Log.warning(F("[WebUI] informationDisplay content length ( %d ) greater than WEB_TEMPLATE_BUFFER_MAX_SIZE. Display truncated" CR), informationDisplay.length());
}
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Information").c_str());
String response = String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, information_script, informationDisplay.c_str());
response += String(buffer);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, information_body, jsonChar, gateway_name);
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
}
/**
* @brief /handleFavicon - Send Favicon
*
*/
void handleFavicon() {
WEBUI_TRACE_LOG(F("handleCN: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
server.sendHeader("Content-Type", "image/x-icon");
server.send_P(200, "image/x-icon", reinterpret_cast<const char*>(Openmqttgateway_logo_mini_ico), sizeof(Openmqttgateway_logo_mini_ico));
}
# if defined(ESP32) && defined(MQTT_HTTPS_FW_UPDATE)
/**
* @brief /UP - Firmware Upgrade Page
*
*/
void handleUP() {
WEBUI_TRACE_LOG(F("handleUP: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args()) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleUP Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
DynamicJsonDocument jsonBuffer(JSON_MSG_BUFFER);
JsonObject WEBtoSYS = jsonBuffer.to<JsonObject>();
if (server.hasArg("o")) {
WEBtoSYS["url"] = server.arg("o");
WEBtoSYS["version"] = "test";
WEBtoSYS["password"] = ota_pass;
{
sendRestartPage();
String output;
serializeJson(WEBtoSYS, output);
Log.notice(F("[WebUI] XtoSYSupdate %s" CR), output.c_str());
}
String topic = String(mqtt_topic) + String(gateway_name) + String(subjectMQTTtoSYSupdate);
MQTTHttpsFWUpdate((char*)topic.c_str(), WEBtoSYS);
return;
} else if (server.hasArg("le")) {
uint32_t le = server.arg("le").toInt();
if (le != 0) {
WEBtoSYS["version"] = (le == 1 ? "latest" : (le == 2 ? "dev" : "unknown"));
WEBtoSYS["password"] = ota_pass;
{
sendRestartPage();
String output;
serializeJson(WEBtoSYS, output);
Log.notice(F("[WebUI] XtoSYSupdate %s" CR), output.c_str());
}
String topic = String(mqtt_topic) + String(gateway_name) + String(subjectMQTTtoSYSupdate);
MQTTHttpsFWUpdate((char*)topic.c_str(), WEBtoSYS);
return;
}
}
}
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Firmware Upgrade").c_str());
String response = String(buffer);
response += String(script);
response += String(style);
String systemUrl = RELEASE_LINK + latestVersion + "/" + ENV_NAME + "-firmware.bin";
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, upgrade_body, jsonChar, gateway_name, systemUrl.c_str());
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
# endif
void sendRestartPage() {
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Updating Firmware and Restart").c_str());
String response = String(buffer);
response += String(restart_script);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, reset_body, jsonChar, gateway_name, "Updating Firmware and Restart");
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
delay(2000); // Wait for web page to be sent before
}
/**
* @brief /CS - Serial Console and Command Line
*
*/
void handleCS() {
WEBUI_TRACE_LOG(F("handleCS: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
if (server.args() && server.hasArg("c2")) {
for (uint8_t i = 0; i < server.args(); i++) {
WEBUI_TRACE_LOG(F("handleCS Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
if (server.hasArg("c1")) {
String c1 = server.arg("c1");
String cmdTopic = String(mqtt_topic) + String(gateway_name) + "/" + c1.substring(0, c1.indexOf(' '));
String command = c1.substring(c1.indexOf(' ') + 1);
if (command.length()) {
WEBUI_TRACE_LOG(F("[WebUI] handleCS inject MQTT Command topic: '%s', command: '%s'" CR), cmdTopic.c_str(), command.c_str());
receivingDATA(cmdTopic.c_str(), command.c_str());
} else {
Log.warning(F("[WebUI] Missing command: '%s', command: '%s'" CR), cmdTopic.c_str(), command.c_str());
}
}
uint32_t index = server.arg("c2").toInt();
String message = String(log_buffer_pointer) + "}1" + String(reset_web_log_flag) + "}1";
if (!reset_web_log_flag) {
index = 0;
reset_web_log_flag = true;
}
bool cflg = (index);
char* line;
size_t len;
while (GetLog(1, &index, &line, &len)) {
if (cflg) {
message += "\n";
}
for (int x = 0; x < len - 1; x++) {
message += line[x];
}
cflg = true;
}
message += "}1";
server.send(200, "text/plain", message);
} else {
char jsonChar[100];
serializeJson(modules, jsonChar, measureJson(modules) + 1);
char buffer[WEB_TEMPLATE_BUFFER_MAX_SIZE];
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, header_html, (String(gateway_name) + " - Console").c_str());
String response = String(buffer);
response += String(console_script);
response += String(script);
response += String(style);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, console_body, jsonChar, gateway_name);
response += String(buffer);
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
response += String(buffer);
server.send(200, "text/html", response);
}
}
/**
* @brief Page not found handler
*
*/
void notFound() {
WEBUI_SECURE
# ifdef WEBUI_DEVELOPMENT
String path = server.uri();
if (!exists(path)) {
if (exists(path + ".html")) {
path += ".html";
} else {
# endif
Log.warning(F("[WebUI] notFound: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
server.send(404, "text/plain", "Not found");
return;
# ifdef WEBUI_DEVELOPMENT
}
}
WEBUI_TRACE_LOG(F("notFound returning: actual uri: %s, args: %d, method: %d" CR), path, server.args(), server.method());
File file = FILESYSTEM.open(path, "r");
server.streamFile(file, "text/html");
file.close();
# endif
}
void WebUISetup() {
WEBUI_TRACE_LOG(F("ZwebUI setup start" CR));
WebUIConfig_load();
webUIQueue = xQueueCreate(5, sizeof(webUIQueueMessage*));
# ifdef WEBUI_DEVELOPMENT
FILESYSTEM.begin();
{
File root = FILESYSTEM.open("/");
File file = root.openNextFile();
while (file) {
String fileName = file.name();
size_t fileSize = file.size();
WEBUI_TRACE_LOG(F("FS File: %s, size: %s" CR), fileName.c_str(), formatBytes(fileSize).c_str());
file = root.openNextFile();
}
}
# endif
server.onNotFound(notFound);
server.on("/", handleRoot); // Main Menu
server.on("/in", handleIN); // Information
server.on("/cs", handleCS); // Console
# if defined(ESP32) && defined(MQTT_HTTPS_FW_UPDATE)
server.on("/up", handleUP); // Firmware Upgrade
# endif
server.on("/cn", handleCN); // Configuration
server.on("/wi", HTTP_POST, handleWI); // Configure Wifi
server.on("/mq", HTTP_POST, handleMQ); // Configure MQTT
# ifndef ESPWifiManualSetup
server.on("/cg", HTTP_POST, handleCG); // Configure gateway"
# endif
server.on("/wu", handleWU); // Configure WebUI
# ifdef ZgatewayLORA
server.on("/la", handleLA); // Configure LORA
# elif defined(ZgatewayRTL_433) || defined(ZgatewayPilight) || defined(ZgatewayRF) || defined(ZgatewayRF2) || defined(ZactuatorSomfy)
server.on("/rf", handleRF); // Configure RF
# endif
# if defined(ZgatewayCloud)
server.on("/cl", handleCL); // Configure Cloud
server.on("/tk", handleTK); // Store Device Token
# endif
server.on("/lo", handleLO); // Configure Logging
server.on("/rt", handleRT); // Reset configuration ( Erase and Restart )
server.on("/favicon.ico", handleFavicon); // Information
server.begin();
Log.begin(LOG_LEVEL, &WebLog);
Log.trace(F("[WebUI] displayMetric %T" CR), displayMetric);
Log.trace(F("[WebUI] WebUI Secure %T" CR), webUISecure);
Log.notice(F("OpenMQTTGateway URL: http://%s/" CR), WiFi.localIP().toString().c_str());
displayPrint("URL: http://", (char*)WiFi.localIP().toString().c_str());
Log.notice(F("ZwebUI setup done" CR));
}
unsigned long nextWebUIMessage = uptime() + DISPLAY_WEBUI_INTERVAL;
void WebUILoop() {
server.handleClient();
if (uptime() >= nextWebUIMessage && uxQueueMessagesWaiting(webUIQueue)) {
webUIQueueMessage* message = nullptr;
xQueueReceive(webUIQueue, &message, portMAX_DELAY);
newSSD1306Message = true;
if (currentWebUIMessage) {
free(currentWebUIMessage);
}
currentWebUIMessage = message;
nextWebUIMessage = uptime() + DISPLAY_WEBUI_INTERVAL;
}
}
void XtoWebUI(const char* topicOri, JsonObject& WebUIdata) { // json object decoding
bool success = false;
if (cmpToMainTopic(topicOri, subjectMQTTtoWebUIset)) {
WEBUI_TRACE_LOG(F("MQTTtoWebUI json set" CR));
// properties
if (WebUIdata.containsKey("displayMetric")) {
displayMetric = WebUIdata["displayMetric"].as<bool>();
Log.notice(F("Set displayMetric: %T" CR), displayMetric);
success = true;
}
// save, load, init, erase
if (WebUIdata.containsKey("save") && WebUIdata["save"]) {
success = WebUIConfig_save();
if (success) {
Log.notice(F("WebUI config saved" CR));
}
} else if (WebUIdata.containsKey("load") && WebUIdata["load"]) {
success = WebUIConfig_load();
if (success) {
Log.notice(F("WebUI config loaded" CR));
}
} else if (WebUIdata.containsKey("init") && WebUIdata["init"]) {
WebUIConfig_init();
success = true;
if (success) {
Log.notice(F("WebUI config initialised" CR));
}
} else if (WebUIdata.containsKey("erase") && WebUIdata["erase"]) {
// Erase config from NVS (non-volatile storage)
preferences.begin(Gateway_Short_Name, false);
success = preferences.remove("WebUIConfig");
preferences.end();
if (success) {
Log.notice(F("WebUI config erased" CR));
}
}
if (success) {
stateWebUIStatus();
} else {
Log.error(F("[ WebUI ] XtoWebUI Fail json" CR), WebUIdata);
}
}
}
String stateWebUIStatus() {
//Publish display state
StaticJsonDocument<JSON_MSG_BUFFER> WebUIdataBuffer;
JsonObject WebUIdata = WebUIdataBuffer.to<JsonObject>();
WebUIdata["displayMetric"] = (bool)displayMetric;
WebUIdata["webUISecure"] = (bool)webUISecure;
WebUIdata["displayQueue"] = uxQueueMessagesWaiting(webUIQueue);
String output;
serializeJson(WebUIdata, output);
// WebUIdata["currentMessage"] = currentWebUIMessage;
WebUIdata["origin"] = subjectWebUItoMQTT;
enqueueJsonObject(WebUIdata);
return output;
}
bool WebUIConfig_save() {
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
JsonObject jo = jsonBuffer.to<JsonObject>();
jo["displayMetric"] = (bool)displayMetric;
jo["webUISecure"] = (bool)webUISecure;
// Save config into NVS (non-volatile storage)
String conf = "";
serializeJson(jsonBuffer, conf);
preferences.begin(Gateway_Short_Name, false);
int result = preferences.putString("WebUIConfig", conf);
preferences.end();
Log.trace(F("[WebUI] WebUIConfig_save: %s, result: %d" CR), conf.c_str(), result);
return true;
}
void WebUIConfig_init() {
displayMetric = DISPLAY_METRIC;
webUISecure = WEBUI_AUTH;
Log.notice(F("WebUI config initialised" CR));
}
bool WebUIConfig_load() {
StaticJsonDocument<JSON_MSG_BUFFER> jsonBuffer;
preferences.begin(Gateway_Short_Name, true);
if (preferences.isKey("WebUIConfig")) {
auto error = deserializeJson(jsonBuffer, preferences.getString("WebUIConfig", "{}"));
preferences.end();
if (error) {
Log.error(F("WebUI config deserialization failed: %s, buffer capacity: %u" CR), error.c_str(), jsonBuffer.capacity());
return false;
}
if (jsonBuffer.isNull()) {
Log.warning(F("WebUI config is null" CR));
return false;
}
JsonObject jo = jsonBuffer.as<JsonObject>();
displayMetric = jo["displayMetric"].as<bool>();
webUISecure = jo["webUISecure"].as<bool>();
return true;
} else {
preferences.end();
Log.notice(F("No WebUI config to load" CR));
return false;
}
}
/*
Workaround for c not having a string based switch/case function
*/
constexpr unsigned int webUIHash(const char* s, int off = 0) { // workaround for switching on a string https://stackoverflow.com/a/46711735/18643696
return !s[off] ? 5381 : (webUIHash(s, off + 1) * 33) ^ s[off];
}
/*
Parse json message from module into a format for display
*/
void webUIPubPrint(const char* topicori, JsonObject& data) {
WEBUI_TRACE_LOG(F("[ webUIPubPrint ] pub %s " CR), topicori);
if (webUIQueue) {
webUIQueueMessage* message = (webUIQueueMessage*)heap_caps_calloc(1, sizeof(webUIQueueMessage), MALLOC_CAP_8BIT);
if (message != NULL) {
// Initalize message
strlcpy(message->line1, "", WEBUI_TEXT_WIDTH);
strlcpy(message->line2, "", WEBUI_TEXT_WIDTH);
strlcpy(message->line3, "", WEBUI_TEXT_WIDTH);
strlcpy(message->line4, "", WEBUI_TEXT_WIDTH);
char* topic = strdup(topicori);
strlcpy(message->title, strtok(topic, "/"), WEBUI_TEXT_WIDTH);
free(topic);
// WEBUI_TRACE_LOG(F("[ webUIPubPrint ] switch %s " CR), message->title);
switch (webUIHash(message->title)) {
case webUIHash("SYStoMQTT"): {
// Line 1
if (data["version"]) {
strlcpy(message->line1, data["version"], WEBUI_TEXT_WIDTH);
} else {
strlcpy(message->line1, "", WEBUI_TEXT_WIDTH);
}
// Line 2
String uptime = data["uptime"];
String line = "uptime: " + uptime;
line.toCharArray(message->line2, WEBUI_TEXT_WIDTH);
// Line 3
String freemem = data["freemem"];
line = "freemem: " + freemem;
line.toCharArray(message->line3, WEBUI_TEXT_WIDTH);
// Line 4
String ip = data["ip"];
line = "ip: " + ip;
line.toCharArray(message->line4, WEBUI_TEXT_WIDTH);
// Queue completed message
if (xQueueSend(webUIQueue, (void*)&message, 0) != pdTRUE) {
Log.warning(F("[ WebUI ] ERROR: webUIQueue full, discarding %s" CR), message->title);
free(message);
} else {
// Log.notice(F("[ WebUI ] Queued %s" CR), message->title);
}
break;
}
# ifdef ZgatewayRTL_433
case webUIHash("RTL_433toMQTT"): {
if (data["model"] && strncmp(data["model"], "status", 6)) { // Does not contain "status"
// {"model":"Acurite-Tower","id":2043,"channel":"B","battery_ok":1,"temperature_C":5.3,"humidity":81,"mic":"CHECKSUM","protocol":"Acurite 592TXR Temp/Humidity, 5n1 Weather Station, 6045 Lightning, 3N1, Atlas","rssi":-81,"duration":121060}
// Line 1
strlcpy(message->line1, data["model"], WEBUI_TEXT_WIDTH);
// Line 2
String line2 = "";
if (data["id"]) {
String id = data["id"];
line2 += "id: " + id + " ";
}
if (data["channel"]) {
String channel = data["channel"];
line2 += "channel: " + channel;
}
line2.toCharArray(message->line2, WEBUI_TEXT_WIDTH);
// Line 3
String line3 = "";
if (data.containsKey("temperature_C")) {
float temperature_C = data["temperature_C"];
char temp[5];
if (displayMetric) {
dtostrf(temperature_C, 3, 1, temp);
line3 = "temp: " + (String)temp + "°C ";
} else {
dtostrf(convertTemp_CtoF(temperature_C), 3, 1, temp);
line3 = "temp: " + (String)temp + "°F ";
}
}
float humidity = data["humidity"];
if (data.containsKey("humidity") && humidity <= 100 && humidity >= 0) {
char hum[5];
dtostrf(humidity, 3, 1, hum);
line3 += "hum: " + (String)hum + "% ";
}
if (data.containsKey("wind_avg_km_h")) {
float wind_avg_km_h = data["wind_avg_km_h"];
char wind[6];
if (displayMetric) {
dtostrf(wind_avg_km_h, 3, 1, wind);
line3 += "wind: " + (String)wind + "km/h ";
} else {
dtostrf(convert_kmph2mph(wind_avg_km_h), 3, 1, wind);
line3 += "wind: " + (String)wind + "mp/h ";
}
}
float moisture = data["moisture"];
if (data.containsKey("moisture") && moisture <= 100 && moisture >= 0) {
char moist[5];
dtostrf(moisture, 3, 1, moist);
line3 += "moist: " + (String)moist + "% ";
}
line3.toCharArray(message->line3, WEBUI_TEXT_WIDTH);
// Line 4
String line4 = "";
if (data["battery_ok"]) {
line4 = "batt: " + data["battery_ok"].as<String>();
} else {
line4 = "pulses: " + data["pulses"].as<String>();
}
line4 += " rssi: " + data["rssi"].as<String>();
line4.toCharArray(message->line4, WEBUI_TEXT_WIDTH);
// Queue completed message
if (xQueueSend(webUIQueue, (void*)&message, 0) != pdTRUE) {
Log.warning(F("[ WebUI ] webUIQueue full, discarding signal %s" CR), message->title);
free(message);
} else {
// Log.notice(F("[ WebUI ] Queued %s" CR), message->title);
}
} else {
Log.error(F("[ WebUI ] rtl_433 not displaying %s" CR), message->title);
free(message);
}
break;
}
# endif
# ifdef ZsensorBME280
case webUIHash("CLIMAtoMQTT"): {
// {"tempc":17.06,"tempf":62.708,"hum":50.0752,"pa":98876.14,"altim":205.8725,"altift":675.4348}
// Line 1
strlcpy(message->line1, "bme280", WEBUI_TEXT_WIDTH);
// Line 2
String line2 = "";
if (data.containsKey("tempc")) {
char temp[5];
float temperature_C = data["tempc"];
if (displayMetric) {
dtostrf(temperature_C, 3, 1, temp);
line2 = "temp: " + (String)temp + "°C ";
} else {
dtostrf(convertTemp_CtoF(temperature_C), 3, 1, temp);
line2 = "temp: " + (String)temp + "°F ";
}
}
line2.toCharArray(message->line2, WEBUI_TEXT_WIDTH);
// Line 3
String line3 = "";
float humidity = data["hum"];
if (data.containsKey("hum") && humidity <= 100 && humidity >= 0) {
char hum[5];
dtostrf(humidity, 3, 1, hum);
line3 += "hum: " + (String)hum + "% ";
}
line3.toCharArray(message->line3, WEBUI_TEXT_WIDTH);
// Line 4
float pa = (int)data["pa"] / 100;
char pressure[6];
String line4 = "";
if (displayMetric) {
dtostrf(pa, 3, 1, pressure);
line4 = "pressure: " + (String)pressure + " hPa";
} else {
dtostrf(convert_hpa2inhg(pa), 3, 1, pressure);
line4 = "pressure: " + (String)pressure + " inHg";
}
line4.toCharArray(message->line4, WEBUI_TEXT_WIDTH);
// Queue completed message
if (xQueueSend(webUIQueue, (void*)&message, 0) != pdTRUE) {
Log.warning(F("[ WebUI ] webUIQueue full, discarding signal %s" CR), message->title);
free(message);
} else {
// Log.notice(F("[ WebUI ] Queued %s" CR), message->title);
}
break;
}
# endif
# ifdef ZgatewayBT
case webUIHash("BTtoMQTT"): {
// {"id":"AA:BB:CC:DD:EE:FF","mac_type":0,"adv_type":0,"name":"sps","manufacturerdata":"de071f1000b1612908","rssi":-70,"brand":"Inkbird","model":"T(H) Sensor","model_id":"IBS-TH1/TH2/P01B","type":"THBX","cidc":false,"acts":true,"tempc":20.14,"tempf":68.252,"hum":41.27,"batt":41}
if (data["model_id"] != "MS-CDP" && data["model_id"] != "GAEN" && data["model_id"] != "APPLE_CONT" && data["model_id"] != "IBEACON") {
// Line 2, 3, 4
String line2 = "";
String line3 = "";
String line4 = "";
// Properties
String properties[6] = {"", "", "", "", "", ""};
int property = -1;
if (data["type"] == "THB" || data["type"] == "THBX" || data["type"] == "PLANT" || data["type"] == "AIR" || data["type"] == "BATT" || data["type"] == "ACEL" || (data["type"] == "UNIQ" && data["model_id"] == "SDLS")) {
if (data.containsKey("tempc")) {
property++;
char temp[5];
if (displayMetric) {
float temperature = data["tempc"];
dtostrf(temperature, 3, 1, temp);
properties[property] = "temp: " + (String)temp + "°C ";
} else {
float temperature = data["tempf"];
dtostrf(temperature, 3, 1, temp);
properties[property] = "temp: " + (String)temp + "°F ";
}
}
if (data.containsKey("tempc2_dp")) {
property++;
char tempdp[5];
if (displayMetric) {
float temperature = data["tempc2_dp"];
dtostrf(temperature, 3, 1, tempdp);
properties[property] = "dewp: " + (String)tempdp + "°C ";
} else {
float temperature = data["tempf2_dp"];
dtostrf(temperature, 3, 1, tempdp);
properties[property] = "dewp: " + (String)tempdp + "°F ";
}
}
if (data.containsKey("extprobe")) {
property++;
properties[property] = " ext. probe";
}
if (data.containsKey("hum")) {
property++;
float humidity = data["hum"];
char hum[5];
dtostrf(humidity, 3, 1, hum);
properties[property] = "hum: " + (String)hum + "% ";
}
if (data.containsKey("pm25")) {
property++;
int pm25int = data["pm25"];
char pm25[3];
itoa(pm25int, pm25, 10);
if ((data.containsKey("pm10"))) {
properties[property] = "PM 2.5: " + (String)pm25 + " ";
} else {
properties[property] = "pm2.5: " + (String)pm25 + "μg/m³ ";
}
}
if (data.containsKey("pm10")) {
property++;
int pm10int = data["pm10"];
char pm10[3];
itoa(pm10int, pm10, 10);
if ((data.containsKey("pm25"))) {
properties[property] = "/ 10: " + (String)pm10 + "μg/m³ ";
} else {
properties[property] = "pm10: " + (String)pm10 + "μg/m³ ";
}
}
if (data.containsKey("for")) {
property++;
int formint = data["for"];
char form[3];
itoa(formint, form, 10);
properties[property] = "CH₂O: " + (String)form + "mg/m³ ";
}
if (data.containsKey("co2")) {
property++;
int co2int = data["co2"];
char co2[4];
itoa(co2int, co2, 10);
properties[property] = "co2: " + (String)co2 + "ppm ";
}
if (data.containsKey("moi")) {
property++;
int moiint = data["moi"];
char moi[4];
itoa(moiint, moi, 10);
properties[property] = "moi: " + (String)moi + "% ";
}
if (data.containsKey("lux")) {
property++;
int luxint = data["lux"];
char lux[5];
itoa(luxint, lux, 10);
properties[property] = "lux: " + (String)lux + "lx ";
}
if (data.containsKey("fer")) {
property++;
int ferint = data["fer"];
char fer[7];
itoa(ferint, fer, 10);
properties[property] = "fer: " + (String)fer + "µS/cm ";
}
if (data.containsKey("pres")) {
property++;
int presint = data["pres"];
char pres[4];
itoa(presint, pres, 10);
properties[property] = "pres: " + (String)pres + "hPa ";
}
if (data.containsKey("batt")) {
property++;
int battery = data["batt"];
char batt[5];
itoa(battery, batt, 10);
properties[property] = "batt: " + (String)batt + "% ";
}
if (data.containsKey("shake")) {
property++;
int shakeint = data["shake"];
char shake[3];
itoa(shakeint, shake, 10);
properties[property] = "shake: " + (String)shake + " ";
}
if (data.containsKey("volt")) {
property++;
float voltf = data["volt"];
char volt[5];
dtostrf(voltf, 3, 1, volt);
properties[property] = "volt: " + (String)volt + "V ";
}
if (data.containsKey("wake")) {
property++;
String wakestr = data["wake"];
properties[property] = "wake: " + wakestr + " ";
}
if (data.containsKey("gravity")) {
property++;
property++;
char sgrav[6];
float gravityf = data["gravity"];
dtostrf(gravityf, 5, 3, sgrav);
properties[property] = "SG: " + (String)sgrav + " ";
}
} else if (data["type"] == "BBQ") {
String tempcstr = "";
int j = 7;
if (data["model_id"] == "IBT-2X(S)") {
j = 3;
} else if (data["model_id"] == "IBT-4X(S/C)") {
j = 5;
}
for (int i = 0; i < j; i++) {
if (i == 0) {
if (displayMetric) {
tempcstr = "tempc";
} else {
tempcstr = "tempf";
}
i++;
} else {
if (displayMetric) {
tempcstr = "tempc" + (String)i;
} else {
tempcstr = "tempf" + (String)i;
}
}
if (data.containsKey(tempcstr)) {
char temp[5];
float temperature = data[tempcstr];
dtostrf(temperature, 3, 1, temp);
properties[i - 1] = "tp" + (String)i + ": " + (String)temp;
if (displayMetric) {
properties[i - 1] += "°C ";
} else {
properties[i - 1] += "°F ";
}
} else {
properties[i - 1] = "tp" + (String)i + ": " + "off ";
}
}
} else if (data["type"] == "BODY") {
if (data.containsKey("steps")) {
property++;
int stepsint = data["steps"];
char steps[5];
itoa(stepsint, steps, 10);
properties[property] = "steps: " + (String)steps + " ";
// next line
property++;
}
if (data.containsKey("act_bpm")) {
property++;
int actbpmint = data["act_bpm"];
char actbpm[3];
itoa(actbpmint, actbpm, 10);
properties[property] = "activity bpm: " + (String)actbpm + " ";
}
if (data.containsKey("bpm")) {
property++;
int bpmint = data["bpm"];
char bpm[3];
itoa(bpmint, bpm, 10);
properties[property] = "bpm: " + (String)bpm + " ";
}
} else if (data["type"] == "SCALE") {
if (data.containsKey("weighing_mode")) {
property++;
String mode = data["weighing_mode"];
properties[property] = mode + " ";
// next line
property++;
}
if (data.containsKey("weight")) {
property++;
float weightf = data["weight"];
char weight[7];
dtostrf(weightf, 3, 1, weight);
if (data.containsKey("unit")) {
String unit = data["unit"];
properties[property] = "weight: " + (String)weight + unit + " ";
} else {
properties[property] = "weight: " + (String)weight;
}
// next line
property++;
}
if (data.containsKey("impedance")) {
property++;
int impint = data["impedance"];
char imp[3];
itoa(impint, imp, 10);
properties[property] = "impedance: " + (String)imp + "ohm ";
}
} else if (data["type"] == "UNIQ") {
if (data["model_id"] == "M1017" || data["model_id"] == "HOBOMX2001") {
if (data.containsKey("lvl_cm")) {
property++;
char lvl[5];
if (displayMetric) {
float lvlf = data["lvl_cm"];
dtostrf(lvlf, 3, 1, lvl);
properties[property] = "level: " + (String)lvl + "cm ";
} else {
float lvlf = data["lvl_in"];
dtostrf(lvlf, 3, 1, lvl);
properties[property] = "level: " + (String)lvl + "\" ";
}
}
if (data.containsKey("quality")) {
property++;
int qualint = data["quality"];
char qual[3];
itoa(qualint, qual, 10);
properties[property] = "qy: " + (String)qual + " ";
}
if (data.containsKey("batt")) {
property++;
int battery = data["batt"];
char batt[5];
itoa(battery, batt, 10);
properties[property] = "batt: " + (String)batt + "% ";
}
}
}
line2 = properties[0] + properties[1];
line3 = properties[2] + properties[3];
line4 = properties[4] + properties[5];
if (!(line2 == "" && line3 == "" && line4 == "")) {
// Titel
char* topic = strdup(topicori);
String heading = strtok(topic, "/");
String line0 = heading + " " + data["id"].as<String>().substring(9, 17);
line0.toCharArray(message->title, WEBUI_TEXT_WIDTH);
free(topic);
// Line 1
strlcpy(message->line1, data["model"], WEBUI_TEXT_WIDTH);
line2.toCharArray(message->line2, WEBUI_TEXT_WIDTH);
line3.toCharArray(message->line3, WEBUI_TEXT_WIDTH);
line4.toCharArray(message->line4, WEBUI_TEXT_WIDTH);
if (xQueueSend(webUIQueue, (void*)&message, 0) != pdTRUE) {
Log.warning(F("[ WebUI ] webUIQueue full, discarding signal %s" CR), message->title);
free(message);
} else {
// Log.notice(F("[ WebUI ] Queued %s" CR), message->title);
}
} else {
WEBUI_TRACE_LOG(F("[ WebUI ] incomplete messaage %s" CR), topicori);
free(message);
}
break;
} else {
WEBUI_TRACE_LOG(F("[ WebUI ] incorrect model_id %s" CR), topicori);
free(message);
break;
}
}
# endif
# ifdef ZsensorRN8209
case webUIHash("RN8209toMQTT"): {
// {"volt":1073178,"current":0,"power":0}
// Line 1
String line1 = "";
if (data.containsKey("volt")) {
char volt[5];
float voltage = data["volt"];
dtostrf(voltage, 3, 1, volt);
line1 = "volt: " + (String)volt;
}
line1.toCharArray(message->line1, WEBUI_TEXT_WIDTH);
// Line 2
String line2 = "";
if (data.containsKey("current")) {
char curr[5];
float current = data["current"];
dtostrf(current, 3, 1, curr);
line2 = "current: " + (String)curr + " A";
}
line2.toCharArray(message->line2, WEBUI_TEXT_WIDTH);
// Line 3
String line3 = "";
if (data.containsKey("power")) {
char pow[5];
float power = data["power"];
dtostrf(power, 3, 1, pow);
line3 = "power: " + (String)pow + " W";
}
line3.toCharArray(message->line3, WEBUI_TEXT_WIDTH);
// Queue completed message
if (xQueueSend(webUIQueue, (void*)&message, 0) != pdTRUE) {
Log.warning(F("[ WebUI ] webUIQueue full, discarding signal %s" CR), message->title);
free(message);
} else {
// Log.notice(F("[ WebUI ] Queued %s" CR), message->title);
}
break;
}
# endif
# ifdef ZgatewayLORA
case webUIHash("LORAtoMQTT"): {
// {"tempc":25.4,"hum":0,"batt":0}
String line1 = "";
if (data.containsKey("tempc")) {
char temp[5];
float temperature_C = data["tempc"];
if (displayMetric) {
dtostrf(temperature_C, 3, 1, temp);
line1 = "temp: " + (String)temp + "°C ";
} else {
dtostrf(convertTemp_CtoF(temperature_C), 3, 1, temp);
line1 = "temp: " + (String)temp + "°F ";
}
}
line1.toCharArray(message->line1, WEBUI_TEXT_WIDTH);
// Line 2
String line2 = "";
float humidity = data["hum"];
if (data.containsKey("hum") && humidity <= 100 && humidity >= 0) {
char hum[5];
dtostrf(humidity, 3, 1, hum);
line2 += "hum: " + (String)hum + "% ";
}
line2.toCharArray(message->line2, WEBUI_TEXT_WIDTH);
// Line 3
String line3 = "";
float adc = data["adc"];
if (data.containsKey("adc") && adc <= 100 && adc >= 0) {
char cAdc[5];
dtostrf(adc, 3, 1, cAdc);
line3 += "adc: " + (String)cAdc + "µS/cm ";
}
line3.toCharArray(message->line2, WEBUI_TEXT_WIDTH);
// Queue completed message
if (xQueueSend(webUIQueue, (void*)&message, 0) != pdTRUE) {
Log.warning(F("[ WebUI ] webUIQueue full, discarding signal %s" CR), message->title);
free(message);
} else {
// Log.notice(F("[ WebUI ] Queued %s" CR), message->title);
}
break;
}
# endif
default:
Log.verbose(F("[ WebUI ] unhandled topic %s" CR), message->title);
free(message);
}
} else {
Log.error(F("[ WebUI ] insufficent memory " CR));
}
} else {
Log.error(F("[ WebUI ] not initalized " CR));
}
}
/*------------------- Serial logging interceptor ----------------------*/
// This pattern was borrowed from HardwareSerial and modified to support the WebUI display
SerialWeb WebLog(0); // Not sure about this, came from Hardwareserial
SerialWeb::SerialWeb(int x) {
}
/*
Initialize WebUI oled display for use, and display OMG logo
*/
void SerialWeb::begin() {
// WebUI.begin(); // User OMG serial support
}
/*
Dummy virtual functions carried over from Serial
*/
int SerialWeb::available(void) {
}
/*
Dummy virtual functions carried over from Serial
*/
int SerialWeb::peek(void) {
}
/*
Dummy virtual functions carried over from Serial
*/
int SerialWeb::read(void) {
}
/*
Dummy virtual functions carried over from Serial
*/
void SerialWeb::flush(void) {
}
/*
Write line of text to the display with vertical scrolling of screen
*/
size_t SerialWeb::write(const uint8_t* buffer, size_t size) {
// Default to Serial output if the display is not available
addLog(buffer, size);
return Serial.write(buffer, size);
}
char line[ROW_LENGTH];
int lineIndex = 0;
void addLog(const uint8_t* buffer, size_t size) {
for (int i = 0; i < size; i++) {
if (char(buffer[i]) == 10 | lineIndex > ROW_LENGTH - 2) {
if (char(buffer[i]) != 10) {
line[lineIndex++] = char(buffer[i]);
}
line[lineIndex++] = char(0);
AddLogData(1, (const char*)&line[0]);
lineIndex = 0;
} else {
line[lineIndex++] = char(buffer[i]);
}
}
}
#endif