From 2af991eb8eeab88575e4bd75907351563644276b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20M=C3=BChl?= <31169771+Blueforcer@users.noreply.github.com> Date: Thu, 1 Jun 2023 21:51:59 +0200 Subject: [PATCH] 0.67 - Adds wakeup command to the notification API. closes #145 - Increase dev json buffer. closes #147 - fixes a bug with mulitple pages command. closes #146 - clear json buffer after modifing the apps vector. - adds hostname - adds new HA sensor to get the mqtt prefix - fixes a bug where alarms doesnt work - Switch http api returns an error, if the app doesnt exist. closes #140 --- docs/api.md | 1 + src/Apps.h | 10 +++++++ src/Dictionary.cpp | 6 +++++ src/Dictionary.h | 4 +++ src/DisplayManager.cpp | 58 ++++++++++++++++++++++++++++------------ src/DisplayManager.h | 2 +- src/Globals.cpp | 4 +-- src/MQTTManager.cpp | 11 ++++++-- src/MatrixDisplayUi.cpp | 11 +++----- src/MatrixDisplayUi.h | 2 +- src/PeripheryManager.cpp | 2 +- src/ServerManager.cpp | 15 +++++++++-- 12 files changed, 93 insertions(+), 33 deletions(-) diff --git a/docs/api.md b/docs/api.md index 1362bc6b..5c68f9e9 100644 --- a/docs/api.md +++ b/docs/api.md @@ -102,6 +102,7 @@ The JSON object has the following properties, | `draw` | array of objects | Array of drawing instructions. Each object represents a drawing command. | See the drawing instructions below | | `lifetime` | integer | Removes the custom app when there is no update after the given time in seconds | 0 | | `stack` | boolean | Defines if the **notification** will be stacked. false will immediately replace the current notification | true | +| `wakeup` | boolean | If the Matrix is off, the notification will wake it up for the time of the notification. | false | Color values can have a hex string or an array of R,G,B values: diff --git a/src/Apps.h b/src/Apps.h index 9be4d7a5..b3bdb0fb 100644 --- a/src/Apps.h +++ b/src/Apps.h @@ -93,6 +93,7 @@ struct Notification uint16_t pColor; uint16_t background = 0; uint16_t pbColor; + bool wakeup; }; std::vector notifications; bool notifyFlag = false; @@ -675,6 +676,11 @@ void NotifyApp(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, GifPlayer // Set current app name CURRENT_APP = "Notification"; + if (notifications[0].wakeup && MATRIX_OFF) + { + DisplayManager.setBrightness(BRIGHTNESS); + } + // Check if notification duration has expired or if repeat count is 0 and hold is not enabled if ((((millis() - notifications[0].startime >= notifications[0].duration) && notifications[0].repeat == -1) || notifications[0].repeat == 0) && !notifications[0].hold) { @@ -685,6 +691,10 @@ void NotifyApp(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, GifPlayer notifications[1].startime = millis(); } notifications.erase(notifications.begin()); + if (notifications[0].wakeup && MATRIX_OFF) + { + DisplayManager.setBrightness(0); + } return; } diff --git a/src/Dictionary.cpp b/src/Dictionary.cpp index ac823b1f..49a1c837 100644 --- a/src/Dictionary.cpp +++ b/src/Dictionary.cpp @@ -51,6 +51,12 @@ const char HAappID[] PROGMEM = {"%s_app"}; const char HAappIcon[] PROGMEM = {"mdi:apps"}; const char HAappName[] PROGMEM = {"Current app"}; + +const char HAIDID[] PROGMEM = {"%s_id"}; +const char HAIDIcon[] PROGMEM = {"mdi:id-card"}; +const char HAIDName[] PROGMEM = {"Device topic"}; + + const char HAtempID[] PROGMEM = {"%s_temp"}; const char HAtempIcon[] PROGMEM = {"mdi:thermometer"}; const char HAtempName[] PROGMEM = {"Temperature"}; diff --git a/src/Dictionary.h b/src/Dictionary.h index 01c5ceff..69baec1a 100644 --- a/src/Dictionary.h +++ b/src/Dictionary.h @@ -51,6 +51,10 @@ extern const char HAappID[]; extern const char HAappIcon[]; extern const char HAappName[]; +extern const char HAIDID[]; +extern const char HAIDIcon[]; +extern const char HAIDName[]; + extern const char HAtempID[]; extern const char HAtempIcon[]; extern const char HAtempName[]; diff --git a/src/DisplayManager.cpp b/src/DisplayManager.cpp index d26db01d..05668644 100644 --- a/src/DisplayManager.cpp +++ b/src/DisplayManager.cpp @@ -59,7 +59,13 @@ DisplayManager_ &DisplayManager = DisplayManager.getInstance(); void DisplayManager_::setBrightness(int bri) { - if (MATRIX_OFF && !ALARM_ACTIVE) + bool wakeup; + if (!notifications.empty()) + { + wakeup = notifications[0].wakeup; + } + + if (MATRIX_OFF && !ALARM_ACTIVE && !wakeup) { matrix->setBrightness(0); } @@ -298,10 +304,8 @@ bool parseFragmentsText(const String &jsonText, std::vector &colors, s { colors.clear(); fragments.clear(); - StaticJsonDocument<2048> doc; DeserializationError error = deserializeJson(doc, jsonText); - if (error) { return false; @@ -311,7 +315,7 @@ bool parseFragmentsText(const String &jsonText, std::vector &colors, s for (JsonObject fragmentObj : fragmentArray) { - String textFragment = fragmentObj["t"].as(); + String textFragment = utf8ascii(fragmentObj["t"].as()); uint16_t color; if (fragmentObj.containsKey("c")) { @@ -331,7 +335,6 @@ bool parseFragmentsText(const String &jsonText, std::vector &colors, s bool DisplayManager_::parseCustomPage(const String &name, const char *json) { - if ((strcmp(json, "") == 0) || (strcmp(json, "{}") == 0)) { removeCustomAppFromApps(name, true); @@ -347,6 +350,7 @@ bool DisplayManager_::parseCustomPage(const String &name, const char *json) if (doc.is()) { + DEBUG_PRINTLN("Single Page"); return generateCustomPage(name, json); } else if (doc.is()) @@ -355,25 +359,26 @@ bool DisplayManager_::parseCustomPage(const String &name, const char *json) int cpIndex = 0; for (JsonVariant customPage : customPagesArray) { + Serial.printf("Multiple Page: %i", cpIndex); JsonObject customPageObject = customPage.as(); String customPageJson; serializeJson(customPageObject, customPageJson); - if (generateCustomPage(name + String(cpIndex), customPageJson.c_str())) - return false; + generateCustomPage(name + String(cpIndex), customPageJson.c_str()); ++cpIndex; } } - doc.clear(); + return true; } bool DisplayManager_::generateCustomPage(const String &name, const char *json) { - DynamicJsonDocument doc(4096); DeserializationError error = deserializeJson(doc, json); if (error) { + DEBUG_PRINTLN("PARSING FAILED"); + DEBUG_PRINTLN(error.c_str()); doc.clear(); return false; } @@ -587,6 +592,7 @@ bool DisplayManager_::generateCustomPage(const String &name, const char *json) pushCustomApp(name, pos - 1); customApps[name] = customApp; doc.clear(); + DEBUG_PRINTLN("PARSING FINISHED"); return true; } @@ -643,6 +649,7 @@ bool DisplayManager_::generateNotification(const char *json) newNotification.repeat = doc.containsKey("repeat") ? doc["repeat"].as() : -1; newNotification.rainbow = doc.containsKey("rainbow") ? doc["rainbow"].as() : false; newNotification.hold = doc.containsKey("hold") ? doc["hold"].as() : false; + newNotification.wakeup = doc.containsKey("wakeup") ? doc["wakeup"].as() : false; newNotification.pushIcon = doc.containsKey("pushIcon") ? doc["pushIcon"] : 0; newNotification.textCase = doc.containsKey("textCase") ? doc["textCase"] : 0; newNotification.textOffset = doc.containsKey("textOffset") ? doc["textOffset"] : 0; @@ -651,10 +658,6 @@ bool DisplayManager_::generateNotification(const char *json) newNotification.iconWasPushed = false; newNotification.iconPosition = 0; newNotification.scrollDelay = 0; - if (doc.containsKey("sound")) - { - PeripheryManager.playFromFile(doc["sound"].as()); - } bool autoscale = true; if (doc.containsKey("autoscale")) @@ -794,6 +797,15 @@ bool DisplayManager_::generateNotification(const char *json) notifications[0] = newNotification; } } + + if (doc.containsKey("sound")) + { + if (!MATRIX_OFF || (MATRIX_OFF && newNotification.wakeup)) + { + PeripheryManager.playFromFile(doc["sound"].as()); + } + } + doc.clear(); return true; } @@ -1134,17 +1146,24 @@ void DisplayManager_::gererateTimer(String Payload) TimerTicker.once_ms(interval, timerCallback); } -void DisplayManager_::switchToApp(const char *json) +bool DisplayManager_::switchToApp(const char *json) { DynamicJsonDocument doc(512); DeserializationError error = deserializeJson(doc, json); if (error) - return; + return false; String name = doc["name"].as(); bool fast = doc["fast"] | false; int index = findAppIndexByName(name); if (index > -1) + { ui->transitionToApp(index); + return true; + } + else + { + return false; + } } void DisplayManager_::drawProgressBar(int16_t x, int16_t y, int progress, uint16_t pColor, uint16_t pbColor) @@ -1250,10 +1269,11 @@ void DisplayManager_::updateAppVector(const char *json) { DEBUG_PRINTLN(F("New apps vector received")); DEBUG_PRINTLN(json); - StaticJsonDocument<2048> doc; + DynamicJsonDocument doc(2048); DeserializationError error = deserializeJson(doc, json); if (error) { + doc.clear(); DEBUG_PRINTLN(F("Failed to parse json")); return; } @@ -1319,11 +1339,12 @@ void DisplayManager_::updateAppVector(const char *json) saveSettings(); sendAppLoop(); setAutoTransition(AUTO_TRANSITION); + doc.clear(); } String DisplayManager_::getStats() { - StaticJsonDocument<256> doc; + StaticJsonDocument<512> doc; char buffer[5]; #ifdef ULANZI doc[BatKey] = BATTERY_PERCENT; @@ -1346,6 +1367,9 @@ String DisplayManager_::getStats() doc[UpdateKey] = UPDATE_AVAILABLE; doc[MessagesKey] = RECEIVED_MESSAGES; doc[VersionKey] = VERSION; + doc[F("indicator1")] = ui->indicator1State; + doc[F("indicator2")] = ui->indicator2State; + doc[F("indicator3")] = ui->indicator3State; String jsonString; return serializeJson(doc, jsonString), jsonString; } diff --git a/src/DisplayManager.h b/src/DisplayManager.h index 838583b4..927dca86 100644 --- a/src/DisplayManager.h +++ b/src/DisplayManager.h @@ -54,7 +54,7 @@ class DisplayManager_ bool generateCustomPage(const String &name, const char *json); void printText(int16_t x, int16_t y, const char *text, bool centered, byte textCase); bool setAutoTransition(bool active); - void switchToApp(const char *json); + bool switchToApp(const char *json); void setNewSettings(const char *json); void drawJPG(uint16_t x, uint16_t y, fs::File jpgFile); void drawProgressBar(int16_t x, int16_t y, int progress, uint16_t pColor, uint16_t pbColor); diff --git a/src/Globals.cpp b/src/Globals.cpp index 6dddfe09..16857e11 100644 --- a/src/Globals.cpp +++ b/src/Globals.cpp @@ -41,7 +41,7 @@ void loadDevSettings() if (LittleFS.exists("/dev.json")) { File file = LittleFS.open("/dev.json", "r"); - DynamicJsonDocument doc(128); + DynamicJsonDocument doc(256); DeserializationError error = deserializeJson(doc, file); if (error) { @@ -216,7 +216,7 @@ IPAddress gateway; IPAddress subnet; IPAddress primaryDNS; IPAddress secondaryDNS; -const char *VERSION = "0.66"; +const char *VERSION = "0.67"; String MQTT_HOST = ""; uint16_t MQTT_PORT = 1883; diff --git a/src/MQTTManager.cpp b/src/MQTTManager.cpp index 5fa1d352..cccec51c 100644 --- a/src/MQTTManager.cpp +++ b/src/MQTTManager.cpp @@ -20,10 +20,10 @@ HASwitch *transition = nullptr; #ifdef ULANZI HASensor *battery = nullptr; #endif -HASensor *temperature, *humidity, *illuminance, *uptime, *strength, *version, *ram, *curApp = nullptr; +HASensor *temperature, *humidity, *illuminance, *uptime, *strength, *version, *ram, *curApp, *myOwnID = nullptr; HABinarySensor *btnleft, *btnmid, *btnright = nullptr; -char matID[40], ind1ID[40], ind2ID[40], ind3ID[40], briID[40], btnAID[40], btnBID[40], btnCID[40], appID[40], tempID[40], humID[40], luxID[40], verID[40], ramID[40], upID[40], sigID[40], btnLID[40], btnMID[40], btnRID[40], transID[40], doUpdateID[40], batID[40]; +char matID[40], ind1ID[40], ind2ID[40], ind3ID[40], briID[40], btnAID[40], btnBID[40], btnCID[40], appID[40], tempID[40], humID[40], luxID[40], verID[40], ramID[40], upID[40], sigID[40], btnLID[40], btnMID[40], btnRID[40], transID[40], doUpdateID[40], batID[40], myID[40]; // The getter for the instantiated singleton instance MQTTManager_ &MQTTManager_::getInstance() @@ -328,6 +328,8 @@ void onMqttConnected() mqtt.subscribe(fullTopic.c_str()); delay(30); } + if (HA_DISCOVERY) + myOwnID->setValue(MQTT_PREFIX.c_str()); } void connect() @@ -441,6 +443,11 @@ void MQTTManager_::setup() curApp->setIcon(HAappIcon); curApp->setName(HAappName); + sprintf(myID, HAIDID, macStr); + myOwnID = new HASensor(myID); + myOwnID->setIcon(HAIDIcon); + myOwnID->setName(HAIDName); + sprintf(btnBID, HAbtnbID, macStr); nextApp = new HAButton(btnBID); nextApp->setIcon(HAbtnbIcon); diff --git a/src/MatrixDisplayUi.cpp b/src/MatrixDisplayUi.cpp index a4d9eb45..2e6b7ece 100644 --- a/src/MatrixDisplayUi.cpp +++ b/src/MatrixDisplayUi.cpp @@ -139,15 +139,16 @@ void MatrixDisplayUi::previousApp() } } -void MatrixDisplayUi::switchToApp(uint8_t app) +bool MatrixDisplayUi::switchToApp(uint8_t app) { if (app >= this->AppCount) - return; + return false; this->state.ticksSinceLastStateSwitch = 0; if (app == this->state.currentApp) - return; + return false; this->state.appState = FIXED; this->state.currentApp = app; + return true; } void MatrixDisplayUi::transitionToApp(uint8_t app) @@ -381,8 +382,6 @@ void MatrixDisplayUi::setIndicator1Blink(int blink) this->indicator1Blink = blink; } - - void MatrixDisplayUi::setIndicator2Color(uint16_t color) { this->indicator2Color = color; @@ -398,8 +397,6 @@ void MatrixDisplayUi::setIndicator2Blink(int blink) this->indicator2Blink = blink; } - - void MatrixDisplayUi::setIndicator3Color(uint16_t color) { this->indicator3Color = color; diff --git a/src/MatrixDisplayUi.h b/src/MatrixDisplayUi.h index a370cd2e..7957737a 100644 --- a/src/MatrixDisplayUi.h +++ b/src/MatrixDisplayUi.h @@ -188,7 +188,7 @@ class MatrixDisplayUi /** * Switch without transition to app `app`. */ - void switchToApp(uint8_t app); + bool switchToApp(uint8_t app); /** * Transition to app `app`, when the `app` number is bigger than the current diff --git a/src/PeripheryManager.cpp b/src/PeripheryManager.cpp index 80669896..ebc4b1e6 100644 --- a/src/PeripheryManager.cpp +++ b/src/PeripheryManager.cpp @@ -349,7 +349,7 @@ void PeripheryManager_::tick() CURRENT_HUM = bme280.readHumidity(); #endif } - // checkAlarms(); + checkAlarms(); MQTTManager.sendStats(); } diff --git a/src/ServerManager.cpp b/src/ServerManager.cpp index 5994184a..67880d39 100644 --- a/src/ServerManager.cpp +++ b/src/ServerManager.cpp @@ -78,8 +78,17 @@ void addHandler() { DisplayManager.dismissNotify(); mws.webserver->send(200,F("text/plain"),F("OK")); }); mws.addHandler("/api/apps", HTTP_POST, []() { DisplayManager.updateAppVector(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,F("text/plain"),F("OK")); }); - mws.addHandler("/api/switch", HTTP_POST, []() - { DisplayManager.switchToApp(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,F("text/plain"),F("OK")); }); + mws.addHandler( + "/api/switch", HTTP_POST, []() + { + if (DisplayManager.switchToApp(mws.webserver->arg("plain").c_str())) + { + mws.webserver->send(200, F("text/plain"), F("OK")); + } + else + { + mws.webserver->send(500, F("text/plain"), F("FAILED")); + } }); mws.addHandler("/api/apps", HTTP_GET, []() { mws.webserver->send_P(200, "application/json", DisplayManager.getAppsWithIcon().c_str()); }); mws.addHandler("/api/settings", HTTP_POST, []() @@ -138,11 +147,13 @@ void ServerManager_::setup() { WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS); } + WiFi.setHostname(uniqueID); // define hostname myIP = mws.startWiFi(15000, uniqueID, "12345678"); isConnected = !(myIP == IPAddress(192, 168, 4, 1)); DEBUG_PRINTF("My IP: %d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]); if (isConnected) { + mws.addOptionBox("Network"); mws.addOption("Static IP", NET_STATIC); mws.addOption("Local IP", NET_IP);