diff --git a/main/User_config.h b/main/User_config.h
index d899592..8f2e4f6 100644
--- a/main/User_config.h
+++ b/main/User_config.h
@@ -664,6 +664,9 @@ CRGB leds2[FASTLED_IND_NUM_LEDS];
# endif
#endif
+/*-----------PLACEHOLDERS FOR WebUI DISPLAY--------------*/
+#define pubWebUI(...) // display the published message onto the OLED display
+
/*-----------PLACEHOLDERS FOR OLED/LCD DISPLAY--------------*/
// The real definitions are in config_M5.h / config_SSD1306.h
#define pubOled(...) // display the published message onto the OLED display
diff --git a/main/ZdisplaySSD1306.ino b/main/ZdisplaySSD1306.ino
index b49e18b..e0198ba 100644
--- a/main/ZdisplaySSD1306.ino
+++ b/main/ZdisplaySSD1306.ino
@@ -40,10 +40,9 @@
# include "config_SSD1306.h"
SemaphoreHandle_t semaphoreOLEDOperation;
-QueueHandle_t displayQueue;
+
boolean logToOLEDDisplay = LOG_TO_OLED;
boolean jsonDisplay = JSON_TO_OLED;
-boolean displayMetric = DISPLAY_METRIC;
boolean displayFlip = DISPLAY_FLIP;
boolean displayState = DISPLAY_STATE;
boolean idlelogo = DISPLAY_IDLE_LOGO;
@@ -61,17 +60,15 @@ void logToOLED(bool display) {
module setup, for use in Arduino setup
*/
void setupSSD1306() {
+ SSD1306Config_init();
+ SSD1306Config_load();
Log.trace(F("Setup SSD1306 Display" CR));
Log.trace(F("ZdisplaySSD1306 command topic: %s" CR), subjectMQTTtoSSD1306set);
Log.trace(F("ZdisplaySSD1306 log-oled: %T" CR), logToOLEDDisplay);
Log.trace(F("ZdisplaySSD1306 json-oled: %T" CR), jsonDisplay);
Log.trace(F("ZdisplaySSD1306 DISPLAY_PAGE_INTERVAL: %d" CR), DISPLAY_PAGE_INTERVAL);
- Log.trace(F("ZdisplaySSD1306 DISPLAY_IDLE_LOGO: %T" CR), DISPLAY_IDLE_LOGO);
- Log.trace(F("ZdisplaySSD1306 DISPLAY_METRIC: %T" CR), displayMetric);
+ Log.trace(F("ZdisplaySSD1306 DISPLAY_IDLE_LOGO: %T" CR), idlelogo);
Log.trace(F("ZdisplaySSD1306 DISPLAY_FLIP: %T" CR), displayFlip);
-
- SSD1306Config_init();
- SSD1306Config_load();
Oled.begin();
Log.notice(F("Setup SSD1306 Display end" CR));
@@ -96,16 +93,10 @@ void loopSSD1306() {
long enough since the last message and display not being used and a queue message waiting
*/
if (jsonDisplay && displayState) {
- if (uptime() >= nextDisplayPage && uxSemaphoreGetCount(semaphoreOLEDOperation) && uxQueueMessagesWaiting(displayQueue)) {
- displayQueueMessage* message = nullptr;
- xQueueReceive(displayQueue, &message, portMAX_DELAY);
- if (!Oled.displayPage(message)) {
- Log.warning(F("[ssd1306] displayPage failed: %s" CR), message->title);
+ if (uptime() >= nextDisplayPage && uxSemaphoreGetCount(semaphoreOLEDOperation) && currentWebUIMessage) {
+ if (!Oled.displayPage(currentWebUIMessage)) {
+ Log.warning(F("[ssd1306] displayPage failed: %s" CR), currentWebUIMessage->title);
}
- if (currentOledMessage) {
- free(currentOledMessage);
- }
- currentOledMessage = message;
nextDisplayPage = uptime() + DISPLAY_PAGE_INTERVAL;
logoDisplayed = false;
}
@@ -158,11 +149,6 @@ void MQTTtoSSD1306(char* topicOri, JsonObject& SSD1306data) { // json object dec
Log.notice(F("Set json-oled: %T" CR), jsonDisplay);
success = true;
}
- if (SSD1306data.containsKey("displaymetric")) {
- displayMetric = SSD1306data["displaymetric"].as();
- Log.notice(F("Set displaymetric: %T" CR), displayMetric);
- success = true;
- }
if (SSD1306data.containsKey("idlelogo")) {
idlelogo = SSD1306data["idlelogo"].as();
success = true;
@@ -212,7 +198,6 @@ void SSD1306Config_save() {
jo["brightness"] = displayBrightness;
jo["log-oled"] = logToOLEDDisplay;
jo["json-oled"] = jsonDisplay;
- jo["displaymetric"] = displayMetric;
jo["idlelogo"] = idlelogo;
jo["display-flip"] = displayFlip;
// Save config into NVS (non-volatile storage)
@@ -229,7 +214,6 @@ void SSD1306Config_init() {
displayBrightness = DISPLAY_BRIGHTNESS;
logToOLEDDisplay = LOG_TO_OLED;
jsonDisplay = JSON_TO_OLED;
- displayMetric = DISPLAY_METRIC;
idlelogo = DISPLAY_IDLE_LOGO;
displayFlip = DISPLAY_FLIP;
Log.notice(F("SSD1306 config initialised" CR));
@@ -255,7 +239,6 @@ bool SSD1306Config_load() {
displayBrightness = jo["brightness"].as();
logToOLEDDisplay = jo["log-oled"].as();
jsonDisplay = jo["json-oled"].as();
- displayMetric = jo["displaymetric"].as();
idlelogo = jo["idlelogo"].as();
displayFlip = jo["display-flip"].as();
Log.notice(F("Saved SSD1306 config loaded" CR));
@@ -267,531 +250,6 @@ bool SSD1306Config_load() {
}
}
-/*
-Workaround for c not having a string based switch/case function
-*/
-constexpr unsigned int hash(const char* s, int off = 0) { // workaround for switching on a string https://stackoverflow.com/a/46711735/18643696
- return !s[off] ? 5381 : (hash(s, off + 1) * 33) ^ s[off];
-}
-
-/*
-Parse json message from module into a format for displaying on screen, and queue for display
-*/
-void ssd1306PubPrint(const char* topicori, JsonObject& data) {
- if (jsonDisplay && displayState) {
- displayQueueMessage* message = (displayQueueMessage*)malloc(sizeof(displayQueueMessage));
- if (message != NULL) {
- char* topic = strdup(topicori);
- strlcpy(message->title, strtok(topic, "/"), OLED_TEXT_WIDTH);
- free(topic);
-
- Oled.display->normalDisplay();
-
- switch (hash(message->title)) {
- case hash("SYStoMQTT"): {
- // {"uptime":456356,"version":"lilygo-rtl_433-test-A-v1.1.1-25-g574177d[lily-cloud]","freemem":125488,"mqttport":"1883","mqttsecure":false,"freestack":3752,"rssi":-36,"SSID":"The_Beach","BSSID":"64:A5:C3:69:C3:38","ip":"192.168.1.239","mac":"4C:75:25:A8:D5:D8","actRec":3,"mhz":433.92,"RTLRssiThresh":-98,"RTLRssi":-108,"RTLAVGRssi":-107,"RTLCnt":121707,"RTLOOKThresh":90,"modules":["LILYGO_OLED","CLOUD","rtl_433"]}
-
- // Line 1
-
- strlcpy(message->line1, data["version"], OLED_TEXT_WIDTH);
-
- // Line 2
-
- String uptime = data["uptime"];
- String line2 = "uptime: " + uptime;
- line2.toCharArray(message->line2, OLED_TEXT_WIDTH);
-
- // Line 3
-
- String freemem = data["freemem"];
- String line3 = "freemem: " + freemem;
- line3.toCharArray(message->line3, OLED_TEXT_WIDTH);
-
- // Line 4
-
- String ip = data["ip"];
- String line4 = "ip: " + ip;
- line4.toCharArray(message->line4, OLED_TEXT_WIDTH);
-
- // Queue completed message
-
- if (xQueueSend(displayQueue, (void*)&message, 0) != pdTRUE) {
- Log.error(F("[ SSD1306 ] ERROR: displayQueue full, discarding signal %s" CR), message->title);
- } else {
- // Log.notice(F("Queued %s" CR), message->title);
- }
- break;
- }
-
-# ifdef ZgatewayRTL_433
- case hash("RTL_433toMQTT"): {
- // {"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"], OLED_TEXT_WIDTH);
-
- // Line 2
-
- String id = data["id"];
- String channel = data["channel"];
- String line2 = "id: " + id + " channel: " + channel;
- line2.toCharArray(message->line2, OLED_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 ";
- }
- }
-
- line3.toCharArray(message->line3, OLED_TEXT_WIDTH);
-
- // Line 4
-
- String rssi = data["rssi"];
- String battery_ok = data["battery_ok"];
-
- String line4 = "batt: " + battery_ok + " rssi: " + rssi;
- line4.toCharArray(message->line4, OLED_TEXT_WIDTH);
-
- // Queue completed message
-
- if (xQueueSend(displayQueue, (void*)&message, 0) != pdTRUE) {
- Log.error(F("[ SSD1306 ] displayQueue full, discarding signal %s" CR), message->title);
- free(message);
- } else {
- // Log.notice(F("Queued %s" CR), message->title);
- }
- break;
- }
-# endif
-# ifdef ZsensorBME280
- case hash("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", OLED_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, OLED_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, OLED_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, OLED_TEXT_WIDTH);
-
- // Queue completed message
-
- if (xQueueSend(displayQueue, (void*)&message, 0) != pdTRUE) {
- Log.error(F("[ SSD1306 ] displayQueue full, discarding signal %s" CR), message->title);
- free(message);
- } else {
- // Log.notice(F("Queued %s" CR), message->title);
- }
- break;
- }
-# endif
-# ifdef ZgatewayBT
- case hash("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 + " ";
- }
-
- } 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") {
- 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().substring(9, 17);
- line0.toCharArray(message->title, OLED_TEXT_WIDTH);
- free(topic);
-
- // Line 1
- strlcpy(message->line1, data["model"], OLED_TEXT_WIDTH);
-
- line2.toCharArray(message->line2, OLED_TEXT_WIDTH);
- line3.toCharArray(message->line3, OLED_TEXT_WIDTH);
- line4.toCharArray(message->line4, OLED_TEXT_WIDTH);
-
- if (xQueueSend(displayQueue, (void*)&message, 0) != pdTRUE) {
- Log.error(F("[ SSD1306 ] displayQueue full, discarding signal %s" CR), message->title);
- free(message);
- } else {
- // Log.notice(F("Queued %s" CR), message->title);
- }
- } else {
- free(message);
- }
-
- break;
- } else {
- free(message);
- }
- }
-# endif
- default:
- Log.error(F("[ SSD1306 ] unhandled topic %s" CR), message->title);
- }
- } else {
- Log.error(F("[ SSD1306 ] insufficent memory " CR));
- }
- }
-}
-
// Simple print methonds
/*
@@ -825,7 +283,6 @@ void ssd1306Print(char* line1) {
OledSerial Oled(0); // Not sure about this, came from Hardwareserial
OledSerial::OledSerial(int x) {
- displayQueue = xQueueCreate(5, sizeof(displayQueueMessage*));
# if defined(WIFI_Kit_32) || defined(WIFI_LoRa_32) || defined(WIFI_LoRa_32_V2)
pinMode(RST_OLED, OUTPUT); // https://github.com/espressif/arduino-esp32/issues/4278
digitalWrite(RST_OLED, LOW);
@@ -939,7 +396,7 @@ size_t OledSerial::write(const uint8_t* buffer, size_t size) {
Display full page message on the display.
- Used to display JSON messages published from each gateway module
*/
-boolean OledSerial::displayPage(displayQueueMessage* message) {
+boolean OledSerial::displayPage(webUIQueueMessage* message) {
if (xPortGetCoreID() == CONFIG_ARDUINO_RUNNING_CORE) {
if (xSemaphoreTake(semaphoreOLEDOperation, pdMS_TO_TICKS(30000)) == pdTRUE) {
display->normalDisplay();
@@ -1004,7 +461,6 @@ String stateSSD1306Display() {
JsonObject DISPLAYdata = jsonBuffer.to();
DISPLAYdata["onstate"] = (bool)displayState;
DISPLAYdata["brightness"] = (int)displayBrightness;
- DISPLAYdata["displaymetric"] = (bool)displayMetric;
DISPLAYdata["display-flip"] = (bool)displayFlip;
DISPLAYdata["idlelogo"] = (bool)idlelogo;
DISPLAYdata["log-oled"] = (bool)logToOLEDDisplay;
diff --git a/main/ZgatewayBT.ino b/main/ZgatewayBT.ino
index e722e32..590e992 100644
--- a/main/ZgatewayBT.ino
+++ b/main/ZgatewayBT.ino
@@ -104,7 +104,7 @@ void BTConfig_init() {
unsigned long timeBetweenConnect = 0;
unsigned long timeBetweenActive = 0;
-void stateBTMeasures(bool start) {
+String stateBTMeasures(bool start) {
StaticJsonDocument jsonBuffer;
JsonObject jo = jsonBuffer.to();
jo["bleconnect"] = BTConfig.bleConnect;
@@ -135,9 +135,12 @@ void stateBTMeasures(bool start) {
Log.notice(F("BT sys: "));
serializeJsonPretty(jsonBuffer, Serial);
Serial.println();
- return; // Do not try to erase/write/send config at startup
+ return ""; // Do not try to erase/write/send config at startup
}
+ String output;
+ serializeJson(jo, output);
pub(subjectBTtoMQTT, jo);
+ return (output);
}
void BTConfig_fromJson(JsonObject& BTdata, bool startup = false) {
@@ -1091,9 +1094,6 @@ void PublishDeviceData(JsonObject& BLEdata, bool processBLEData) {
if (!BTConfig.pubRandomMACs && (BLEdata["type"].as()).compare("RMAC") == 0) {
return;
}
- if (BLEdata.containsKey("type") && (BLEdata.containsKey("model") || BLEdata.containsKey("distance"))) { // Only display sensor data with type
- pubOled(subjectBTtoMQTT, BLEdata);
- }
if (!BTConfig.pubAdvData) {
BLEdata.remove("servicedatauuid");
BLEdata.remove("servicedata");
@@ -1101,7 +1101,7 @@ void PublishDeviceData(JsonObject& BLEdata, bool processBLEData) {
BLEdata.remove("mac_type");
BLEdata.remove("adv_type");
// tag device properties
- BLEdata.remove("type");
+ // BLEdata.remove("type"); type is used by the WebUI module to determine the template used to display the signal
BLEdata.remove("cidc");
BLEdata.remove("acts");
BLEdata.remove("cont");
diff --git a/main/ZsensorBME280.ino b/main/ZsensorBME280.ino
index b7d05d3..b82e21b 100644
--- a/main/ZsensorBME280.ino
+++ b/main/ZsensorBME280.ino
@@ -193,7 +193,6 @@ void MeasureTempHumAndPressure() {
}
if (BME280data.size() > 0) {
pub(BMETOPIC, BME280data);
- pubOled(BMETOPIC, BME280data);
}
}
diff --git a/main/ZwebUI.ino b/main/ZwebUI.ino
new file mode 100644
index 0000000..593301c
--- /dev/null
+++ b/main/ZwebUI.ino
@@ -0,0 +1,1632 @@
+/*
+ OpenMQTTGateway - ESP8266 or Arduino program for home automation
+
+ Act as a wifi or ethernet gateway between your 433mhz/infrared IR signal and a 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 .
+*/
+#include "User_config.h"
+#if defined(ZwebUI) && defined(ESP32)
+# include
+# include
+# include // 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);
+
+boolean displayMetric = DISPLAY_METRIC;
+
+/*------------------- Local functions ----------------------*/
+
+void notFound();
+void handleRoot(); // "/"
+
+void handleCS(); // Console
+void handleCN(); // Configuration
+
+void handleIN(); // Information
+
+void handleRT(); // Restart
+void handleCL(); // Configure Cloud
+void handleTK(); // Return Cloud token
+
+/*------------------- External functions ----------------------*/
+
+esp_err_t nvs_flash_erase(void);
+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 DRAM
+
+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
+
+# ifdef ESP32
+/*********************************************************************************************\
+ * 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;
+}
+
+# endif // ESP32
+
+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());
+ 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}" + String(currentWebUIMessage->title) + "{e}{s}" + String(currentWebUIMessage->line1) + "{e}{s}" + String(currentWebUIMessage->line2) + "{e}{s}" + String(currentWebUIMessage->line3) + "{e}{s}" + String(currentWebUIMessage->line4) + "{e}");
+ } else {
+ server.send(200, "application/json", "{t}{s}Uptime:{m}" + String(uptime()) + "{e}");
+ }
+ } 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
+
+# if defined(ESP8266)
+ ESP.reset();
+# else
+ ESP.restart();
+# endif
+ } 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_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 /WI - Configure WiFi Page
+ *
+ */
+void handleWI() {
+ WEBUI_TRACE_LOG(F("handleWI: 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("handleWI Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).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(script);
+ response += String(style);
+ snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_wifi_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 /MQ - Configure MQTT Page
+ *
+ */
+void handleMQ() {
+ WEBUI_TRACE_LOG(F("handleMQ: 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("handleMQ Arg: %d, %s=%s" CR), i, server.argName(i).c_str(), server.arg(i).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 MQTT").c_str());
+ String response = String(buffer);
+ response += String(script);
+ response += String(style);
+ // mqtt server, mqtt port, client id, mqtt username, mqtt password, topic, Full Topic
+ snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, config_mqtt_body, jsonChar, gateway_name, mqtt_server, mqtt_port, gateway_name, mqtt_user, mqtt_pass, mqtt_topic, "");
+ response += String(buffer);
+ snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
+ response += String(buffer);
+ server.send(200, "text/html", response);
+}
+
+/**
+ * @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());
+ 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);
+
+ nvs_flash_erase();
+ ESP.restart();
+ } 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());
+ 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(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(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());
+ 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());
+ 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
BT}2}1"; // }1 the bracket is not needed as the previous message ends with }
+ informationDisplay += stateBTMeasures(false);
+# endif
+# if defined(ZdisplaySSD1306)
+ informationDisplay += "1
SSD1306}2}1"; // }1 the bracket is not needed as the previous message ends with }
+ informationDisplay += stateSSD1306Display();
+# endif
+# if defined(ZgatewayCloud)
+ informationDisplay += "1
Cloud}2}1";
+ informationDisplay += stateCLOUDStatus();
+# endif
+ informationDisplay += "1
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 /UP - Firmware Upgrade Page
+ *
+ */
+void handleUP() {
+ WEBUI_TRACE_LOG(F("handleUP: 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("handleUP 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) + " - Firmware Upgrade").c_str());
+ String response = String(buffer);
+ response += String(script);
+ response += String(style);
+ snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, upgrade_body, jsonChar, gateway_name, "http://upgrade/");
+ response += String(buffer);
+ snprintf(buffer, WEB_TEMPLATE_BUFFER_MAX_SIZE, footer, OMG_VERSION);
+ response += String(buffer);
+ server.send(200, "text/html", response);
+ }
+}
+
+uint32_t logIndex = 0;
+
+/**
+ * @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());
+ 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());
+ receivingMQTT((char*)cmdTopic.c_str(), (char*)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";
+ }
+ message += String(line, len - 1);
+ 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) + " - Configuration").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() {
+# 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));
+
+ 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
+ server.on("/up", handleUP); // Firmware Upgrade
+
+ server.on("/cn", handleCN); // Configuration
+ server.on("/wi", handleWI); // Configuration Wifi
+ server.on("/mq", handleMQ); // Configuration MQTT
+# if defined(ZgatewayCloud)
+ server.on("/cl", handleCL); // Cloud configuration
+ server.on("/tk", handleTK); // Store Device Token
+# endif
+
+ server.on("/rt", handleRT); // Reset configuration ( Erase and Restart )
+ server.begin();
+
+ Log.begin(LOG_LEVEL, &WebLog);
+
+ 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 nextWebUIPage = uptime() + DISPLAY_WEBUI_INTERVAL;
+
+void WebUILoop() {
+ server.handleClient();
+
+ if (uptime() >= nextWebUIPage && uxQueueMessagesWaiting(webUIQueue)) {
+ webUIQueueMessage* message = nullptr;
+ xQueueReceive(webUIQueue, &message, portMAX_DELAY);
+
+ if (currentWebUIMessage) {
+ free(currentWebUIMessage);
+ }
+ currentWebUIMessage = message;
+ nextWebUIPage = uptime() + DISPLAY_WEBUI_INTERVAL;
+ }
+}
+
+void MQTTtoWebUI(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();
+ 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 {
+ // pub(subjectWebUItoMQTT, "{\"Status\": \"Error\"}"); // Fail feedback
+ Log.error(F("[ WebUI ] MQTTtoWebUI Fail json" CR), WebUIdata);
+ }
+ }
+}
+
+String stateWebUIStatus() {
+ //Publish display state
+ StaticJsonDocument jsonBuffer;
+ JsonObject WebUIdata = jsonBuffer.to();
+ WebUIdata["displayMetric"] = (bool)displayMetric;
+ WebUIdata["displayQueue"] = uxQueueMessagesWaiting(webUIQueue);
+ ;
+
+ String output;
+ serializeJson(WebUIdata, output);
+
+ // WebUIdata["currentMessage"] = currentWebUIMessage;
+ pub(subjectWebUItoMQTT, WebUIdata);
+ return output;
+}
+
+bool WebUIConfig_save() {
+ StaticJsonDocument jsonBuffer;
+ JsonObject jo = jsonBuffer.to();
+ jo["displayMetric"] = (bool)displayMetric;
+ // Save config into NVS (non-volatile storage)
+ String conf = "";
+ serializeJson(jsonBuffer, conf);
+ preferences.begin(Gateway_Short_Name, false);
+ preferences.putString("WebUIConfig", conf);
+ preferences.end();
+ return true;
+}
+
+void WebUIConfig_init() {
+ boolean displayMetric = DISPLAY_METRIC;
+ Log.notice(F("WebUI config initialised" CR));
+}
+
+bool WebUIConfig_load() {
+ StaticJsonDocument jsonBuffer;
+ preferences.begin(Gateway_Short_Name, true);
+ String exists = preferences.getString("WebUIConfig", "{}");
+ if (exists != "{}") {
+ 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();
+ displayMetric = jo["displayMetric"].as();
+ 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"): {
+ // {"uptime":456356,"version":"lilygo-rtl_433-test-A-v1.1.1-25-g574177d[lily-cloud]","freemem":125488,"mqttport":"1883","mqttsecure":false,"freestack":3752,"rssi":-36,"SSID":"The_Beach","BSSID":"64:A5:C3:69:C3:38","ip":"192.168.1.239","mac":"4C:75:25:A8:D5:D8","actRec":3,"mhz":433.92,"RTLRssiThresh":-98,"RTLRssi":-108,"RTLAVGRssi":-107,"RTLCnt":121707,"RTLOOKThresh":90,"modules":["LILYGO_OLED","CLOUD","rtl_433"]}
+
+ // 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"): {
+ // {"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 id = data["id"];
+ String channel = data["channel"];
+ String line2 = "id: " + id + " 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 ";
+ }
+ }
+
+ line3.toCharArray(message->line3, WEBUI_TEXT_WIDTH);
+
+ // Line 4
+
+ String rssi = data["rssi"];
+ String battery_ok = data["battery_ok"];
+
+ String line4 = "batt: " + battery_ok + " rssi: " + rssi;
+ line4.toCharArray(message->line4, WEBUI_TEXT_WIDTH);
+
+ // Queue completed message
+
+ if (xQueueSend(webUIQueue, (void*)&message, 0) != pdTRUE) {
+ Log.error(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 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.error(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 + " ";
+ }
+
+ } 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") {
+ 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().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.error(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.error(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
\ No newline at end of file
diff --git a/main/config_BT.h b/main/config_BT.h
index f0d84c2..112a7f5 100644
--- a/main/config_BT.h
+++ b/main/config_BT.h
@@ -34,7 +34,7 @@ extern void launchBTDiscovery(bool overrideDiscovery);
extern void stopProcessing();
extern void startProcessing();
extern void lowPowerESP32();
-extern void stateBTMeasures(bool);
+extern String stateBTMeasures(bool);
#ifdef ESP32
extern int btQueueBlocked;
diff --git a/main/config_RF.h b/main/config_RF.h
index ec02eea..7cfb8ba 100644
--- a/main/config_RF.h
+++ b/main/config_RF.h
@@ -246,8 +246,6 @@ bool validFrequency(float mhz) {
int currentReceiver = -1;
-extern void stateMeasures(); // Send a status message
-
# if !defined(ZgatewayRFM69) && !defined(ZactuatorSomfy)
# if defined(ESP8266) || defined(ESP32)
// Check if a receiver is available
diff --git a/main/config_SSD1306.h b/main/config_SSD1306.h
index 9a77cca..6f66229 100644
--- a/main/config_SSD1306.h
+++ b/main/config_SSD1306.h
@@ -39,6 +39,7 @@
#include
#include "SSD1306Wire.h"
+#include "config_WebUI.h"
/*-------------------DEFINE LOG LEVEL----------------------*/
@@ -50,8 +51,14 @@
# define LOG_TO_OLED false // Default to not display log messages on display
#endif
-#ifndef JSON_TO_OLED
-# define JSON_TO_OLED true // Default to displaying JSON messages on the display
+#ifndef ZwebUI
+# ifndef JSON_TO_OLED
+# define JSON_TO_OLED true // Default to displaying JSON messages on the display
+# endif
+#else
+# ifndef JSON_TO_OLED
+# define JSON_TO_OLED false // If WebUI is NOT included within the build, the JSON message parser is not included within the build and the JSON messages are not available for display
+# endif
#endif
#ifndef DISPLAY_PAGE_INTERVAL
@@ -62,10 +69,6 @@
# define DISPLAY_IDLE_LOGO true // Display the OMG logo when idle
#endif
-#ifndef DISPLAY_METRIC
-# define DISPLAY_METRIC true // Units used for display of sensor data
-#endif
-
#ifndef DISPLAY_FLIP
# define DISPLAY_FLIP true // Flip display orientation
#endif
@@ -119,30 +122,6 @@ void ssd1306Print(char*, char*, char*);
void ssd1306Print(char*, char*);
void ssd1306Print(char*);
-#define pubOled(...) ssd1306PubPrint(__VA_ARGS__)
-void ssd1306PubPrint(const char*, JsonObject&);
-
-// Structure for queueing OMG messages to the display
-
-/*
-Structure for queueing OMG messages to the display.
-Length of each line is OLED_TEXT_WIDTH
-- title
-- line1
-- line2
-- line3
-- line4
-*/
-struct displayQueueMessage {
- char title[OLED_TEXT_WIDTH];
- char line1[OLED_TEXT_WIDTH];
- char line2[OLED_TEXT_WIDTH];
- char line3[OLED_TEXT_WIDTH];
- char line4[OLED_TEXT_WIDTH];
-};
-
-displayQueueMessage* currentOledMessage;
-
/*-------------------End of Global Variables----------------------*/
// This pattern was borrowed from HardwareSerial and modified to support the ssd1306 display
@@ -152,7 +131,7 @@ public:
OledSerial(int);
void begin();
void drawLogo(int xshift, int yshift);
- boolean displayPage(displayQueueMessage*);
+ boolean displayPage(webUIQueueMessage*);
SSD1306Wire* display;
@@ -194,22 +173,4 @@ protected:
extern OledSerial Oled;
-/*------------------- Unit Conversion Functions ----------------------*/
-
-#define convert_kmph2mph(kmph) (kmph * (1.0f / 1.609344f))
-
-#define convert_mph2kmph(mph) (mph * 1.609344f)
-
-#define convert_mm2inch(mm) (mm * 0.039370f)
-
-#define convert_inch2mm(inch) (inch * 25.4f)
-
-#define convert_kpa2psi(kpa) (kpa * (1.0f / 6.89475729f))
-
-#define convert_psi2kpa(psi) (psi * 6.89475729f)
-
-#define convert_hpa2inhg(hpa) (hpa * (1.0f / 33.8639f))
-
-#define convert_inhg2hpa(inhg) (inhg * 33.8639f)
-
#endif
diff --git a/main/config_WebContent.h b/main/config_WebContent.h
new file mode 100644
index 0000000..7d68c9a
--- /dev/null
+++ b/main/config_WebContent.h
@@ -0,0 +1,94 @@
+/*
+ OpenMQTTGateway - ESP8266 or Arduino program for home automation
+
+ Act as a wifi or ethernet gateway between your 433mhz/infrared IR signal and a MQTT broker
+ Send and receiving command by MQTT
+
+ This files enables to set your parameter for the DHT11/22 sensor
+
+ 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 .
+*/
+#ifndef config_WebContent_h
+#define config_WebContent_h
+
+/*------------------- ----------------------*/
+
+// TODO: Create a script to generate these from WebPack
+
+#define body_footer_main_menu "
"
+#define body_footer_config_menu "";
+#define body_header "%s
%s
"
+
+#define button_upgrade "
"
+
+// Configuration Menu
+
+#define configure_1 "
"
+#define configure_2 "
"
+#if defined(ZgatewayCloud)
+# define configure_3 "
"
+#else
+# define configure_3
+#endif
+#define configure_4
+#define configure_5
+#define configure_6
+#define configure_7
+#define configure_8
+
+/*------------------- ----------------------*/
+
+const char header_html[] = "
%s";
+
+const char style[] = "";
+
+const char root_body[] = body_header "
" button_upgrade "
";
+
+const char config_body[] = body_header "" configure_1 "" configure_2 "" configure_3 "" configure_4 "" configure_5 "" configure_6 "" configure_7 "" configure_8 "
" body_footer_main_menu;
+
+const char reset_body[] = body_header "
%s
Device will restart in a few seconds
" body_footer_main_menu;
+
+const char config_cloud_body[] = body_header "
" body_footer_config_menu;
+
+const char token_body[] = body_header "div style='text-align:center;'>Link Cloud Account
Cloud was successfully linked
";
+
+const char console_body[] = body_header "
" body_footer_main_menu;
+
+const char information_body[] = body_header "" body_footer_main_menu;
+
+const char upgrade_body[] = body_header "
Upload started ...
" body_footer_main_menu;
+
+const char config_wifi_body[] = body_header "
" body_footer_config_menu;
+
+// mqtt server, mqtt port, client id, mqtt username, mqtt password, topic, Full Topic
+
+const char config_mqtt_body[] = body_header "" body_footer_config_menu;
+
+const char footer[] = "";
+
+#endif
\ No newline at end of file
diff --git a/main/config_WebUI.h b/main/config_WebUI.h
new file mode 100644
index 0000000..dbf6026
--- /dev/null
+++ b/main/config_WebUI.h
@@ -0,0 +1,161 @@
+/*
+ OpenMQTTGateway - ESP8266 or Arduino program for home automation
+
+ Act as a wifi or ethernet gateway between your 433mhz/infrared IR signal and a MQTT broker
+ Send and receiving command by MQTT
+
+ This files enables to set your parameter for the DHT11/22 sensor
+
+ 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 .
+*/
+#ifndef config_WebUI_h
+#define config_WebUI_h
+
+#include
+
+#define WEBUI_TEXT_WIDTH 128
+
+/*------------------- Optional Compiler Directives ----------------------*/
+
+#ifndef WEB_TEMPLATE_BUFFER_MAX_SIZE
+# define WEB_TEMPLATE_BUFFER_MAX_SIZE 2000
+#endif
+
+#ifndef DISPLAY_METRIC
+# define DISPLAY_METRIC true // Units used for display of sensor data
+#endif
+
+#ifndef DISPLAY_WEBUI_INTERVAL
+# define DISPLAY_WEBUI_INTERVAL 3 // Number of seconds between json message displays
+#endif
+
+#ifdef WEBUI_DEVELOPMENT
+# pragma message("[WebUI] Usage of SPIFFS for missing WebUI content enabled")
+# define FILESYSTEM SPIFFS
+# define WEBUI_TRACE_LOGGING
+#endif
+
+#ifdef WEBUI_TRACE_LOGGING
+# define WEBUI_TRACE_LOG(...) Log.trace(__VA_ARGS__)
+#else
+# define WEBUI_TRACE_LOG(...)
+#endif
+
+/*------------------- End of Compiler Directives ----------------------*/
+
+// Structure for queueing OMG messages to the display
+
+/*
+Structure for queueing OMG messages to the display.
+Length of each line is WEBUI_TEXT_WIDTH
+- title
+- line1
+- line2
+- line3
+- line4
+*/
+
+struct webUIQueueMessage {
+ char title[WEBUI_TEXT_WIDTH];
+ char line1[WEBUI_TEXT_WIDTH];
+ char line2[WEBUI_TEXT_WIDTH];
+ char line3[WEBUI_TEXT_WIDTH];
+ char line4[WEBUI_TEXT_WIDTH];
+} webUIQueueMessage_t;
+
+/*------------------- Global Functions and Variables ----------------------*/
+
+#ifdef ZwebUI
+# define pubWebUI(...) webUIPubPrint(__VA_ARGS__)
+void webUIPubPrint(const char*, JsonObject&);
+#endif
+void WebUISetup();
+void WebUILoop();
+void MQTTtoWebUI(char*, JsonObject&);
+
+String stateWebUIStatus();
+
+webUIQueueMessage* currentWebUIMessage;
+
+/*------------------- End of Global Functions ----------------------*/
+
+#define subjectMQTTtoWebUIset "/commands/MQTTtoWebUI/config"
+#define subjectWebUItoMQTT "/WebUItoMQTT"
+
+/*------------------- Unit Conversion Functions ----------------------*/
+
+#define convert_kmph2mph(kmph) (kmph * (1.0f / 1.609344f))
+
+#define convert_mph2kmph(mph) (mph * 1.609344f)
+
+#define convert_mm2inch(mm) (mm * 0.039370f)
+
+#define convert_inch2mm(inch) (inch * 25.4f)
+
+#define convert_kpa2psi(kpa) (kpa * (1.0f / 6.89475729f))
+
+#define convert_psi2kpa(psi) (psi * 6.89475729f)
+
+#define convert_hpa2inhg(hpa) (hpa * (1.0f / 33.8639f))
+
+#define convert_inhg2hpa(inhg) (inhg * 33.8639f)
+
+/*------------------- Take over serial output and split to ----------------------*/
+
+class SerialWeb : public Stream {
+public:
+ SerialWeb(int);
+ void begin();
+
+ int available(void); // Dummy functions
+ int peek(void); // Dummy functions
+ int read(void); // Dummy functions
+ void flush(void); // Dummy functions
+
+ inline size_t write(uint8_t x) {
+ return write(&x, 1);
+ }
+
+ size_t write(const uint8_t* buffer, size_t size);
+ inline size_t write(const char* buffer, size_t size) {
+ return write((uint8_t*)buffer, size);
+ }
+ inline size_t write(const char* s) {
+ return write((uint8_t*)s, strlen(s));
+ }
+ inline size_t write(unsigned long n) {
+ return write((uint8_t)n);
+ }
+ inline size_t write(long n) {
+ return write((uint8_t)n);
+ }
+ inline size_t write(unsigned int n) {
+ return write((uint8_t)n);
+ }
+ inline size_t write(int n) {
+ return write((uint8_t)n);
+ }
+
+protected:
+};
+
+extern SerialWeb WebLog;
+
+/*------------------- Take over serial output and split to ----------------------*/
+
+#endif
\ No newline at end of file
diff --git a/main/main.ino b/main/main.ino
index 5c5b4e5..6fe7e3b 100644
--- a/main/main.ino
+++ b/main/main.ino
@@ -70,6 +70,9 @@ struct GfSun2000Data {};
#endif
// Modules config inclusion
+#if defined(ZwebUI) && defined(ESP32)
+# include "config_WebUI.h"
+#endif
#if defined(ZgatewayRF) || defined(ZgatewayRF2) || defined(ZgatewayPilight) || defined(ZactuatorSomfy) || defined(ZgatewayRTL_433)
# include "config_RF.h"
#endif
@@ -396,6 +399,7 @@ void pub(const char* topicori, JsonObject& data) {
#if jsonPublishing
Log.trace(F("jsonPubl - ON" CR));
pubMQTT(topic, dataAsString.c_str());
+ pubWebUI(topicori, data);
#endif
#if simplePublishing
@@ -811,6 +815,11 @@ void setup() {
setup_ethernet();
#endif
+#if defined(ZwebUI) && defined(ESP32)
+ WebUISetup();
+ modules.add(ZwebUI);
+#endif
+
#if defined(ESP8266) || defined(ESP32)
if (mqtt_secure) {
eClient = new WiFiClientSecure;
@@ -1184,6 +1193,7 @@ void setup_wifi() {
# endif
}
Log.notice(F("WiFi ok with manual config credentials" CR));
+ displayPrint("Wifi connected");
}
#elif defined(ESP8266) || defined(ESP32)
@@ -1610,6 +1620,9 @@ void loop() {
# endif
# ifdef ZdisplaySSD1306
stateSSD1306Display();
+# endif
+# if defined(ZwebUI) && defined(ESP32)
+ stateWebUIStatus();
# endif
}
if (now > (timer_sys_checks + (TimeBetweenCheckingSYS * 1000)) || !timer_sys_checks) {
@@ -1729,6 +1742,9 @@ void loop() {
if (disc)
launchRTL_433Discovery(publishDiscovery);
# endif
+#endif
+#if defined(ZwebUI) && defined(ESP32)
+ WebUILoop();
#endif
} else {
// MQTT disconnected
@@ -1827,7 +1843,7 @@ String UTCtimestamp() {
#endif
#if defined(ESP8266) || defined(ESP32) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__)
-void stateMeasures() {
+String stateMeasures() {
StaticJsonDocument jsonBuffer;
JsonObject SYSdata = jsonBuffer.to();
SYSdata["uptime"] = uptime();
@@ -1919,8 +1935,12 @@ void stateMeasures() {
}
# endif
SYSdata["modules"] = modules;
+ String output;
+ serializeJson(SYSdata, output);
+
pub(subjectSYStoMQTT, SYSdata);
pubOled(subjectSYStoMQTT, SYSdata);
+ return output;
}
#endif
diff --git a/platformio.ini b/platformio.ini
index 4b1b207..ab79495 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -187,6 +187,7 @@ build_flags =
'-DMQTTsetMQTT'
'-DMQTT_HTTPS_FW_UPDATE'
;'-DCORE_DEBUG_LEVEL=4'
+ '-DZwebUI="WebUI"' ; enable WebUI as a default for all ESP32 builds ( the module only enables for ESP32 based builds )
[com-arduino]
lib_deps =