Files
OpenMQTTGateway/main/webUI.cpp
Alessandro Staniscia c6b2aae965 Refactor RF Configuration Management (#2245)
- Introduced RFConfiguration class to encapsulate RF settings and operations.
- Replaced direct usage of RFConfig structure with iRFConfig instance across multiple files.
- Updated frequency handling in actuatorSomfy, gatewayPilight, gatewayRF, and gatewayRF2 to use iRFConfig.
- Modified webUI to interact with iRFConfig for RF settings management.
- Removed deprecated RFConfig structure and related functions.
- Enhanced JSON handling for RF configuration loading and saving.
- Improved logging for RF configuration operations.
2025-12-07 09:56:15 -06:00

2654 lines
92 KiB
C++

/*
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 <SPIFFS.h>
# include <WebServer.h> // Docs for this are here - https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer
# include <WiFi.h>
# include "TheengsCommon.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
extern String stateRFMeasures();
uint32_t requestToken = 0;
QueueHandle_t webUIQueue;
WebServer server(80);
webUIQueueMessage* currentWebUIMessage;
extern JsonArray modules;
extern char discovery_prefix[];
extern String latestVersion;
bool WebUIConfig_save();
void sendRestartPage();
bool WebUIConfig_load();
void WebUIConfig_init();
void addLog(const uint8_t* buffer, size_t size);
/*------------------- External functions ----------------------*/
extern void eraseConfig();
extern unsigned long uptime();
extern void ESPRestart(byte reason);
extern void XtoSYS(const char* topicOri, JsonObject& SYSdata);
extern String stateMeasures();
extern void MQTTHttpsFWUpdate(const char* topicOri, JsonObject& HttpsFwUpdateData);
extern void receivingDATA(const char* topicOri, const char* datacallback);
/*------------------- 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 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;
}
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);
}
/*------------------- 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
THEENGS_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);
THEENGS_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 {
THEENGS_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) {
THEENGS_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) {
THEENGS_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 {
THEENGS_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) {
THEENGS_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 {
THEENGS_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);
}
# if BLEDecryptor
/**
* @brief /BL - Config BLE
* T: handleBL: uri: /bl, args: 3, method: 1
* T: handleBL Arg: 0, dn=on - Force Device Name
* T: handleBL Arg: 1, bk=on - BLE AES Key
* T: handleBL Arg: 2, save=
*/
void handleBL() {
WEBUI_TRACE_LOG(F("handleBL: uri: %s, args: %d, method: %d" CR), server.uri(), server.args(), server.method());
WEBUI_SECURE
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("handleBL Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).c_str());
}
bool update = false;
if (server.hasArg("save")) {
// Default BLE AES Key
if (server.hasArg("bk")) {
WEBtoSYS["ble_aes"] = server.arg("bk");
update = true;
}
// Split Custom BLE AES key pair string add to config
if (server.hasArg("kp")) {
String kp = server.arg("kp");
while (kp.length() > 0) {
int kpindex = kp.indexOf(' ');
if (kpindex == -1) {
if (kp.indexOf(':') == 12) {
WEBtoSYS["ble_aes_keys"][kp.substring(0, 12)] = kp.substring(13, 45);
}
break;
} else {
if (kp.indexOf(':') == 12) {
WEBtoSYS["ble_aes_keys"][kp.substring(0, 12)] = kp.substring(13, 45);
}
kp = kp.substring(kpindex + 1);
}
}
update = true;
}
if (update) {
THEENGS_LOG_WARNING(F("[BLE] Save Config" CR));
String topic = String(mqtt_topic) + String(gateway_name) + String(subjectMQTTtoSYSset);
XtoSYS((char*)topic.c_str(), WEBtoSYS);
} else {
THEENGS_LOG_WARNING(F("[BLE] No changes" CR));
}
}
}
// Build BLE Key Pair string
std::string aeskeysstring;
JsonObject root = ble_aes_keys.as<JsonObject>();
for (JsonPair kv : root) {
aeskeysstring += kv.key().c_str();
aeskeysstring += ":";
aeskeysstring += kv.value().as<const char*>();
aeskeysstring += " ";
}
aeskeysstring.pop_back();
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 BLE").c_str());
String response = String(buffer);
response += String(ble_script);
response += String(script);
response += String(style);
int logLevel = Log.getLevel();
snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_ble_body, jsonChar, gateway_name, ble_aes, aeskeysstring.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
# ifdef ZgatewayLORA
# include "config_LORA.h"
extern void LORAConfig_fromJson(JsonObject& LORAdata);
extern String stateLORAMeasures();
extern LORAConfig_s LORAConfig;
/**
* @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) {
THEENGS_LOG_NOTICE(F("[WebUI] Save data" CR));
WEBtoLORA["save"] = true;
LORAConfig_fromJson(WEBtoLORA);
stateLORAMeasures();
THEENGS_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>
# include "rf/RFConfiguration.h"
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
};
extern RFConfiguration iRFConfig;
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=
* TODO: need a review, it's a bit strance set the config in the iRFConfig attribute and then
* setup a message and finally call the loadFromMessage
*/
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");
float freq = freqStr.toFloat();
if (iRFConfig.validFrequency(freq)) {
iRFConfig.setFrequency(freq);
WEBtoRF["frequency"] = iRFConfig.getFrequency();
update = true;
} else {
THEENGS_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
iRFConfig.setActiveReceiver(selectedReceiver);
WEBtoRF["activereceiver"] = iRFConfig.getActiveReceiver();
update = true;
} else {
THEENGS_LOG_WARNING(F("[WebUI] Invalid Active Receiver" CR));
}
}
if (server.hasArg("oo")) {
iRFConfig.setNewOokThreshold(server.arg("oo").toInt());
WEBtoRF["ookthreshold"] = iRFConfig.getNewOokThreshold();
update = true;
}
if (server.hasArg("rs")) {
iRFConfig.setRssiThreshold(server.arg("rs").toInt());
WEBtoRF["rssithreshold"] = iRFConfig.getRssiThreshold();
update = true;
}
if (update) {
THEENGS_LOG_NOTICE(F("[WebUI] Save data" CR));
WEBtoRF["save"] = true;
iRFConfig.loadFromMessage(WEBtoRF);
stateRFMeasures();
THEENGS_LOG_TRACE(F("[WebUI] RFConfig end" CR));
}
}
}
String activeReceiverHtml = generateActiveReceiverOptions(iRFConfig.getActiveReceiver());
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, iRFConfig.getFrequency(), 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);
THEENGS_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);
eraseConfig();
} 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);
THEENGS_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)
String stateBTMeasures(bool start);
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) {
THEENGS_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);
THEENGS_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);
THEENGS_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 {
THEENGS_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
THEENGS_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("webUI 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
# if BLEDecryptor
server.on("/bl", HTTP_POST, handleBL); // Configure BLE
# endif
server.on("/rt", handleRT); // Reset configuration ( Erase and Restart )
server.on("/favicon.ico", handleFavicon); // Information
server.begin();
Log.begin(LOG_LEVEL, &WebLog);
THEENGS_LOG_TRACE(F("[WebUI] displayMetric %T" CR), displayMetric);
THEENGS_LOG_TRACE(F("[WebUI] WebUI Secure %T" CR), webUISecure);
THEENGS_LOG_NOTICE(F("OpenMQTTGateway URL: http://%s/" CR), WiFi.localIP().toString().c_str());
displayPrint("URL: http://", (char*)WiFi.localIP().toString().c_str());
THEENGS_LOG_NOTICE(F("webUI 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);
# if defined(ZdisplaySSD1306)
newSSD1306Message = true;
# endif
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>();
THEENGS_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) {
THEENGS_LOG_NOTICE(F("WebUI config saved" CR));
}
} else if (WebUIdata.containsKey("load") && WebUIdata["load"]) {
success = WebUIConfig_load();
if (success) {
THEENGS_LOG_NOTICE(F("WebUI config loaded" CR));
}
} else if (WebUIdata.containsKey("init") && WebUIdata["init"]) {
WebUIConfig_init();
success = true;
if (success) {
THEENGS_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) {
THEENGS_LOG_NOTICE(F("WebUI config erased" CR));
}
}
if (success) {
stateWebUIStatus();
} else {
THEENGS_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();
THEENGS_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;
THEENGS_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) {
THEENGS_LOG_ERROR(F("WebUI config deserialization failed: %s, buffer capacity: %u" CR), error.c_str(), jsonBuffer.capacity());
return false;
}
if (jsonBuffer.isNull()) {
THEENGS_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();
THEENGS_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) {
THEENGS_LOG_WARNING(F("[ WebUI ] ERROR: webUIQueue full, discarding %s" CR), message->title);
free(message);
} else {
// THEENGS_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) {
THEENGS_LOG_WARNING(F("[ WebUI ] webUIQueue full, discarding signal %s" CR), message->title);
free(message);
} else {
// THEENGS_LOG_NOTICE(F("[ WebUI ] Queued %s" CR), message->title);
}
} else {
THEENGS_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) {
THEENGS_LOG_WARNING(F("[ WebUI ] webUIQueue full, discarding signal %s" CR), message->title);
free(message);
} else {
// THEENGS_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") && data["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) {
THEENGS_LOG_WARNING(F("[ WebUI ] webUIQueue full, discarding signal %s" CR), message->title);
free(message);
} else {
// THEENGS_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) {
THEENGS_LOG_WARNING(F("[ WebUI ] webUIQueue full, discarding signal %s" CR), message->title);
free(message);
} else {
// THEENGS_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) {
THEENGS_LOG_WARNING(F("[ WebUI ] webUIQueue full, discarding signal %s" CR), message->title);
free(message);
} else {
// THEENGS_LOG_NOTICE(F("[ WebUI ] Queued %s" CR), message->title);
}
break;
}
# endif
default:
THEENGS_LOG_VERBOSE(F("[ WebUI ] unhandled topic %s" CR), message->title);
free(message);
}
} else {
THEENGS_LOG_ERROR(F("[ WebUI ] insufficent memory " CR));
}
} else {
THEENGS_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