Add Serial as a communication layer so as to transmit data to/from serial Enable discovery of BT sensors through serial Add serial heartbeat Change discovery principle for interval and intervalacts, keep them always discovered and deactivate adaptivescan when changed. Instead of removing their discovery when adaptive scan is activated Increase JSON_MSG_BUFFER_MAX as we are now using const char* and it requires more memory for Arduino Json Deserialization Co-authored-by: Florian <1technophile@users.noreply.github.com>
2530 lines
88 KiB
Arduino
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 eraseAndRestart();
|
|
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 = "&|>|<|"|'|\";
|
|
|
|
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> (" + WiFi.channel(indices[i]) + ") <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);
|
|
|
|
eraseAndRestart();
|
|
} 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
|