From d75fde411a25006109fb17fc6bf588234a341862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20M=C3=BChl?= <31169771+Blueforcer@users.noreply.github.com> Date: Wed, 17 May 2023 18:48:19 +0200 Subject: [PATCH] V0.66 - Allows to decide if a notification is stacked. closes #129 - Add indicator3 to http API. closes #130 - Adds some meaningful responses to http api. closes #131 - Fixes a bug where customapp crashes without an icon. closes #132 --- docs/api.md | 10 +-- src/Apps.h | 5 +- src/DisplayManager.cpp | 58 ++++++++++------ src/DisplayManager.h | 10 +-- src/Globals.cpp | 2 +- src/PeripheryManager.cpp | 25 ++++--- src/PeripheryManager.h | 4 +- src/ServerManager.cpp | 141 ++++++++++++++++++++++++++------------- 8 files changed, 168 insertions(+), 87 deletions(-) diff --git a/docs/api.md b/docs/api.md index be2dd1fe..447bf071 100644 --- a/docs/api.md +++ b/docs/api.md @@ -76,11 +76,11 @@ You can also send a one-time notification with the same JSON format. Simply send ### JSON Properties The JSON object has the following properties, -All keys are optional, so you can send just the properties you want to use. +**All keys are optional**, so you can send just the properties you want to use. | Key | Type | Description | Default | | --- | ---- | ----------- | ------- | -| `text` | string | The text to display on the app. | N/A | +| `text` | string | The text to display. | N/A | | `textCase` | integer | Changes the Uppercase setting. 0=global setting, 1=forces uppercase; 2=shows as it sent. | 0 | | `textOffset` | integer | Sets an offset for the x position of a starting text. | 0 | | `color` | string or array of integers | The text, bar or line color | | @@ -94,13 +94,14 @@ All keys are optional, so you can send just the properties you want to use. | `sound` | string | The filename of your RTTTL ringtone file (without extension). | N/A | | `bar` | array of integers | draws a bargraph. Without icon maximum 16 values, with icon 11 values | N/A | | `line` | array of integers | draws a linechart. Without icon maximum 16 values, with icon 11 values | N/A | -| `autoscale` | boolean | enables or disables autoscaling for bar and linechart | true | +| `autoscale` | boolean | Enables or disables autoscaling for bar and linechart | true | | `progress` | integer | Shows a progressbar. Value can be 0-100 | -1 | | `progressC` | string or array of integers | The color of the progressbar | -1 | | `progressBC` | string or array of integers | The color of the progressbar background | -1 | -| `pos` | number | defines the position of your custompage in the loop, starting at 0 for the first position. This will only apply with your first push. This function is experimental | N/A | +| `pos` | number | Defines the position of your custompage in the loop, starting at 0 for the first position. This will only apply with your first push. This function is experimental | N/A | | `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` | integer | Defines if the **notification** will be stacked. false will immediately replace the current notification | true | Color values can have a hex string or an array of R,G,B values: `"#FFFFFF" or [255,255,0]` @@ -133,6 +134,7 @@ Each drawing instruction is an object with a required command key and an array o | `dc` | `[x, y, r, cl]` | Draw a circle with center at (`x`, `y`), radius `r`, and color `cl` | | `dfc` | `[x, y, r, cl]` | Draw a filled circle with center at (`x`, `y`), radius `r`, and color `cl` | | `dt` | `[x, y, t, cl]` | Draw text `t` with top-left corner at (`x`, `y`) and color `cl` | +| `db` | `[x, y, w, h, [bmp]]` | Draws a RGB565 bitmap array `[bmp]` with top-left corner at (`x`, `y`) and size of (`w`, `h`) | Color values can be a hex string or an array of R, G, B values: `"#FFFFFF" or [255, 255, 0]` diff --git a/src/Apps.h b/src/Apps.h index a5df1d48..9be4d7a5 100644 --- a/src/Apps.h +++ b/src/Apps.h @@ -381,6 +381,7 @@ void ShowCustomApp(String name, FastLED_NeoMatrix *matrix, MatrixDisplayUiState currentCustomApp = name; bool hasIcon = ca->icon; + matrix->fillRect(x, y, 32, 8, ca->background); // Calculate text and available width @@ -459,7 +460,7 @@ void ShowCustomApp(String name, FastLED_NeoMatrix *matrix, MatrixDisplayUiState { if (ca->iconWasPushed && ca->pushIcon == 1) { - ca->scrollposition = 0 + notifications[0].textOffset; + ca->scrollposition = 0 + ca->textOffset; } else { @@ -468,7 +469,7 @@ void ShowCustomApp(String name, FastLED_NeoMatrix *matrix, MatrixDisplayUiState } else { - ca->scrollposition = 0 + notifications[0].textOffset; + ca->scrollposition = 0 + ca->textOffset; } } } diff --git a/src/DisplayManager.cpp b/src/DisplayManager.cpp index fdf614f6..d26db01d 100644 --- a/src/DisplayManager.cpp +++ b/src/DisplayManager.cpp @@ -329,25 +329,25 @@ bool parseFragmentsText(const String &jsonText, std::vector &colors, s return true; } -void DisplayManager_::parseCustomPage(const String &name, const char *json) +bool DisplayManager_::parseCustomPage(const String &name, const char *json) { if ((strcmp(json, "") == 0) || (strcmp(json, "{}") == 0)) { removeCustomAppFromApps(name, true); - return; + return true; } DynamicJsonDocument doc(4096); DeserializationError error = deserializeJson(doc, json); if (error) { doc.clear(); - return; + return false; } if (doc.is()) { - generateCustomPage(name, json); + return generateCustomPage(name, json); } else if (doc.is()) { @@ -358,7 +358,8 @@ void DisplayManager_::parseCustomPage(const String &name, const char *json) JsonObject customPageObject = customPage.as(); String customPageJson; serializeJson(customPageObject, customPageJson); - generateCustomPage(name + String(cpIndex), customPageJson.c_str()); + if (generateCustomPage(name + String(cpIndex), customPageJson.c_str())) + return false; ++cpIndex; } } @@ -366,7 +367,7 @@ void DisplayManager_::parseCustomPage(const String &name, const char *json) doc.clear(); } -void DisplayManager_::generateCustomPage(const String &name, const char *json) +bool DisplayManager_::generateCustomPage(const String &name, const char *json) { DynamicJsonDocument doc(4096); @@ -374,7 +375,7 @@ void DisplayManager_::generateCustomPage(const String &name, const char *json) if (error) { doc.clear(); - return; + return false; } CustomApp customApp; @@ -586,16 +587,17 @@ void DisplayManager_::generateCustomPage(const String &name, const char *json) pushCustomApp(name, pos - 1); customApps[name] = customApp; doc.clear(); + return true; } -void DisplayManager_::generateNotification(const char *json) +bool DisplayManager_::generateNotification(const char *json) { StaticJsonDocument<4096> doc; DeserializationError error = deserializeJson(doc, json); if (error) { doc.clear(); - return; + return false; } Notification newNotification; @@ -757,13 +759,11 @@ void DisplayManager_::generateNotification(const char *json) { newNotification.isGif = false; newNotification.icon = LittleFS.open("/ICONS/" + iconFileName + ".jpg"); - } else if (LittleFS.exists("/ICONS/" + iconFileName + ".gif")) { newNotification.isGif = true; newNotification.icon = LittleFS.open("/ICONS/" + iconFileName + ".gif"); - } else { @@ -777,7 +777,25 @@ void DisplayManager_::generateNotification(const char *json) newNotification.icon = nullPointer; } - notifications.push_back(newNotification); + bool stack = doc.containsKey("stack") ? doc["stack"] : true; + + if (stack) + { + notifications.push_back(newNotification); + } + else + { + if (notifications.empty()) + { + notifications.push_back(newNotification); + } + else + { + notifications[0] = newNotification; + } + } + doc.clear(); + return true; } void DisplayManager_::loadNativeApps() @@ -1463,7 +1481,7 @@ void DisplayManager_::setIndicator3State(bool state) ui->setIndicator3State(state); } -void DisplayManager_::indicatorParser(uint8_t indicator, const char *json) +bool DisplayManager_::indicatorParser(uint8_t indicator, const char *json) { if (strcmp(json, "") == 0) @@ -1485,13 +1503,13 @@ void DisplayManager_::indicatorParser(uint8_t indicator, const char *json) default: break; } - return; + return true; } DynamicJsonDocument doc(128); DeserializationError error = deserializeJson(doc, json); if (error) - return; + return false; if (doc.containsKey("color")) { @@ -1573,6 +1591,7 @@ void DisplayManager_::indicatorParser(uint8_t indicator, const char *json) } } MQTTManager.setIndicatorState(indicator, ui->indicator1State, ui->indicator1Color); + return true; } void DisplayManager_::gammaCorrection() @@ -1932,18 +1951,18 @@ void DisplayManager_::processDrawInstructions(int16_t xOffset, int16_t yOffset, } } -void DisplayManager_::moodlight(const char *json) +bool DisplayManager_::moodlight(const char *json) { if (strcmp(json, "") == 0) { MOODLIGHT_MODE = false; - return; + return true; } DynamicJsonDocument doc(512); DeserializationError error = deserializeJson(doc, json); if (error) - return; + return false; int brightness = doc["brightness"] | BRIGHTNESS; matrix->setBrightness(brightness); @@ -1967,10 +1986,11 @@ void DisplayManager_::moodlight(const char *json) } else { - return; + return true; } MOODLIGHT_MODE = true; matrix->show(); + return true; } \ No newline at end of file diff --git a/src/DisplayManager.h b/src/DisplayManager.h index f19d5425..838583b4 100644 --- a/src/DisplayManager.h +++ b/src/DisplayManager.h @@ -50,8 +50,8 @@ class DisplayManager_ void setBrightness(int); void setTextColor(uint16_t color); void setFPS(uint8_t); - void generateNotification(const char *json); - void generateCustomPage(const String &name, const char *json); + bool generateNotification(const char *json); + 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); @@ -78,7 +78,7 @@ class DisplayManager_ void setIndicator3State(bool state); void reorderApps(const String &jsonString); void gammaCorrection(); - void indicatorParser(uint8_t indicator, const char *json); + bool indicatorParser(uint8_t indicator, const char *json); void showSleepAnimation(); void showCurtainEffect(); void sendAppLoop(); @@ -86,8 +86,8 @@ class DisplayManager_ String ledsAsJson(); String getAppsWithIcon(); void startArtnet(); - void parseCustomPage(const String &name, const char *json); - void moodlight(const char *json); + bool parseCustomPage(const String &name, const char *json); + bool moodlight(const char *json); }; extern DisplayManager_ &DisplayManager; diff --git a/src/Globals.cpp b/src/Globals.cpp index 6e99ae0c..6dddfe09 100644 --- a/src/Globals.cpp +++ b/src/Globals.cpp @@ -216,7 +216,7 @@ IPAddress gateway; IPAddress subnet; IPAddress primaryDNS; IPAddress secondaryDNS; -const char *VERSION = "0.65"; +const char *VERSION = "0.66"; String MQTT_HOST = ""; uint16_t MQTT_PORT = 1883; diff --git a/src/PeripheryManager.cpp b/src/PeripheryManager.cpp index ba3651ea..80669896 100644 --- a/src/PeripheryManager.cpp +++ b/src/PeripheryManager.cpp @@ -222,34 +222,43 @@ void PeripheryManager_::setVolume(uint8_t vol) } #endif -void PeripheryManager_::parseSound(const char *json) +bool PeripheryManager_::parseSound(const char *json) { StaticJsonDocument<128> doc; DeserializationError error = deserializeJson(doc, json); if (error) { - playFromFile(String(json)); - return; + return playFromFile(String(json)); } if (doc.containsKey("sound")) { - playFromFile(doc["sound"].as()); + return playFromFile(doc["sound"].as()); } } -void PeripheryManager_::playFromFile(String file) +bool PeripheryManager_::playFromFile(String file) { if (!SOUND_ACTIVE) - return; + return true; #ifdef ULANZI DEBUG_PRINTLN(F("Playing RTTTL sound file")); - Melody melody = MelodyFactory.loadRtttlFile("/MELODIES/" + String(file) + ".txt"); - player.playAsync(melody); + if (LittleFS.exists("/MELODIES/" + String(file) + ".txt")) + { + Melody melody = MelodyFactory.loadRtttlFile("/MELODIES/" + String(file) + ".txt"); + player.playAsync(melody); + return true; + } + else + { + return false; + } + #else dfmp3.playMp3FolderTrack(file.toInt()); #endif + return true; } bool PeripheryManager_::isPlaying() diff --git a/src/PeripheryManager.h b/src/PeripheryManager.h index 351ac2a8..4524b315 100644 --- a/src/PeripheryManager.h +++ b/src/PeripheryManager.h @@ -35,8 +35,8 @@ class PeripheryManager_ void setup(); void tick(); void playBootSound(); - void playFromFile(String file); - void parseSound(const char *json); + bool playFromFile(String file); + bool parseSound(const char *json); bool isPlaying(); void stopSound(); #ifndef ULANZI diff --git a/src/ServerManager.cpp b/src/ServerManager.cpp index ae2255e1..5994184a 100644 --- a/src/ServerManager.cpp +++ b/src/ServerManager.cpp @@ -28,7 +28,7 @@ ServerManager_ &ServerManager = ServerManager.getInstance(); void versionHandler() { WebServerClass *webRequest = mws.getRequest(); - webRequest->send(200, "text/plain", VERSION); + webRequest->send(200, F("text/plain"), VERSION); } void saveHandler() @@ -38,6 +38,98 @@ void saveHandler() webRequest->send(200); } +void addHandler() +{ + mws.addHandler("/api/power", HTTP_POST, []() + { DisplayManager.powerStateParse(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,F("text/plain"),F("OK")); }); + mws.addHandler("/api/reboot", HTTP_POST, []() + { mws.webserver->send(200,F("text/plain"),F("OK")); delay(200); ESP.restart(); }); + mws.addHandler("/api/sound", HTTP_POST, []() + { if (PeripheryManager.parseSound(mws.webserver->arg("plain").c_str())){ + mws.webserver->send(200,F("text/plain"),F("OK")); + }else{ + mws.webserver->send(404,F("text/plain"),F("FileNotFound")); + }; }); + mws.addHandler("/api/moodlight", HTTP_POST, []() + { + if (DisplayManager.moodlight(mws.webserver->arg("plain").c_str())) + { + mws.webserver->send(200, F(F("text/plain")), F("OK")); + } + else + { + mws.webserver->send(500, F("text/plain"), F("ErrorParsingJson")); + } }); + mws.addHandler("/api/notify", HTTP_POST, []() + { + if (DisplayManager.generateNotification(mws.webserver->arg("plain").c_str())) + { + mws.webserver->send(200, F("text/plain"), F("OK")); + }else{ + mws.webserver->send(500, F("text/plain"), F("ErrorParsingJson")); + } }); + mws.addHandler("/api/nextapp", HTTP_POST, []() + { DisplayManager.nextApp(); mws.webserver->send(200,F("text/plain"),F("OK")); }); + mws.addHandler("/api/previousapp", HTTP_POST, []() + { DisplayManager.previousApp(); mws.webserver->send(200,F("text/plain"),F("OK")); }); + mws.addHandler("/api/timer", HTTP_POST, []() + { DisplayManager.gererateTimer(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,F("text/plain"),F("OK")); }); + mws.addHandler("/api/notify/dismiss", HTTP_POST, []() + { 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/apps", HTTP_GET, []() + { mws.webserver->send_P(200, "application/json", DisplayManager.getAppsWithIcon().c_str()); }); + mws.addHandler("/api/settings", HTTP_POST, []() + { DisplayManager.setNewSettings(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,F("text/plain"),F("OK")); }); + mws.addHandler("/api/reorder", HTTP_POST, []() + { DisplayManager.reorderApps(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,F("text/plain"),F("OK")); }); + mws.addHandler("/api/settings", HTTP_GET, []() + { mws.webserver->send_P(200, "application/json", DisplayManager.getSettings().c_str()); }); + mws.addHandler("/api/custom", HTTP_POST, []() + { + if (DisplayManager.parseCustomPage(mws.webserver->arg("name"),mws.webserver->arg("plain").c_str())){ + mws.webserver->send(200,F("text/plain"),F("OK")); + }else{ + mws.webserver->send(500,F("text/plain"),F("ErrorParsingJson")); + } }); + mws.addHandler("/api/stats", HTTP_GET, []() + { mws.webserver->send_P(200, "application/json", DisplayManager.getStats().c_str()); }); + mws.addHandler("/api/screen", HTTP_GET, []() + { mws.webserver->send_P(200, "application/json", DisplayManager.ledsAsJson().c_str()); }); + mws.addHandler("/api/indicator1", HTTP_POST, []() + { + if (DisplayManager.indicatorParser(1,mws.webserver->arg("plain").c_str())){ + mws.webserver->send(200,F("text/plain"),F("OK")); + }else{ + mws.webserver->send(500,F("text/plain"),F("ErrorParsingJson")); + } }); + mws.addHandler("/api/indicator2", HTTP_POST, []() + { + if (DisplayManager.indicatorParser(2,mws.webserver->arg("plain").c_str())){ + mws.webserver->send(200,F("text/plain"),F("OK")); + }else{ + mws.webserver->send(500,F("text/plain"),F("ErrorParsingJson")); + } }); + mws.addHandler("/api/indicator3", HTTP_POST, []() + { + if (DisplayManager.indicatorParser(3,mws.webserver->arg("plain").c_str())){ + mws.webserver->send(200,F("text/plain"),F("OK")); + }else{ + mws.webserver->send(500,F("text/plain"),F("ErrorParsingJson")); + } }); + mws.addHandler("/api/doupdate", HTTP_POST, []() + { + if (UpdateManager.checkUpdate(true)){ + mws.webserver->send(200,F("text/plain"),F("OK")); + UpdateManager.updateFirmware(); + }else{ + mws.webserver->send(404,F("text/plain"),"NoUpdateFound"); + } }); +} + void ServerManager_::setup() { if (!local_IP.fromString(NET_IP) || !gateway.fromString(NET_GW) || !subnet.fromString(NET_SN) || !primaryDNS.fromString(NET_PDNS) || !secondaryDNS.fromString(NET_SDNS)) @@ -74,51 +166,8 @@ void ServerManager_::setup() mws.addCSS(custom_css); mws.addJavascript(custom_script); mws.addHandler("/save", HTTP_POST, saveHandler); - mws.addHandler("/api/sound", HTTP_POST, []() - { PeripheryManager.parseSound(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/moodlight", HTTP_POST, []() - { DisplayManager.moodlight(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/notify", HTTP_POST, []() - { DisplayManager.generateNotification(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/nextapp", HTTP_POST, []() - { DisplayManager.nextApp(); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/previousapp", HTTP_POST, []() - { DisplayManager.previousApp(); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/timer", HTTP_POST, []() - { DisplayManager.gererateTimer(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/notify/dismiss", HTTP_POST, []() - { DisplayManager.dismissNotify(); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/apps", HTTP_POST, []() - { DisplayManager.updateAppVector(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/switch", HTTP_POST, []() - { DisplayManager.switchToApp(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/apps", HTTP_GET, []() - { mws.webserver->send_P(200, "application/json", DisplayManager.getAppsWithIcon().c_str()); }); - mws.addHandler("/api/settings", HTTP_POST, []() - { DisplayManager.setNewSettings(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/reorder", HTTP_POST, []() - { DisplayManager.reorderApps(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/settings", HTTP_GET, []() - { mws.webserver->send_P(200, "application/json", DisplayManager.getSettings().c_str()); }); - mws.addHandler("/api/custom", HTTP_POST, []() - { DisplayManager.generateCustomPage(mws.webserver->arg("name"),mws.webserver->arg("plain").c_str()); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/stats", HTTP_GET, []() - { mws.webserver->send_P(200, "application/json", DisplayManager.getStats().c_str()); }); - mws.addHandler("/api/screen", HTTP_GET, []() - { mws.webserver->send_P(200, "application/json", DisplayManager.ledsAsJson().c_str()); }); - mws.addHandler("/api/indicator1", HTTP_POST, []() - { DisplayManager.indicatorParser(1,mws.webserver->arg("plain").c_str()); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/indicator2", HTTP_POST, []() - { DisplayManager.indicatorParser(2,mws.webserver->arg("plain").c_str()); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/doupdate", HTTP_POST, []() - { mws.webserver->send(200,"text/plain","OK"); if (UpdateManager.checkUpdate(true)) - { - UpdateManager.updateFirmware(); - } }); - mws.addHandler("/api/power", HTTP_POST, []() - { DisplayManager.powerStateParse(mws.webserver->arg("plain").c_str()); mws.webserver->send(200,"text/plain","OK"); }); - mws.addHandler("/api/reboot", HTTP_POST, []() - { mws.webserver->send(200,"text/plain","OK"); delay(200); ESP.restart(); }); + addHandler(); + DEBUG_PRINTLN(F("Webserver loaded")); } mws.addHandler("/version", HTTP_GET, versionHandler);