diff --git a/docs/README.md b/docs/README.md index 7bbce85..ddfa03b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,11 +7,11 @@ https://paypal.me/blueforcer Awtrix Light is a custom firmware for the [Ulanzi Smart Pixel clock](https://www.ulanzi.com/products/ulanzi-pixel-smart-clock-2882). That offers a simple and user-friendly interface, making it the perfect solution for non-techies who want to enjoy the benefits of the popular awtrix system. -It is ready to use straight out of the box, with time, date, temperature, and humidity pages pre-installed. You don't need to do anything other than turning it on to start using these features. +It is ready to use straight out of the box, with time, date, temperature, and humidity apps pre-installed. You don't need to do anything other than turning it on to start using these features. During the development of Awtrix Light, usability and simplicity are my top priorities. My aim is to make it easy for non-tech-savvy users to benefit from Awtrix Light without any headaches or hours of scripting. Awtrix Light is ready to use straight out of the box, without the need for a single line of code or commands. -However, for those with more advanced skills, the customization options available with custom pages allow you to take Awtrix Light to its full potential. +However, for those with more advanced skills, the customization options available with custom apps allow you to take Awtrix Light to its full potential. With Awtrix Light, you can effortlessly bring your ideas to life and enjoy a hassle-free experience. Join the thousands of satisfied awtrix users who have already chosen Awtrix 2 and Awtrix Light and experience the difference today! https://discord.gg/cyBCpdx \ No newline at end of file diff --git a/docs/custom.md b/docs/custom.md index 1d2550d..f4c7f8f 100644 --- a/docs/custom.md +++ b/docs/custom.md @@ -1,6 +1,6 @@ -# Custom Pages & Notifications +# Custom Apps & Notifications -With AWTRIX Light, you can create custom pages or notifications to display your own text and icons. +With AWTRIX Light, you can create custom apps or notifications to display your own text and icons. Simply send a JSON object to the topic "awtrixlight/custom/[page]" where [page] is a the name of your page (without spaces). ## JSON Properties @@ -19,6 +19,7 @@ The JSON object has the following properties: | `hold` | boolean | Set it to true, to hold your notification on top until you press the middle button or dismiss it via HomeAssistant. This key only belongs to notification. | false | | `sound` | string | The filename of your RTTTL ringtone file (without extension). | | | `pushIcon` | number | 0 = Icon doesn't move. 1 = Icon moves with text and will not appear again. 2 = Icon moves with text but appears again when the text starts to scroll again. | 0 | +| `bar` | array of integers | draws a bargraph. Without icon maximum 16 values, with icon 11 values | | All keys are optional, so you can send just the properties you want to use. diff --git a/docs/mqtt.md b/docs/mqtt.md index 70d1665..99b7ca8 100644 --- a/docs/mqtt.md +++ b/docs/mqtt.md @@ -29,7 +29,7 @@ Built-in app names are: - `hum` - `bat` -For custom pages, use the name you set in the topic. For example, if `[PREFIX]/custom/test` is your topic, then `test` is the name. +For custom apps, use the name you set in the topic. For example, if `[PREFIX]/custom/test` is your topic, then `test` is the name. ## Change Settings Change various settings related to the app display. diff --git a/lib/MatrixUI/MatrixDisplayUi.cpp b/lib/MatrixUI/MatrixDisplayUi.cpp index ae68344..1cd9739 100644 --- a/lib/MatrixUI/MatrixDisplayUi.cpp +++ b/lib/MatrixUI/MatrixDisplayUi.cpp @@ -48,9 +48,9 @@ void MatrixDisplayUi::setTargetFPS(uint8_t fps) float oldInterval = this->updateInterval; this->updateInterval = ((float)1.0 / (float)fps) * 1000; - // Calculate new ticksPerFrame + // Calculate new ticksPerApp float changeRatio = oldInterval / (float)this->updateInterval; - this->ticksPerFrame *= changeRatio; + this->ticksPerApp *= changeRatio; this->ticksPerTransition *= changeRatio; } @@ -66,33 +66,34 @@ void MatrixDisplayUi::disablesetAutoTransition() } void MatrixDisplayUi::setsetAutoTransitionForwards() { - this->state.frameTransitionDirection = 1; + this->state.appTransitionDirection = 1; this->lastTransitionDirection = 1; } void MatrixDisplayUi::setsetAutoTransitionBackwards() { - this->state.frameTransitionDirection = -1; + this->state.appTransitionDirection = -1; this->lastTransitionDirection = -1; } void MatrixDisplayUi::setTimePerApp(uint16_t time) { - this->ticksPerFrame = (int)((float)time / (float)updateInterval); + this->ticksPerApp = (int)((float)time / (float)updateInterval); } void MatrixDisplayUi::setTimePerTransition(uint16_t time) { this->ticksPerTransition = (int)((float)time / (float)updateInterval); } -// -/----- Frame settings -----\- +// -/----- App settings -----\- void MatrixDisplayUi::setAppAnimation(AnimationDirection dir) { - this->frameAnimationDirection = dir; + this->appAnimationDirection = dir; } void MatrixDisplayUi::setApps(const std::vector> &appPairs) { delete[] AppFunctions; AppCount = appPairs.size(); + Serial.println(AppCount); AppFunctions = new AppCallback[AppCount]; for (size_t i = 0; i < AppCount; ++i) @@ -113,51 +114,51 @@ void MatrixDisplayUi::setOverlays(OverlayCallback *overlayFunctions, uint8_t ove // -/----- Manuel control -----\- void MatrixDisplayUi::nextApp() { - if (this->state.frameState != IN_TRANSITION) + if (this->state.appState != IN_TRANSITION) { this->state.manuelControll = true; - this->state.frameState = IN_TRANSITION; + this->state.appState = IN_TRANSITION; this->state.ticksSinceLastStateSwitch = 0; - this->lastTransitionDirection = this->state.frameTransitionDirection; - this->state.frameTransitionDirection = 1; + this->lastTransitionDirection = this->state.appTransitionDirection; + this->state.appTransitionDirection = 1; } } void MatrixDisplayUi::previousApp() { - if (this->state.frameState != IN_TRANSITION) + if (this->state.appState != IN_TRANSITION) { this->state.manuelControll = true; - this->state.frameState = IN_TRANSITION; + this->state.appState = IN_TRANSITION; this->state.ticksSinceLastStateSwitch = 0; - this->lastTransitionDirection = this->state.frameTransitionDirection; - this->state.frameTransitionDirection = -1; + this->lastTransitionDirection = this->state.appTransitionDirection; + this->state.appTransitionDirection = -1; } } -void MatrixDisplayUi::switchToApp(uint8_t frame) +void MatrixDisplayUi::switchToApp(uint8_t app) { - if (frame >= this->AppCount) + if (app >= this->AppCount) return; this->state.ticksSinceLastStateSwitch = 0; - if (frame == this->state.currentFrame) + if (app == this->state.currentApp) return; - this->state.frameState = FIXED; - this->state.currentFrame = frame; + this->state.appState = FIXED; + this->state.currentApp = app; } -void MatrixDisplayUi::transitionToApp(uint8_t frame) +void MatrixDisplayUi::transitionToApp(uint8_t app) { - if (frame >= this->AppCount) + if (app >= this->AppCount) return; this->state.ticksSinceLastStateSwitch = 0; - if (frame == this->state.currentFrame) + if (app == this->state.currentApp) return; - this->nextAppNumber = frame; - this->lastTransitionDirection = this->state.frameTransitionDirection; + this->nextAppNumber = app; + this->lastTransitionDirection = this->state.appTransitionDirection; this->state.manuelControll = true; - this->state.frameState = IN_TRANSITION; + this->state.appState = IN_TRANSITION; - this->state.frameTransitionDirection = frame < this->state.currentFrame ? -1 : 1; + this->state.appTransitionDirection = app < this->state.currentApp ? -1 : 1; } // -/----- State information -----\- @@ -168,18 +169,18 @@ MatrixDisplayUiState *MatrixDisplayUi::getUiState() int8_t MatrixDisplayUi::update() { - long frameStart = millis(); - int8_t timeBudget = this->updateInterval - (frameStart - this->state.lastUpdate); + long appStart = millis(); + int8_t timeBudget = this->updateInterval - (appStart - this->state.lastUpdate); if (timeBudget <= 0) { - // Implement frame skipping to ensure time budget is keept + // Implement app skipping to ensure time budget is keept if (this->setAutoTransition && this->state.lastUpdate != 0) this->state.ticksSinceLastStateSwitch += ceil(-timeBudget / this->updateInterval); - this->state.lastUpdate = frameStart; + this->state.lastUpdate = appStart; this->tick(); } - return this->updateInterval - (millis() - frameStart); + return this->updateInterval - (millis() - appStart); } void MatrixDisplayUi::tick() @@ -188,13 +189,13 @@ void MatrixDisplayUi::tick() if (this->AppCount > 0) { - switch (this->state.frameState) + switch (this->state.appState) { case IN_TRANSITION: if (this->state.ticksSinceLastStateSwitch >= this->ticksPerTransition) { - this->state.frameState = FIXED; - this->state.currentFrame = getnextAppNumber(); + this->state.appState = FIXED; + this->state.currentApp = getnextAppNumber(); this->state.ticksSinceLastStateSwitch = 0; this->nextAppNumber = -1; } @@ -203,14 +204,14 @@ void MatrixDisplayUi::tick() // Revert manuelControll if (this->state.manuelControll) { - this->state.frameTransitionDirection = this->lastTransitionDirection; + this->state.appTransitionDirection = this->lastTransitionDirection; this->state.manuelControll = false; } - if (this->state.ticksSinceLastStateSwitch >= this->ticksPerFrame) + if (this->state.ticksSinceLastStateSwitch >= this->ticksPerApp) { if (this->setAutoTransition) { - this->state.frameState = IN_TRANSITION; + this->state.appState = IN_TRANSITION; } this->state.ticksSinceLastStateSwitch = 0; } @@ -227,13 +228,13 @@ void MatrixDisplayUi::tick() void MatrixDisplayUi::drawApp() { - switch (this->state.frameState) + switch (this->state.appState) { case IN_TRANSITION: { float progress = (float)this->state.ticksSinceLastStateSwitch / (float)this->ticksPerTransition; int16_t x, y, x1, y1; - switch (this->frameAnimationDirection) + switch (this->appAnimationDirection) { case SLIDE_LEFT: x = -32 * progress; @@ -261,20 +262,20 @@ void MatrixDisplayUi::drawApp() break; } // Invert animation if direction is reversed. - int8_t dir = this->state.frameTransitionDirection >= 0 ? 1 : -1; + int8_t dir = this->state.appTransitionDirection >= 0 ? 1 : -1; x *= dir; y *= dir; x1 *= dir; y1 *= dir; - bool FirstFrame = progress < 0.2; - bool LastFrame = progress > 0.8; + bool FirstApp = progress < 0.2; + bool LastApp = progress > 0.8; this->matrix->drawRect(x, y, x1, y1, matrix->Color(0, 0, 0)); - (this->AppFunctions[this->state.currentFrame])(this->matrix, &this->state, x, y, FirstFrame, LastFrame); - (this->AppFunctions[this->getnextAppNumber()])(this->matrix, &this->state, x1, y1, FirstFrame, LastFrame); + (this->AppFunctions[this->state.currentApp])(this->matrix, &this->state, x, y, FirstApp, LastApp); + (this->AppFunctions[this->getnextAppNumber()])(this->matrix, &this->state, x1, y1, FirstApp, LastApp); break; } case FIXED: - (this->AppFunctions[this->state.currentFrame])(this->matrix, &this->state, 0, 0, false, false); + (this->AppFunctions[this->state.currentApp])(this->matrix, &this->state, 0, 0, false, false); break; } } @@ -283,8 +284,8 @@ void MatrixDisplayUi::resetState() { this->state.lastUpdate = 0; this->state.ticksSinceLastStateSwitch = 0; - this->state.frameState = FIXED; - this->state.currentFrame = 0; + this->state.appState = FIXED; + this->state.currentApp = 0; } void MatrixDisplayUi::drawOverlays() @@ -299,5 +300,5 @@ uint8_t MatrixDisplayUi::getnextAppNumber() { if (this->nextAppNumber != -1) return this->nextAppNumber; - return (this->state.currentFrame + this->AppCount + this->state.frameTransitionDirection) % this->AppCount; + return (this->state.currentApp + this->AppCount + this->state.appTransitionDirection) % this->AppCount; } diff --git a/lib/MatrixUI/MatrixDisplayUi.h b/lib/MatrixUI/MatrixDisplayUi.h index 552cdd6..bb8a5fc 100644 --- a/lib/MatrixUI/MatrixDisplayUi.h +++ b/lib/MatrixUI/MatrixDisplayUi.h @@ -46,7 +46,7 @@ enum AnimationDirection SLIDE_RIGHT }; -enum FrameState +enum AppState { IN_TRANSITION, FIXED @@ -58,11 +58,11 @@ struct MatrixDisplayUiState u_int64_t lastUpdate = 0; uint16_t ticksSinceLastStateSwitch = 0; - FrameState frameState = FIXED; - uint8_t currentFrame = 0; + AppState appState = FIXED; + uint8_t currentApp = 0; // Normal = 1, Inverse = -1; - int8_t frameTransitionDirection = 1; + int8_t appTransitionDirection = 1; bool manuelControll = false; @@ -70,7 +70,7 @@ struct MatrixDisplayUiState void *userData = NULL; }; -typedef void (*AppCallback)(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame); +typedef void (*AppCallback)(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp); typedef void (*OverlayCallback)(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state); class MatrixDisplayUi @@ -78,21 +78,19 @@ class MatrixDisplayUi private: FastLED_NeoMatrix *matrix; - // Values for the Frames - AnimationDirection frameAnimationDirection = SLIDE_RIGHT; + // Values for the Apps + AnimationDirection appAnimationDirection = SLIDE_RIGHT; int8_t lastTransitionDirection = 1; - uint16_t ticksPerFrame = 151; // ~ 5000ms at 30 FPS + uint16_t ticksPerApp = 151; // ~ 5000ms at 30 FPS uint16_t ticksPerTransition = 15; // ~ 500ms at 30 FPS bool setAutoTransition = true; AppCallback *AppFunctions; - uint8_t AppCount = 0; - - // Internally used to transition to a specific frame + // Internally used to transition to a specific app int8_t nextAppNumber = -1; // Values for Overlays @@ -115,6 +113,7 @@ private: public: MatrixDisplayUi(FastLED_NeoMatrix *matrix); + uint8_t AppCount = 0; /** * Initialise the display */ @@ -127,12 +126,12 @@ public: // Automatic Controll /** - * Enable automatic transition to next frame after the some time can be configured with `setTimePerApp` and `setTimePerTransition`. + * Enable automatic transition to next app after the some time can be configured with `setTimePerApp` and `setTimePerTransition`. */ void enablesetAutoTransition(); /** - * Disable automatic transition to next frame. + * Disable automatic transition to next app. */ void disablesetAutoTransition(); @@ -143,7 +142,7 @@ public: void setsetAutoTransitionBackwards(); /** - * Set the approx. time a frame is displayed + * Set the approx. time a app is displayed */ void setTimePerApp(uint16_t time); @@ -154,22 +153,22 @@ public: // Customize indicator position and style - // Frame settings + // App settings /** - * Configure what animation is used to transition from one frame to another + * Configure what animation is used to transition from one app to another */ void setAppAnimation(AnimationDirection dir); /** - * Add frame drawing functions + * Add app drawing functions */ void setApps(const std::vector> &appPairs); // Overlay /** - * Add overlays drawing functions that are draw independent of the Frames + * Add overlays drawing functions that are draw independent of the Apps */ void setOverlays(OverlayCallback *overlayFunctions, uint8_t overlayCount); @@ -178,15 +177,15 @@ public: void previousApp(); /** - * Switch without transition to frame `frame`. + * Switch without transition to app `app`. */ - void switchToApp(uint8_t frame); + void switchToApp(uint8_t app); /** - * Transition to frame `frame`, when the `frame` number is bigger than the current - * frame the forward animation will be used, otherwise the backwards animation is used. + * Transition to app `app`, when the `app` number is bigger than the current + * app the forward animation will be used, otherwise the backwards animation is used. */ - void transitionToApp(uint8_t frame); + void transitionToApp(uint8_t app); // State Info MatrixDisplayUiState *getUiState(); diff --git a/platformio.ini b/platformio.ini index 47cd25b..e627776 100644 --- a/platformio.ini +++ b/platformio.ini @@ -8,12 +8,28 @@ ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html -[env:esp32dev] +[env:ulanzi] platform = https://github.com/platformio/platform-espressif32.git board = esp32dev board_build.partitions = awtrix_partition.csv upload_speed = 921600 framework = arduino +build_flags = -DULANZI +lib_deps = + adafruit/Adafruit SHT31 Library@^2.2.0 + bblanchon/ArduinoJson@^6.20.0 + evert-arias/EasyButton@^2.0.1 + fastled/FastLED@^3.5.0 + marcmerlin/FastLED NeoMatrix@^1.2 + knolleary/PubSubClient@^2.8 + +[env:awtrix_upgrade] +platform = https://github.com/platformio/platform-espressif32.git +board = wemos_d1_mini32 +board_build.partitions = awtrix_partition.csv +upload_speed = 921600 +framework = arduino +build_flags = -DAWTRIX_UPGRADE lib_deps = adafruit/Adafruit SHT31 Library@^2.2.0 bblanchon/ArduinoJson@^6.20.0 diff --git a/src/Frames.h b/src/Apps.h similarity index 54% rename from src/Frames.h rename to src/Apps.h index 4259961..24fa508 100644 --- a/src/Frames.h +++ b/src/Apps.h @@ -1,737 +1,754 @@ -#ifndef FRAMES_H -#define FRAMES_H - -#include -#include -#include "icons.h" -#include -#include "MatrixDisplayUi.h" -#include "Globals.h" -#include "Functions.h" -#include "MenuManager.h" -#include "PeripheryManager.h" -#include "DisplayManager.h" -#include "LittleFS.h" -#include -#include -#include - -Ticker downloader; - -tm timeInfo; -uint16_t nativeAppsCount; -uint16_t customPagesCount; - -int WEATHER_CODE; -String WEATHER_TEMP; -String WEATHER_HUM; - -struct CustomFrame -{ - int16_t scrollposition = 0; - int16_t scrollDelay = 0; - String text; - uint16_t color; - File icon; - bool isGif; - bool rainbow; - bool soundPlayed; - String sound; - int16_t repeat = 0; - int16_t currentRepeat = 0; - String name; - byte pushIcon = 0; - int16_t iconPosition = 0; - bool iconWasPushed = false; -}; - -String currentCustomFrame; -std::map customFrames; - -struct Notification -{ - int16_t scrollposition = 34; - int16_t scrollDelay = 0; - String text; - uint16_t color; - File icon; - bool rainbow; - bool isGif; - bool flag = false; - unsigned long startime = 0; - unsigned long duration = 0; - int16_t repeat = -1; - bool hold = false; - byte pushIcon = 0; - int16_t iconPosition = 0; - bool iconWasPushed = false; -}; - -Notification notify; - -std::vector> Apps; - -CustomFrame *getCustomFrameById(String name) -{ - return customFrames.count(name) ? &customFrames[name] : nullptr; -} - -String getFrameNameByFunction(AppCallback frameFunction) -{ - for (const auto &appPair : Apps) - { - if (appPair.second == frameFunction) - { - return appPair.first; - } - } - - return ""; // Gibt einen leeren String zurück, wenn die Frame-Funktion nicht gefunden wurde -} - -int findAppIndexByName(const String &name) -{ - auto it = std::find_if(Apps.begin(), Apps.end(), [&name](const std::pair &appPair) - { return appPair.first == name; }); - if (it != Apps.end()) - { - return std::distance(Apps.begin(), it); - } - return -1; -} - -void TimeFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - if (notify.flag) - return; - CURRENT_APP = "Time"; - DisplayManager.getInstance().resetTextColor(); - time_t now = time(nullptr); - struct tm *timeInfo; - timeInfo = localtime(&now); - const char *timeformat = TIME_FORMAT.c_str(); - char t[20]; - char t2[20]; - if (timeformat[2] == ' ') - { - strcpy(t2, timeformat); - if (now % 2) - { - t2[2] = ' '; - } - else - { - t2[2] = ':'; - } - strftime(t, sizeof(t), t2, localtime(&now)); - } - else - { - strftime(t, sizeof(t), timeformat, localtime(&now)); - } - - DisplayManager.printText(0 + x, 6 + y, t, true, false); - - if (!SHOW_WEEKDAY) - return; - int dayOffset = START_ON_MONDAY ? 0 : 1; - for (int i = 0; i <= 6; i++) - { - if (i == (timeInfo->tm_wday + 6 + dayOffset) % 7) - { - matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(200, 200, 200)); - } - else - { - matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(100, 100, 100)); - } - } -} - -void DateFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - if (notify.flag) - return; - CURRENT_APP = "Date"; - DisplayManager.getInstance().resetTextColor(); - time_t now = time(nullptr); - struct tm *timeInfo; - timeInfo = localtime(&now); - char d[20]; - strftime(d, sizeof(d), DATE_FORMAT.c_str(), localtime(&now)); - DisplayManager.printText(0 + x, 6 + y, d, true, true); - if (!SHOW_WEEKDAY) - return; - int dayOffset = START_ON_MONDAY ? 0 : 1; - for (int i = 0; i <= 6; i++) - { - if (i == (timeInfo->tm_wday + 6 + dayOffset) % 7) - { - matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(200, 200, 200)); - } - else - { - matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(100, 100, 100)); - } - } -} - -void TempFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - if (notify.flag) - return; - CURRENT_APP = "Temperature"; - DisplayManager.getInstance().resetTextColor(); - matrix->drawRGBBitmap(x, y, get_icon(234), 8, 8); - matrix->setCursor(12 + x, 6 + y); - if (IS_CELSIUS) - { - matrix->print((int)CURRENT_TEMP); - matrix->print(utf8ascii("°C")); - } - else - { - int tempF = (CURRENT_TEMP * 9 / 5) + 32; - matrix->print(tempF); - matrix->print(utf8ascii("°F")); - } -} - -void HumFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - if (notify.flag) - return; - CURRENT_APP = "Humidity"; - DisplayManager.getInstance().resetTextColor(); - matrix->drawRGBBitmap(x, y + 1, get_icon(2075), 8, 8); - matrix->setCursor(14 + x, 6 + y); - int humidity = CURRENT_HUM; // Temperatur ohne Nachkommastellen - matrix->print(humidity); // Ausgabe der Temperatur - matrix->print("%"); -} - -void BatFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - if (notify.flag) - return; - CURRENT_APP = "Battery"; - DisplayManager.getInstance().resetTextColor(); - matrix->drawRGBBitmap(x, y, get_icon(1486), 8, 8); - matrix->setCursor(14 + x, 6 + y); - matrix->print(BATTERY_PERCENT); // Ausgabe des Ladezustands - matrix->print("%"); -} - -void MenuFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state) -{ - if (!MenuManager.inMenu) - return; - matrix->fillScreen(0); - DisplayManager.printText(0, 6, utf8ascii(MenuManager.menutext()).c_str(), true, true); -} - -void AlarmFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state) -{ - if (ALARM_ACTIVE) - { - matrix->fillScreen(matrix->Color(255, 0, 0)); - CURRENT_APP = "Alarm"; - uint16_t textWidth = getTextWidth("ALARM", false); - int16_t textX = ((32 - textWidth) / 2); - matrix->setTextColor(0); - matrix->setCursor(textX, 6); - matrix->print("ALARM"); - if (ALARM_SOUND != "") - { - if (!PeripheryManager.isPlaying()) - PeripheryManager.playFromFile("/MELODIES/" + ALARM_SOUND + ".txt"); - } - } -} - -void TimerFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state) -{ - if (TIMER_ACTIVE) - { - matrix->fillScreen(matrix->Color(0, 255, 0)); - CURRENT_APP = "Timer"; - String menuText = "TIMER"; - uint16_t textWidth = getTextWidth(menuText.c_str(), false); - int16_t textX = ((32 - textWidth) / 2); - matrix->setTextColor(0); - matrix->setCursor(textX, 6); - matrix->print(menuText); - if (TIMER_SOUND != "") - { - if (!PeripheryManager.isPlaying()) - PeripheryManager.playFromFile("/MELODIES/" + TIMER_SOUND + ".txt"); - } - } -} - -void ShowCustomFrame(String name, FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - // Abort if notify.flag is set - if (notify.flag) - { - return; - } - - // Get custom frame by ID - CustomFrame *cf = getCustomFrameById(name); - - // Abort if custom frame not found - if (cf == nullptr) - { - return; - } - - // reset custom frame properties if last frame - if (lastFrame) - { - cf->iconWasPushed = false; - cf->scrollposition = 9; - cf->iconPosition = 0; - cf->scrollDelay = 0; - } - - CURRENT_APP = cf->name; - currentCustomFrame = name; - - bool hasIcon = cf->icon; - uint16_t availableWidth = (hasIcon) ? 24 : 32; - - bool noScrolling = getTextWidth(cf->text.c_str(), false) <= availableWidth; - - if ((cf->repeat > 0) && (getTextWidth(cf->text.c_str(), false) > availableWidth) && (state->frameState == FIXED)) - { - DisplayManager.setAutoTransition(false); - } - else - { - DisplayManager.setAutoTransition(true); - } - - if (getTextWidth(cf->text.c_str(), false) > availableWidth && !(state->frameState == IN_TRANSITION)) - { - if (cf->scrollposition <= -getTextWidth(cf->text.c_str(), false)) - { - cf->scrollDelay = 0; - cf->scrollposition = 9; - if (cf->iconWasPushed && cf->pushIcon == 2) - { - cf->iconWasPushed = false; - } - if ((cf->currentRepeat + 1 >= cf->repeat) && (cf->repeat > 0)) - { - DisplayManager.setAutoTransition(true); - cf->currentRepeat = 0; - DisplayManager.nextApp(); - return; - } - else if (cf->repeat > 0) - { - ++cf->currentRepeat; - } - } - } - - if (!noScrolling) - { - if ((cf->scrollDelay > MATRIX_FPS * 1.2)) - { - --cf->scrollposition; - } - else - { - ++cf->scrollDelay; - if (hasIcon) - { - if (cf->iconWasPushed && cf->pushIcon == 1) - { - cf->scrollposition = 0; - } - else - { - cf->scrollposition = 9; - } - } - else - { - cf->scrollposition = 0; - } - } - } - int16_t textX = (hasIcon) ? ((24 - getTextWidth(cf->text.c_str(), false)) / 2) + 9 : ((32 - getTextWidth(cf->text.c_str(), false)) / 2); - matrix->setTextColor(cf->color); - if (noScrolling) - { - cf->repeat = -1; // Disable repeat if text is too short for scrolling - // Display text with rainbow effect if enabled - if (cf->rainbow) - { - DisplayManager.getInstance().HSVtext(x + textX, 6 + y, cf->text.c_str(), false); - } - else - { - // Display text - DisplayManager.printText(x + textX, y + 6, cf->text.c_str(), false, false); - } - } - else - { - // Display scrolling text with rainbow effect if enabled - if (cf->rainbow) - { - DisplayManager.getInstance().HSVtext(x + cf->scrollposition, 6 + y, cf->text.c_str(), false); - } - else - { - DisplayManager.printText(x + cf->scrollposition, 6 + y, cf->text.c_str(), false, false); - } - } // Display icon if present and not pushed - if (hasIcon) - { - // Push icon if enabled and text is scrolling - if (cf->pushIcon > 0 && !noScrolling) - { - if (cf->iconPosition < 0 && cf->iconWasPushed == false && cf->scrollposition > 8) - { - ++cf->iconPosition; - } - - if (cf->scrollposition < 8 && !cf->iconWasPushed) - { - cf->iconPosition = cf->scrollposition - 8; - - if (cf->iconPosition <= -9) - { - cf->iconWasPushed = true; - } - } - } - - // Display animated GIF if enabled and frame is fixed, since we have only one gifplayer instance, it looks weird when 2 apps want to draw a different gif - if (cf->isGif) - { - if (state->frameState == FIXED) - { - DisplayManager.drawGIF(x + cf->iconPosition, y, cf->icon); - } - } - else - { - // Display JPEG image - DisplayManager.drawJPG(x + cf->iconPosition, y, cf->icon); - } - - // Draw vertical line if text is scrolling - if (!noScrolling) - { - // matrix->drawLine(8 + x + cf->iconPosition, 0 + y, 8 + x + cf->iconPosition, 7 + y, 0); - } - } - // Reset text color - DisplayManager.getInstance().resetTextColor(); -} - -void NotifyFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state) -{ - // Check if notification flag is set - if (!notify.flag) - { - return; // Exit function if flag is not set - } - - // Set current app name - CURRENT_APP = "Notification"; - // Get current time - unsigned long currentTime = millis(); - - // Check if notification duration has expired or if repeat count is 0 and hold is not enabled - if ((((currentTime - notify.startime >= notify.duration) && notify.repeat == -1) || notify.repeat == 0) && !notify.hold) - { - // Reset notification flags and exit function - notify.flag = false; - notify.duration = 0; - notify.startime = 0; - notify.scrollposition = 34; - notify.iconWasPushed = false; - notify.iconPosition = 0; - notify.scrollDelay = 0; - return; - } - - // Check if notification has an icon - bool hasIcon = notify.icon; - - // Clear the matrix display - matrix->fillRect(0, 0, 32, 8, 0); - - // Calculate text and available width - uint16_t textWidth = getTextWidth(notify.text.c_str(), false); - uint16_t availableWidth = hasIcon ? 24 : 32; - - // Check if text is scrolling - bool noScrolling = textWidth <= availableWidth; - - // Check if text needs to be scrolled - if (textWidth > availableWidth && notify.scrollposition <= -textWidth) - { - // Reset scroll position and icon position if needed - notify.scrollDelay = 0; - notify.scrollposition = 9; - - if (notify.pushIcon == 2) - { - notify.iconWasPushed = false; - } - - if (notify.repeat > 0) - { - --notify.repeat; - if (notify.repeat == 0) - return; - } - } - - if (!noScrolling) - { - if ((notify.scrollDelay > MATRIX_FPS * 1.2)) - { - --notify.scrollposition; - } - else - { - ++notify.scrollDelay; - if (hasIcon) - { - if (notify.iconWasPushed && notify.pushIcon == 1) - { - notify.scrollposition = 0; - } - else - { - notify.scrollposition = 9; - } - } - else - { - notify.scrollposition = 0; - } - } - } - - // Calculate text X position based on icon presence - int16_t textX = hasIcon ? ((24 - textWidth) / 2) + 9 : ((32 - textWidth) / 2); - - // Set text color - matrix->setTextColor(notify.color); - - if (noScrolling) - { - // Disable repeat if text is not scrolling - notify.repeat = -1; - - if (notify.rainbow) - { - // Display text in rainbow color if enabled - DisplayManager.getInstance().HSVtext(textX, 6, notify.text.c_str(), false); - } - else - { - // Display text in solid color - DisplayManager.printText(textX, 6, notify.text.c_str(), false, false); - } - } - else - { - if (notify.rainbow) - { - // Display scrolling text in rainbow color if enabled - DisplayManager.getInstance().HSVtext(notify.scrollposition, 6, notify.text.c_str(), false); - } - else - { - // Display scrolling text in solid color - DisplayManager.printText(notify.scrollposition, 6, notify.text.c_str(), false, false); - } - } - - // Display icon if present and not pushed - if (hasIcon) - { - // Push icon if enabled and text is scrolling - if (notify.pushIcon > 0 && !noScrolling) - { - if (notify.iconPosition < 0 && notify.iconWasPushed == false && notify.scrollposition > 8) - { - ++notify.iconPosition; - } - - if (notify.scrollposition < 8 && !notify.iconWasPushed) - { - notify.iconPosition = notify.scrollposition - 9; - - if (notify.iconPosition <= -9) - { - notify.iconWasPushed = true; - } - } - } - - // Display animated GIF if - if (notify.isGif) - { - // Display GIF if present - DisplayManager.drawGIF(notify.iconPosition, 0, notify.icon); - } - else - { - // Display JPG image if present - DisplayManager.drawJPG(notify.iconPosition, 0, notify.icon); - } - - // Display icon divider line if text is scrolling - if (!noScrolling) - { - // matrix->drawLine(8 + notify.iconPosition, 0, 8 + notify.iconPosition, 7, 0); - } - } - - // Reset text color after displaying notification - DisplayManager.getInstance().resetTextColor(); -} - -void CFrame1(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - String name = getFrameNameByFunction(CFrame1); - ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame); -} - -void CFrame2(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - String name = getFrameNameByFunction(CFrame2); - ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame); -} - -void CFrame3(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - String name = getFrameNameByFunction(CFrame3); - ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame); -} - -void CFrame4(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - String name = getFrameNameByFunction(CFrame4); - ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame); -} - -void CFrame5(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - String name = getFrameNameByFunction(CFrame5); - ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame); -} - -void CFrame6(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - String name = getFrameNameByFunction(CFrame6); - ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame); -} - -void CFrame7(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - String name = getFrameNameByFunction(CFrame7); - ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame); -} - -void CFrame8(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - String name = getFrameNameByFunction(CFrame8); - ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame); -} - -void CFrame9(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - String name = getFrameNameByFunction(CFrame9); - ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame); -} - -void CFrame10(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - String name = getFrameNameByFunction(CFrame10); - ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame); -} - -const uint16_t *getWeatherIcon(int code) -{ - switch (code) - { - case 1: - return icon_475; - break; - - default: - return icon_475; - break; - } -} - -void WeatherFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame) -{ - if (notify.flag) - return; - CURRENT_APP = "Weather"; - DisplayManager.getInstance().resetTextColor(); - matrix->drawRGBBitmap(x, y, getWeatherIcon(WEATHER_CODE), 8, 8); - String text = WEATHER_TEMP + "°" + WEATHER_HUM + "%"; - uint16_t textWidth = getTextWidth(text.c_str(), false); - int16_t textX = ((23 - textWidth) / 2); - matrix->setCursor(textX + 11, 6 + y); - matrix->print(utf8ascii(text)); -} - -void getWeatherData() -{ - Serial.println("UPDATE"); - String weatherUrl = "https://wttr.in/" + CITY + "?format=p1"; - if ((WiFi.status() == WL_CONNECTED)) - { - HTTPClient http; - http.begin(weatherUrl); - http.setTimeout(5000); - int httpCode = http.GET(); - - if (httpCode > 0) - { - String payload = http.getString(); - int temperatureIndex = payload.indexOf("temperature_celsius{forecast=\"current\"}"); - int humIndex = payload.indexOf("humidity_percentage{forecast=\"current\"}"); - int weatherCodeIndex = payload.indexOf("weather_code{forecast=\"current\"}"); - - if (temperatureIndex >= 0 && weatherCodeIndex >= 0) - { - int tempEndIndex = payload.indexOf('\n', temperatureIndex); - int codeEndIndex = payload.indexOf('\n', weatherCodeIndex); - int humEndIndex = payload.indexOf('\n', humIndex); - String temperatureValue = payload.substring(temperatureIndex + 40, tempEndIndex); - String humValue = payload.substring(humIndex + 40, humEndIndex); - String weatherCodeValue = payload.substring(weatherCodeIndex + 33, codeEndIndex); - - WEATHER_TEMP = temperatureValue; - WEATHER_HUM = humValue; - WEATHER_CODE = weatherCodeValue.toInt(); - } - } - http.end(); - } -} - -void StartAppUpdater() -{ - // downloader.attach(60,getWeatherData); - // getWeatherData(); -} - -OverlayCallback overlays[] = {MenuFrame, NotifyFrame, AlarmFrame, TimerFrame}; +#ifndef AppS_H +#define AppS_H + +#include +#include +#include "icons.h" +#include +#include "MatrixDisplayUi.h" +#include "Globals.h" +#include "Functions.h" +#include "MenuManager.h" +#include "PeripheryManager.h" +#include "DisplayManager.h" +#include "LittleFS.h" +#include +#include +#include + +Ticker downloader; + +tm timeInfo; +uint16_t nativeAppsCount; +uint16_t customPagesCount; + +int WEATHER_CODE; +String WEATHER_TEMP; +String WEATHER_HUM; + +struct CustomApp +{ + int16_t scrollposition = 0; + int16_t scrollDelay = 0; + String text; + uint16_t color; + File icon; + bool isGif; + bool rainbow; + bool soundPlayed; + String sound; + int16_t repeat = 0; + int16_t currentRepeat = 0; + String name; + byte pushIcon = 0; + int16_t iconPosition = 0; + bool iconWasPushed = false; + int barData[16] = {0}; + int barSize; +}; + +String currentCustomApp; +std::map customApps; + +struct Notification +{ + int16_t scrollposition = 34; + int16_t scrollDelay = 0; + String text; + uint16_t color; + File icon; + bool rainbow; + bool isGif; + bool flag = false; + unsigned long startime = 0; + unsigned long duration = 0; + int16_t repeat = -1; + bool hold = false; + byte pushIcon = 0; + int16_t iconPosition = 0; + bool iconWasPushed = false; + int barData[16] = {0}; + int barSize; +}; + +Notification notify; + +std::vector> Apps; + +CustomApp *getCustomAppById(String name) +{ + return customApps.count(name) ? &customApps[name] : nullptr; +} + +String getAppNameByFunction(AppCallback AppFunction) +{ + for (const auto &appPair : Apps) + { + if (appPair.second == AppFunction) + { + return appPair.first; + } + } + + return ""; // Gibt einen leeren String zurück, wenn die App-Funktion nicht gefunden wurde +} + +int findAppIndexByName(const String &name) +{ + auto it = std::find_if(Apps.begin(), Apps.end(), [&name](const std::pair &appPair) + { return appPair.first == name; }); + if (it != Apps.end()) + { + return std::distance(Apps.begin(), it); + } + return -1; +} + +void TimeApp(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + if (notify.flag) + return; + CURRENT_APP = "Time"; + DisplayManager.getInstance().resetTextColor(); + time_t now = time(nullptr); + struct tm *timeInfo; + timeInfo = localtime(&now); + const char *timeformat = TIME_FORMAT.c_str(); + char t[20]; + char t2[20]; + if (timeformat[2] == ' ') + { + strcpy(t2, timeformat); + if (now % 2) + { + t2[2] = ' '; + } + else + { + t2[2] = ':'; + } + strftime(t, sizeof(t), t2, localtime(&now)); + } + else + { + strftime(t, sizeof(t), timeformat, localtime(&now)); + } + + DisplayManager.printText(0 + x, 6 + y, t, true, false); + + if (!SHOW_WEEKDAY) + return; + int dayOffset = START_ON_MONDAY ? 0 : 1; + for (int i = 0; i <= 6; i++) + { + if (i == (timeInfo->tm_wday + 6 + dayOffset) % 7) + { + matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(200, 200, 200)); + } + else + { + matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(100, 100, 100)); + } + } +} + +void DateApp(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + if (notify.flag) + return; + CURRENT_APP = "Date"; + DisplayManager.getInstance().resetTextColor(); + time_t now = time(nullptr); + struct tm *timeInfo; + timeInfo = localtime(&now); + char d[20]; + strftime(d, sizeof(d), DATE_FORMAT.c_str(), localtime(&now)); + DisplayManager.printText(0 + x, 6 + y, d, true, true); + if (!SHOW_WEEKDAY) + return; + int dayOffset = START_ON_MONDAY ? 0 : 1; + for (int i = 0; i <= 6; i++) + { + if (i == (timeInfo->tm_wday + 6 + dayOffset) % 7) + { + matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(200, 200, 200)); + } + else + { + matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(100, 100, 100)); + } + } +} + +void TempApp(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + if (notify.flag) + return; + CURRENT_APP = "Temperature"; + DisplayManager.getInstance().resetTextColor(); + matrix->drawRGBBitmap(x, y, get_icon(234), 8, 8); + matrix->setCursor(12 + x, 6 + y); + if (IS_CELSIUS) + { + matrix->print((int)CURRENT_TEMP); + matrix->print(utf8ascii("°C")); + } + else + { + int tempF = (CURRENT_TEMP * 9 / 5) + 32; + matrix->print(tempF); + matrix->print(utf8ascii("°F")); + } +} + +void HumApp(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + if (notify.flag) + return; + CURRENT_APP = "Humidity"; + DisplayManager.getInstance().resetTextColor(); + matrix->drawRGBBitmap(x, y + 1, get_icon(2075), 8, 8); + matrix->setCursor(14 + x, 6 + y); + int humidity = CURRENT_HUM; // Temperatur ohne Nachkommastellen + matrix->print(humidity); // Ausgabe der Temperatur + matrix->print("%"); +} + +void BatApp(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + if (notify.flag) + return; + CURRENT_APP = "Battery"; + DisplayManager.getInstance().resetTextColor(); + matrix->drawRGBBitmap(x, y, get_icon(1486), 8, 8); + matrix->setCursor(14 + x, 6 + y); + matrix->print(BATTERY_PERCENT); // Ausgabe des Ladezustands + matrix->print("%"); +} + +void MenuApp(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state) +{ + if (!MenuManager.inMenu) + return; + matrix->fillScreen(0); + DisplayManager.printText(0, 6, utf8ascii(MenuManager.menutext()).c_str(), true, true); +} + +void AlarmApp(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state) +{ + if (ALARM_ACTIVE) + { + matrix->fillScreen(matrix->Color(255, 0, 0)); + CURRENT_APP = "Alarm"; + uint16_t textWidth = getTextWidth("ALARM", false); + int16_t textX = ((32 - textWidth) / 2); + matrix->setTextColor(0); + matrix->setCursor(textX, 6); + matrix->print("ALARM"); + if (ALARM_SOUND != "") + { + if (!PeripheryManager.isPlaying()) + PeripheryManager.playFromFile("/MELODIES/" + ALARM_SOUND + ".txt"); + } + } +} + +void TimerApp(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state) +{ + if (TIMER_ACTIVE) + { + matrix->fillScreen(matrix->Color(0, 255, 0)); + CURRENT_APP = "Timer"; + String menuText = "TIMER"; + uint16_t textWidth = getTextWidth(menuText.c_str(), false); + int16_t textX = ((32 - textWidth) / 2); + matrix->setTextColor(0); + matrix->setCursor(textX, 6); + matrix->print(menuText); + if (TIMER_SOUND != "") + { + if (!PeripheryManager.isPlaying()) + PeripheryManager.playFromFile("/MELODIES/" + TIMER_SOUND + ".txt"); + } + } +} + +void ShowCustomApp(String name, FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + // Abort if notify.flag is set + if (notify.flag) + { + return; + } + + // Get custom App by ID + CustomApp *ca = getCustomAppById(name); + + // Abort if custom App not found + if (ca == nullptr) + { + return; + } + + // reset custom App properties if last App + if (lastApp) + { + ca->iconWasPushed = false; + ca->scrollposition = 9; + ca->iconPosition = 0; + ca->scrollDelay = 0; + } + + CURRENT_APP = ca->name; + currentCustomApp = name; + + bool hasIcon = ca->icon; + uint16_t availableWidth = (hasIcon) ? 24 : 32; + + bool noScrolling = getTextWidth(ca->text.c_str(), false) <= availableWidth; + if (ca->barSize > 0) + { + DisplayManager.drawBarChart(x, y, ca->barData, ca->barSize, hasIcon, ca->color); + } + else + { + if ((ca->repeat > 0) && (getTextWidth(ca->text.c_str(), false) > availableWidth) && (state->appState == FIXED)) + { + DisplayManager.setAutoTransition(false); + } + else + { + DisplayManager.setAutoTransition(true); + } + + if (getTextWidth(ca->text.c_str(), false) > availableWidth && !(state->appState == IN_TRANSITION)) + { + if (ca->scrollposition <= -getTextWidth(ca->text.c_str(), false)) + { + ca->scrollDelay = 0; + ca->scrollposition = 9; + if (ca->iconWasPushed && ca->pushIcon == 2) + { + ca->iconWasPushed = false; + } + if ((ca->currentRepeat + 1 >= ca->repeat) && (ca->repeat > 0)) + { + DisplayManager.setAutoTransition(true); + ca->currentRepeat = 0; + DisplayManager.nextApp(); + return; + } + else if (ca->repeat > 0) + { + ++ca->currentRepeat; + } + } + } + + if (!noScrolling) + { + if ((ca->scrollDelay > MATRIX_FPS * 1.2)) + { + --ca->scrollposition; + } + else + { + ++ca->scrollDelay; + if (hasIcon) + { + if (ca->iconWasPushed && ca->pushIcon == 1) + { + ca->scrollposition = 0; + } + else + { + ca->scrollposition = 9; + } + } + else + { + ca->scrollposition = 0; + } + } + } + int16_t textX = (hasIcon) ? ((24 - getTextWidth(ca->text.c_str(), false)) / 2) + 9 : ((32 - getTextWidth(ca->text.c_str(), false)) / 2); + matrix->setTextColor(ca->color); + if (noScrolling) + { + ca->repeat = -1; // Disable repeat if text is too short for scrolling + // Display text with rainbow effect if enabled + if (ca->rainbow) + { + DisplayManager.HSVtext(x + textX, 6 + y, ca->text.c_str(), false); + } + else + { + // Display text + DisplayManager.printText(x + textX, y + 6, ca->text.c_str(), false, false); + } + } + else + { + // Display scrolling text with rainbow effect if enabled + if (ca->rainbow) + { + DisplayManager.HSVtext(x + ca->scrollposition, 6 + y, ca->text.c_str(), false); + } + else + { + DisplayManager.printText(x + ca->scrollposition, 6 + y, ca->text.c_str(), false, false); + } + } + } + + if (hasIcon) + { + // Push icon if enabled and text is scrolling + if (ca->pushIcon > 0 && !noScrolling && ca->barSize == 0) + { + if (ca->iconPosition < 0 && ca->iconWasPushed == false && ca->scrollposition > 8) + { + ++ca->iconPosition; + } + + if (ca->scrollposition < 8 && !ca->iconWasPushed) + { + ca->iconPosition = ca->scrollposition - 8; + + if (ca->iconPosition <= -9) + { + ca->iconWasPushed = true; + } + } + } + + // Display animated GIF if enabled and App is fixed, since we have only one gifplayer instance, it looks weird when 2 apps want to draw a different gif + if (ca->isGif) + { + if (state->appState == FIXED) + { + DisplayManager.drawGIF(x + ca->iconPosition, y, ca->icon); + } + } + else + { + // Display JPEG image + DisplayManager.drawJPG(x + ca->iconPosition, y, ca->icon); + } + + // Draw vertical line if text is scrolling + if (!noScrolling) + { + // matrix->drawLine(8 + x + ca->iconPosition, 0 + y, 8 + x + ca->iconPosition, 7 + y, 0); + } + } + // Reset text color + DisplayManager.getInstance().resetTextColor(); +} + +void NotifyApp(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state) +{ + // Check if notification flag is set + if (!notify.flag) + { + return; // Exit function if flag is not set + } + + // Set current app name + CURRENT_APP = "Notification"; + // Get current time + unsigned long currentTime = millis(); + + // Check if notification duration has expired or if repeat count is 0 and hold is not enabled + if ((((currentTime - notify.startime >= notify.duration) && notify.repeat == -1) || notify.repeat == 0) && !notify.hold) + { + // Reset notification flags and exit function + notify.flag = false; + notify.duration = 0; + notify.startime = 0; + notify.scrollposition = 34; + notify.iconWasPushed = false; + notify.iconPosition = 0; + notify.scrollDelay = 0; + return; + } + + // Check if notification has an icon + bool hasIcon = notify.icon; + + // Clear the matrix display + matrix->fillRect(0, 0, 32, 8, 0); + + // Calculate text and available width + uint16_t textWidth = getTextWidth(notify.text.c_str(), false); + uint16_t availableWidth = hasIcon ? 24 : 32; + + // Check if text is scrolling + bool noScrolling = textWidth <= availableWidth; + if (notify.barSize > 0) + { + DisplayManager.drawBarChart(0, 0, notify.barData, notify.barSize, hasIcon, notify.color); + } + else + { + // Check if text needs to be scrolled + if (textWidth > availableWidth && notify.scrollposition <= -textWidth) + { + // Reset scroll position and icon position if needed + notify.scrollDelay = 0; + notify.scrollposition = 9; + + if (notify.pushIcon == 2) + { + notify.iconWasPushed = false; + } + + if (notify.repeat > 0) + { + --notify.repeat; + if (notify.repeat == 0) + return; + } + } + + if (!noScrolling) + { + if ((notify.scrollDelay > MATRIX_FPS * 1.2)) + { + --notify.scrollposition; + } + else + { + ++notify.scrollDelay; + if (hasIcon) + { + if (notify.iconWasPushed && notify.pushIcon == 1) + { + notify.scrollposition = 0; + } + else + { + notify.scrollposition = 9; + } + } + else + { + notify.scrollposition = 0; + } + } + } + + // Calculate text X position based on icon presence + int16_t textX = hasIcon ? ((24 - textWidth) / 2) + 9 : ((32 - textWidth) / 2); + + // Set text color + matrix->setTextColor(notify.color); + + if (noScrolling) + { + // Disable repeat if text is not scrolling + notify.repeat = -1; + + if (notify.rainbow) + { + // Display text in rainbow color if enabled + DisplayManager.HSVtext(textX, 6, notify.text.c_str(), false); + } + else + { + // Display text in solid color + DisplayManager.printText(textX, 6, notify.text.c_str(), false, false); + } + } + else + { + if (notify.rainbow) + { + // Display scrolling text in rainbow color if enabled + DisplayManager.HSVtext(notify.scrollposition, 6, notify.text.c_str(), false); + } + else + { + // Display scrolling text in solid color + DisplayManager.printText(notify.scrollposition, 6, notify.text.c_str(), false, false); + } + } + } + + // Display icon if present and not pushed + if (hasIcon) + { + // Push icon if enabled and text is scrolling + if (notify.pushIcon > 0 && !noScrolling && notify.barSize == 0) + { + if (notify.iconPosition < 0 && notify.iconWasPushed == false && notify.scrollposition > 8) + { + ++notify.iconPosition; + } + + if (notify.scrollposition < 8 && !notify.iconWasPushed) + { + notify.iconPosition = notify.scrollposition - 9; + + if (notify.iconPosition <= -9) + { + notify.iconWasPushed = true; + } + } + } + + // Display animated GIF if + if (notify.isGif) + { + // Display GIF if present + DisplayManager.drawGIF(notify.iconPosition, 0, notify.icon); + } + else + { + // Display JPG image if present + DisplayManager.drawJPG(notify.iconPosition, 0, notify.icon); + } + + // Display icon divider line if text is scrolling + if (!noScrolling) + { + // matrix->drawLine(8 + notify.iconPosition, 0, 8 + notify.iconPosition, 7, 0); + } + } + + // Reset text color after displaying notification + DisplayManager.getInstance().resetTextColor(); +} + +void CApp1(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + String name = getAppNameByFunction(CApp1); + ShowCustomApp(name, matrix, state, x, y, firstApp, lastApp); +} + +void CApp2(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + String name = getAppNameByFunction(CApp2); + ShowCustomApp(name, matrix, state, x, y, firstApp, lastApp); +} + +void CApp3(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + String name = getAppNameByFunction(CApp3); + ShowCustomApp(name, matrix, state, x, y, firstApp, lastApp); +} + +void CApp4(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + String name = getAppNameByFunction(CApp4); + ShowCustomApp(name, matrix, state, x, y, firstApp, lastApp); +} + +void CApp5(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + String name = getAppNameByFunction(CApp5); + ShowCustomApp(name, matrix, state, x, y, firstApp, lastApp); +} + +void CApp6(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + String name = getAppNameByFunction(CApp6); + ShowCustomApp(name, matrix, state, x, y, firstApp, lastApp); +} + +void CApp7(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + String name = getAppNameByFunction(CApp7); + ShowCustomApp(name, matrix, state, x, y, firstApp, lastApp); +} + +void CApp8(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + String name = getAppNameByFunction(CApp8); + ShowCustomApp(name, matrix, state, x, y, firstApp, lastApp); +} + +void CApp9(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + String name = getAppNameByFunction(CApp9); + ShowCustomApp(name, matrix, state, x, y, firstApp, lastApp); +} + +void CApp10(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + String name = getAppNameByFunction(CApp10); + ShowCustomApp(name, matrix, state, x, y, firstApp, lastApp); +} + +const uint16_t *getWeatherIcon(int code) +{ + switch (code) + { + case 1: + return icon_475; + break; + + default: + return icon_475; + break; + } +} + +void WeatherApp(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstApp, bool lastApp) +{ + if (notify.flag) + return; + CURRENT_APP = "Weather"; + DisplayManager.getInstance().resetTextColor(); + matrix->drawRGBBitmap(x, y, getWeatherIcon(WEATHER_CODE), 8, 8); + String text = WEATHER_TEMP + "°" + WEATHER_HUM + "%"; + uint16_t textWidth = getTextWidth(text.c_str(), false); + int16_t textX = ((23 - textWidth) / 2); + matrix->setCursor(textX + 11, 6 + y); + matrix->print(utf8ascii(text)); +} + +void getWeatherData() +{ + Serial.println("UPDATE"); + String weatherUrl = "https://wttr.in/" + CITY + "?format=p1"; + if ((WiFi.status() == WL_CONNECTED)) + { + HTTPClient http; + http.begin(weatherUrl); + http.setTimeout(5000); + int httpCode = http.GET(); + + if (httpCode > 0) + { + String payload = http.getString(); + int temperatureIndex = payload.indexOf("temperature_celsius{forecast=\"current\"}"); + int humIndex = payload.indexOf("humidity_percentage{forecast=\"current\"}"); + int weatherCodeIndex = payload.indexOf("weather_code{forecast=\"current\"}"); + + if (temperatureIndex >= 0 && weatherCodeIndex >= 0) + { + int tempEndIndex = payload.indexOf('\n', temperatureIndex); + int codeEndIndex = payload.indexOf('\n', weatherCodeIndex); + int humEndIndex = payload.indexOf('\n', humIndex); + String temperatureValue = payload.substring(temperatureIndex + 40, tempEndIndex); + String humValue = payload.substring(humIndex + 40, humEndIndex); + String weatherCodeValue = payload.substring(weatherCodeIndex + 33, codeEndIndex); + + WEATHER_TEMP = temperatureValue; + WEATHER_HUM = humValue; + WEATHER_CODE = weatherCodeValue.toInt(); + } + } + http.end(); + } +} + +void StartAppUpdater() +{ + // downloader.attach(60,getWeatherData); + // getWeatherData(); +} + +OverlayCallback overlays[] = {MenuApp, NotifyApp, AlarmApp, TimerApp}; #endif \ No newline at end of file diff --git a/src/DisplayManager.cpp b/src/DisplayManager.cpp index e3e9710..eefb3f0 100644 --- a/src/DisplayManager.cpp +++ b/src/DisplayManager.cpp @@ -12,12 +12,17 @@ #include "Functions.h" #include "ServerManager.h" #include "MenuManager.h" -#include "Frames.h" +#include "Apps.h" Ticker AlarmTicker; Ticker TimerTicker; +#ifdef ULANZI #define MATRIX_PIN 32 +#else +#define MATRIX_PIN D2 +#endif + #define MATRIX_WIDTH 32 #define MATRIX_HEIGHT 8 @@ -68,6 +73,11 @@ void DisplayManager_::MatrixState(bool on) bool DisplayManager_::setAutoTransition(bool active) { + if (ui.AppCount < 2) + { + ui.disablesetAutoTransition(); + return false; + } if (active && AUTO_TRANSITION) { ui.enablesetAutoTransition(); @@ -198,31 +208,31 @@ void DisplayManager_::HSVtext(int16_t x, int16_t y, const char *text, bool clear matrix.show(); } -void pushCustomFrame(String name, int position) +void pushCustomApp(String name, int position) { - if (customFrames.count(name) == 0) + if (customApps.count(name) == 0) { ++customPagesCount; - void (*customFrames[10])(FastLED_NeoMatrix *, MatrixDisplayUiState *, int16_t, int16_t, bool, bool) = {CFrame1, CFrame2, CFrame3, CFrame4, CFrame5, CFrame6, CFrame7, CFrame8, CFrame9, CFrame10}; + void (*customApps[10])(FastLED_NeoMatrix *, MatrixDisplayUiState *, int16_t, int16_t, bool, bool) = {CApp1, CApp2, CApp3, CApp4, CApp5, CApp6, CApp7, CApp8, CApp9, CApp10}; if (position < 0) // Insert at the end of the vector { - Apps.push_back(std::make_pair(name, customFrames[customPagesCount])); + Apps.push_back(std::make_pair(name, customApps[customPagesCount])); } else if (position < Apps.size()) // Insert at a specific position { - Apps.insert(Apps.begin() + position, std::make_pair(name, customFrames[customPagesCount])); + Apps.insert(Apps.begin() + position, std::make_pair(name, customApps[customPagesCount])); } else // Invalid position, Insert at the end of the vector { - Apps.push_back(std::make_pair(name, customFrames[customPagesCount])); + Apps.push_back(std::make_pair(name, customApps[customPagesCount])); } - ui.setApps(Apps); // Add frames + ui.setApps(Apps); // Add Apps } } -void removeCustomFrame(const String &name) +void removeCustomApp(const String &name) { auto it = std::find_if(Apps.begin(), Apps.end(), [&name](const std::pair &appPair) { return appPair.first == name; }); @@ -236,10 +246,10 @@ void removeCustomFrame(const String &name) void DisplayManager_::generateCustomPage(String name, String payload) { - if (payload == "" && customFrames.count(name)) + if (payload == "" && customApps.count(name)) { - customFrames.erase(customFrames.find(name)); - removeCustomFrame(name); + customApps.erase(customApps.find(name)); + removeCustomApp(name); return; } @@ -248,31 +258,52 @@ void DisplayManager_::generateCustomPage(String name, String payload) if (error) return; - CustomFrame customFrame; + CustomApp customApp; if (doc.containsKey("sound")) { - customFrame.sound = ("/" + doc["sound"].as() + ".txt"); + customApp.sound = ("/" + doc["sound"].as() + ".txt"); } else { - customFrame.sound = ""; + customApp.sound = ""; } - customFrame.rainbow = doc.containsKey("rainbow") ? doc["rainbow"] : false; - customFrame.pushIcon = doc.containsKey("pushIcon") ? doc["pushIcon"] : 0; - customFrame.name = name; - customFrame.text = utf8ascii(doc["text"].as()); - customFrame.color = doc.containsKey("color") ? doc["color"].is() ? hexToRgb565(doc["color"]) : doc["color"].is() ? hexToRgb565(doc["color"].as()) - : TEXTCOLOR_565 - : TEXTCOLOR_565; - - if (currentCustomFrame != name) + if (doc.containsKey("bar")) { - customFrame.scrollposition = 9; + JsonArray barData = doc["bar"]; + int i = 0; + for (JsonVariant v : barData) + { + if (i >= 16) + { + break; + } + customApp.barData[i] = v.as(); + i++; + } + customApp.barSize = i; + } + else + { + customApp.barSize = 0; } - customFrame.repeat = doc.containsKey("repeat") ? doc["repeat"].as() : -1; + int pos = doc.containsKey("pos") ? doc["pos"].as() : -1; + customApp.rainbow = doc.containsKey("rainbow") ? doc["rainbow"] : false; + customApp.pushIcon = doc.containsKey("pushIcon") ? doc["pushIcon"] : 0; + customApp.name = name; + customApp.text = utf8ascii(doc["text"].as()); + customApp.color = doc.containsKey("color") ? doc["color"].is() ? hexToRgb565(doc["color"]) : doc["color"].is() ? hexToRgb565(doc["color"].as()) + : TEXTCOLOR_565 + : TEXTCOLOR_565; + + if (currentCustomApp != name) + { + customApp.scrollposition = 9; + } + + customApp.repeat = doc.containsKey("repeat") ? doc["repeat"].as() : -1; if (doc.containsKey("icon")) { @@ -280,20 +311,28 @@ void DisplayManager_::generateCustomPage(String name, String payload) if (LittleFS.exists("/ICONS/" + iconFileName + ".jpg")) { - customFrame.isGif = false; - customFrame.icon = LittleFS.open("/ICONS/" + iconFileName + ".jpg"); + customApp.isGif = false; + customApp.icon = LittleFS.open("/ICONS/" + iconFileName + ".jpg"); } else if (LittleFS.exists("/ICONS/" + iconFileName + ".gif")) { - customFrame.isGif = true; - customFrame.icon = LittleFS.open("/ICONS/" + iconFileName + ".gif"); + customApp.isGif = true; + customApp.icon = LittleFS.open("/ICONS/" + iconFileName + ".gif"); + } + else + { + fs::File nullPointer; + customApp.icon = nullPointer; } } + else + { + fs::File nullPointer; + customApp.icon = nullPointer; + } - int pos = doc.containsKey("pos") ? doc["pos"].as() : -1; - - pushCustomFrame(name, pos - 1); - customFrames[name] = customFrame; + pushCustomApp(name, pos - 1); + customApps[name] = customApp; } void DisplayManager_::generateNotification(String payload) @@ -318,6 +357,26 @@ void DisplayManager_::generateNotification(String payload) PeripheryManager.playFromFile("/MELODIES/" + doc["sound"].as() + ".txt"); } + if (doc.containsKey("bar")) + { + JsonArray barData = doc["bar"]; + int i = 0; + for (JsonVariant v : barData) + { + if (i >= 16) + { + break; + } + notify.barData[i] = v.as(); + i++; + } + notify.barSize = i; + } + else + { + notify.barSize = 0; + } + notify.color = doc.containsKey("color") ? doc["color"].is() ? hexToRgb565(doc["color"]) : doc["color"].is() ? hexToRgb565(doc["color"].as()) : TEXTCOLOR_565 : TEXTCOLOR_565; @@ -329,17 +388,24 @@ void DisplayManager_::generateNotification(String payload) { notify.isGif = false; notify.icon = LittleFS.open("/ICONS/" + iconFileName + ".jpg"); + return; } else if (LittleFS.exists("/ICONS/" + iconFileName + ".gif")) { notify.isGif = true; notify.icon = LittleFS.open("/ICONS/" + iconFileName + ".gif"); + return; + } + else + { + fs::File nullPointer; + notify.icon = nullPointer; } } else { - File f; - notify.icon = f; + fs::File nullPointer; + notify.icon = nullPointer; } } @@ -374,25 +440,23 @@ void DisplayManager_::loadNativeApps() }; // Update the "time" app at position 0 - updateApp("time", TimeFrame, SHOW_TIME, 0); + updateApp("time", TimeApp, SHOW_TIME, 0); // Update the "date" app at position 1 - updateApp("date", DateFrame, SHOW_DATE, 1); + updateApp("date", DateApp, SHOW_DATE, 1); // Update the "temp" app at position 2 - updateApp("temp", TempFrame, SHOW_TEMP, 2); + updateApp("temp", TempApp, SHOW_TEMP, 2); // Update the "hum" app at position 3 - updateApp("hum", HumFrame, SHOW_HUM, 3); + updateApp("hum", HumApp, SHOW_HUM, 3); // Update the "bat" app at position 4 - updateApp("bat", BatFrame, SHOW_BAT, 4); + updateApp("bat", BatApp, SHOW_BAT, 4); ui.setApps(Apps); - if (AUTO_TRANSITION && Apps.size() == 1) - { - setAutoTransition(false); - } + + setAutoTransition(true); } void DisplayManager_::setup() @@ -415,12 +479,12 @@ void DisplayManager_::tick() else { ui.update(); - if (ui.getUiState()->frameState == IN_TRANSITION && !appIsSwitching) + if (ui.getUiState()->appState == IN_TRANSITION && !appIsSwitching) { appIsSwitching = true; MQTTManager.setCurrentApp(CURRENT_APP); } - else if (ui.getUiState()->frameState == FIXED && appIsSwitching) + else if (ui.getUiState()->appState == FIXED && appIsSwitching) { appIsSwitching = false; MQTTManager.setCurrentApp(CURRENT_APP); @@ -593,4 +657,50 @@ void DisplayManager_::drawMenuIndicator(int cur, int total, uint16_t color) matrix.drawLine(x, 7, x + menuItemWidth - 1, 7, matrix.Color(100, 100, 100)); } } +} + +void DisplayManager_::drawBarChart(int16_t x, int16_t y, const int data[], byte dataSize, bool withIcon, uint16_t color) +{ + int maximum = 0; + int newData[dataSize]; + + // Finde das Maximum in der Datenliste + for (int i = 0; i < dataSize; i++) + { + int d = data[i]; + if (d > maximum) + { + maximum = d; + } + } + + // Berechne neue Datenwerte zwischen 0 und 8 basierend auf dem Maximum + for (int i = 0; i < dataSize; i++) + { + int d = data[i]; + newData[i] = map(d, 0, maximum, 0, 8); + } + + // Berechne die Breite der Balken basierend auf der Anzahl der Daten und der Breite der Matrix + int barWidth; + if (withIcon) + { + barWidth = ((32 - 9) / (dataSize)-1); + } + else + { + barWidth = (32 / (dataSize)-1); + } + + // Berechne die Startposition des Graphen basierend auf dem Icon-Parameter + int startX = withIcon ? 9 : 0; + + // Zeichne die Balken auf die Matrix + for (int i = 0; i < dataSize; i++) + { + int x1 = x + startX + (barWidth + 1) * i; + int barHeight = newData[i]; + int y1 = min(8 - barHeight, 7); + matrix.fillRect(x1, y1 + y, barWidth, barHeight, color); + } } \ No newline at end of file diff --git a/src/DisplayManager.h b/src/DisplayManager.h index d677e7e..ce8714d 100644 --- a/src/DisplayManager.h +++ b/src/DisplayManager.h @@ -56,6 +56,7 @@ public: void drawProgressBar(int cur, int total); void drawMenuIndicator(int cur, int total, uint16_t color); void drawBMP(int16_t x, int16_t y, const uint16_t bitmap[], int16_t w, int16_t h); + void drawBarChart(int16_t x, int16_t y,const int data[], byte dataSize, bool withIcon, uint16_t color); }; extern DisplayManager_ &DisplayManager; diff --git a/src/GifPlayer.h b/src/GifPlayer.h index 5da1ea5..0fbc6ea 100644 --- a/src/GifPlayer.h +++ b/src/GifPlayer.h @@ -528,7 +528,7 @@ public: } } needNewFrame = false; - lastFrameTime = millis(); + lastFrameTime = millis(); } public: @@ -612,7 +612,6 @@ public: byte b = readByte(); if (b == 0x2c) { - Serial.println("Parse"); parseTableBasedImage(); return 0; } @@ -641,11 +640,10 @@ public: { done = true; file.seek(0); - Serial.println("Finished"); parseGifHeader(); parseLogicalScreenDescriptor(); parseGlobalColorTable(); - drawFrame(offsetX,offsetY); + drawFrame(offsetX, offsetY); return ERROR_FINISHED; } } diff --git a/src/Globals.cpp b/src/Globals.cpp index 421b050..e8ceea7 100644 --- a/src/Globals.cpp +++ b/src/Globals.cpp @@ -1,8 +1,18 @@ #include "Globals.h" #include "Preferences.h" +#include Preferences Settings; +char *getID() +{ + uint8_t mac[6]; + WiFi.macAddress(mac); + char *macStr = new char[24]; + snprintf(macStr, 24, "awtrix_%02x%02x%02x", mac[3], mac[4], mac[5]); + return macStr; +} + void loadSettings() { Settings.begin("awtrix", false); @@ -23,6 +33,8 @@ void loadSettings() SHOW_HUM = Settings.getBool("HUM", true); SHOW_BAT = Settings.getBool("BAT", true); Settings.end(); + uniqueID = getID(); + MQTT_PREFIX = String(uniqueID); } void saveSettings() @@ -39,7 +51,6 @@ void saveSettings() Settings.putString("DFORMAT", DATE_FORMAT); Settings.putBool("SOM", START_ON_MONDAY); Settings.putBool("CEL", IS_CELSIUS); - Settings.putBool("TIM", SHOW_TIME); Settings.putBool("DAT", SHOW_DATE); Settings.putBool("TEMP", SHOW_TEMP); @@ -48,17 +59,18 @@ void saveSettings() Settings.end(); } +const char *uniqueID; IPAddress local_IP; IPAddress gateway; IPAddress subnet; IPAddress primaryDNS; IPAddress secondaryDNS; -const char *VERSION = "0.41"; +const char *VERSION = "0.42"; String MQTT_HOST = ""; uint16_t MQTT_PORT = 1883; String MQTT_USER; String MQTT_PASS; -String MQTT_PREFIX = "AwtrixLight"; +String MQTT_PREFIX; String CITY = "Berlin,de"; bool IO_BROKER = false; bool NET_STATIC = false; diff --git a/src/Globals.h b/src/Globals.h index 81c7b34..66f59f5 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -2,6 +2,7 @@ #define GLOBALS_H #include +extern const char *uniqueID; extern const char *VERSION; extern IPAddress local_IP; extern IPAddress gateway; diff --git a/src/MQTTManager.cpp b/src/MQTTManager.cpp index f74cd50..08a1ab5 100644 --- a/src/MQTTManager.cpp +++ b/src/MQTTManager.cpp @@ -6,24 +6,32 @@ #include #include +unsigned long startTime; + WiFiClient espClient; uint8_t lastBrightness; HADevice device; -HAMqtt mqtt(espClient, device, 15); +HAMqtt mqtt(espClient, device, 18); unsigned long reconnectTimer = 0; const unsigned long reconnectInterval = 30000; // 30 Sekunden -HALight Matrix("bri", HALight::BrightnessFeature | HALight::RGBFeature); -HASelect BriMode("BriMode"); -HAButton dismiss("myButtonA"); -HAButton nextApp("myButtonB"); -HAButton prevApp("myButtonC"); -HASensor curApp("curApp"); -HASensor battery("battery"); -HASensor temperature("temperature"); -HASensor humidity("huminity"); -HASensor illuminance("illuminance"); +HALight *Matrix = nullptr; +HASelect *BriMode = nullptr; +HAButton *dismiss = nullptr; +HAButton *nextApp = nullptr; +HAButton *prevApp = nullptr; + +HASensor *curApp = nullptr; +HASensor *battery = nullptr; +HASensor *temperature = nullptr; +HASensor *humidity = nullptr; +HASensor *illuminance = nullptr; +HASensor *uptime = nullptr; +HASensor *strength = nullptr; + +HASensor *version = nullptr; +HASensor *ram = nullptr; // The getter for the instantiated singleton instance MQTTManager_ &MQTTManager_::getInstance() @@ -37,15 +45,15 @@ MQTTManager_ &MQTTManager = MQTTManager.getInstance(); void onButtonCommand(HAButton *sender) { - if (sender == &dismiss) + if (sender == dismiss) { DisplayManager.dismissNotify(); } - else if (sender == &nextApp) + else if (sender == nextApp) { DisplayManager.nextApp(); } - else if (sender == &prevApp) + else if (sender == prevApp) { DisplayManager.previousApp(); } @@ -66,7 +74,7 @@ void onSelectCommand(int8_t index, HASelect *sender) AUTO_BRIGHTNESS = true; return; } - Matrix.setBrightness(BRIGHTNESS); + Matrix->setBrightness(BRIGHTNESS); saveSettings(); sender->setState(index); // report the selected option back to the HA panel } @@ -203,71 +211,128 @@ void connect() } } +char matID[40], briID[40]; +char btnAID[40], btnBID[40], btnCID[40], appID[40], tempID[40], humID[40], luxID[40], verID[40], batID[40], ramID[40], upID[40], sigID[40]; + void MQTTManager_::setup() { - byte mac[6]; - WiFi.macAddress(mac); - + startTime = millis(); if (HA_DISCOVERY) { Serial.println("Starting Homeassistant discorvery"); + uint8_t mac[6]; + WiFi.macAddress(mac); + char *macStr = new char[18 + 1]; + snprintf(macStr, 24, "%02x%02x%02x", mac[3], mac[4], mac[5]); device.setUniqueId(mac, sizeof(mac)); - device.setName(MQTT_PREFIX.c_str()); + device.setName(uniqueID); device.setSoftwareVersion(VERSION); device.setManufacturer("Blueforcer"); device.setModel("AWTRIX Light"); device.setAvailability(true); - Matrix.setIcon("mdi:lightbulb"); - Matrix.setName("Matrix"); - Matrix.onStateCommand(onStateCommand); - Matrix.onBrightnessCommand(onBrightnessCommand); - Matrix.onRGBColorCommand(onRGBColorCommand); - Matrix.setCurrentState(true); - Matrix.setBRIGHTNESS(BRIGHTNESS); + + String uniqueIDWithSuffix; + + sprintf(matID, "%s_mat", macStr); + Matrix = new HALight(matID, HALight::BrightnessFeature | HALight::RGBFeature); + + Matrix->setIcon("mdi:lightbulb"); + Matrix->setName("Matrix"); + Matrix->onStateCommand(onStateCommand); + Matrix->onBrightnessCommand(onBrightnessCommand); + Matrix->onRGBColorCommand(onRGBColorCommand); + Matrix->setCurrentState(true); + Matrix->setBRIGHTNESS(BRIGHTNESS); HALight::RGBColor color; color.red = (TEXTCOLOR_565 >> 11) << 3; color.green = ((TEXTCOLOR_565 >> 5) & 0x3F) << 2; color.blue = (TEXTCOLOR_565 & 0x1F) << 3; - Matrix.setCurrentRGBColor(color); - Matrix.setState(true, true); + Matrix->setCurrentRGBColor(color); + Matrix->setState(true, true); - BriMode.setOptions("Manual;Auto"); // use semicolons as separator of options - BriMode.onCommand(onSelectCommand); - BriMode.setIcon("mdi:brightness-auto"); // optional - BriMode.setName("Brightness mode"); // optional - BriMode.setState(AUTO_BRIGHTNESS, true); + sprintf(briID, "%s_bri", macStr); + BriMode = new HASelect(briID); + BriMode->setOptions("Manual;Auto"); // use semicolons as separator of options + BriMode->onCommand(onSelectCommand); + BriMode->setIcon("mdi:brightness-auto"); // optional + BriMode->setName("Brightness mode"); // optional + BriMode->setState(AUTO_BRIGHTNESS, true); - dismiss.setIcon("mdi:bell-off"); - dismiss.setName("Dismiss notification"); - nextApp.setIcon("mdi:arrow-right-bold"); - nextApp.setName("Next app"); - prevApp.setIcon("mdi:arrow-left-bold"); - prevApp.setName("Previous app"); + sprintf(btnAID, "%s_btna", macStr); + dismiss = new HAButton(btnAID); + dismiss->setIcon("mdi:bell-off"); + dismiss->setName("Dismiss notification"); - dismiss.onCommand(onButtonCommand); - nextApp.onCommand(onButtonCommand); - prevApp.onCommand(onButtonCommand); + sprintf(btnBID, "%s_btnb", macStr); + nextApp = new HAButton(btnBID); + nextApp->setIcon("mdi:arrow-right-bold"); + nextApp->setName("Next app"); - curApp.setIcon("mdi:apps"); - curApp.setName("Current app"); + sprintf(btnCID, "%s_btnc", macStr); + prevApp = new HAButton(btnCID); + prevApp->setIcon("mdi:arrow-left-bold"); + prevApp->setName("Previous app"); - temperature.setIcon("mdi:thermometer"); - temperature.setName("Temperature"); - temperature.setUnitOfMeasurement("°C"); + dismiss->onCommand(onButtonCommand); + nextApp->onCommand(onButtonCommand); + prevApp->onCommand(onButtonCommand); - humidity.setIcon("mdi:water-percent"); - humidity.setName("humidity"); - humidity.setUnitOfMeasurement("%"); + sprintf(appID, "%s_app", macStr); + curApp = new HASensor(appID); + curApp->setIcon("mdi:apps"); + curApp->setName("Current app"); - battery.setIcon("mdi:battery-90"); - battery.setName("Battery"); - battery.setUnitOfMeasurement("%"); + sprintf(tempID, "%s_temp", macStr); + temperature = new HASensor(tempID); + temperature->setIcon("mdi:thermometer"); + temperature->setName("Temperature"); + temperature->setDeviceClass("temperature"); + temperature->setUnitOfMeasurement("°C"); - illuminance.setIcon("mdi:sun-wireless"); - illuminance.setName("Illuminance"); - illuminance.setUnitOfMeasurement("lx"); + sprintf(humID, "%s_hum", macStr); + humidity = new HASensor(humID); + humidity->setIcon("mdi:water-percent"); + humidity->setName("humidity"); + humidity->setDeviceClass("humidity"); + humidity->setUnitOfMeasurement("%"); + + sprintf(batID, "%s_bat", macStr); + battery = new HASensor(batID); + battery->setIcon("mdi:battery-90"); + battery->setName("Battery"); + battery->setDeviceClass("battery"); + battery->setUnitOfMeasurement("%"); + + sprintf(luxID, "%s_lux", macStr); + illuminance = new HASensor(luxID); + illuminance->setIcon("mdi:sun-wireless"); + illuminance->setName("Illuminance"); + illuminance->setDeviceClass("illuminance"); + illuminance->setUnitOfMeasurement("lx"); + + sprintf(verID, "%s_ver", macStr); + version = new HASensor(verID); + version->setName("Version"); + + sprintf(sigID, "%s_sig", macStr); + strength = new HASensor(sigID); + strength->setName("WiFi strength"); + strength->setDeviceClass("signal_strength"); + strength->setUnitOfMeasurement("dB"); + + sprintf(upID, "%s_up", macStr); + uptime = new HASensor(upID); + uptime->setName("Uptime"); + uptime->setDeviceClass("duration"); + + sprintf(ramID, "%s_ram", macStr); + ram = new HASensor(ramID); + ram->setDeviceClass("data_size"); + ram->setIcon("mdi:application-cog"); + ram->setName("Free ram"); + ram->setUnitOfMeasurement("B"); } else { @@ -299,7 +364,23 @@ void MQTTManager_::publish(const char *topic, const char *payload) void MQTTManager_::setCurrentApp(String value) { if (HA_DISCOVERY) - curApp.setValue(value.c_str()); + curApp->setValue(value.c_str()); +} + +const char *readUptime() +{ + static char uptime[25]; // Make the array static to keep it from being destroyed when the function returns + unsigned long currentTime = millis(); + unsigned long elapsedTime = currentTime - startTime; + unsigned long uptimeSeconds = elapsedTime / 1000; + unsigned long uptimeMinutes = uptimeSeconds / 60; + unsigned long uptimeHours = uptimeMinutes / 60; + unsigned long uptimeDays = uptimeHours / 24; + unsigned long hours = uptimeHours % 24; + unsigned long minutes = uptimeMinutes % 60; + unsigned long seconds = uptimeSeconds % 60; + sprintf(uptime, "P%dDT%dH%dM%dS", uptimeDays, hours, minutes, seconds); + return uptime; } void MQTTManager_::sendStats() @@ -308,20 +389,32 @@ void MQTTManager_::sendStats() { char buffer[5]; snprintf(buffer, 5, "%d", BATTERY_PERCENT); - battery.setValue(buffer); + battery->setValue(buffer); snprintf(buffer, 5, "%.0f", CURRENT_TEMP); - temperature.setValue(buffer); + temperature->setValue(buffer); snprintf(buffer, 5, "%.0f", CURRENT_HUM); - humidity.setValue(buffer); + humidity->setValue(buffer); snprintf(buffer, 5, "%.0f", CURRENT_LUX); - illuminance.setValue(buffer); + illuminance->setValue(buffer); - BriMode.setState(AUTO_BRIGHTNESS, true); - Matrix.setBRIGHTNESS(BRIGHTNESS); - Matrix.setState(!MATRIX_OFF, false); + BriMode->setState(AUTO_BRIGHTNESS, true); + Matrix->setBRIGHTNESS(BRIGHTNESS); + Matrix->setState(!MATRIX_OFF, false); + + int8_t rssiValue = WiFi.RSSI(); + char rssiString[4]; + snprintf(rssiString, sizeof(rssiString), "%d", rssiValue); + strength->setValue(rssiString); + + char rambuffer[10]; + int freeHeapBytes = ESP.getFreeHeap(); + itoa(freeHeapBytes, rambuffer, 10); + ram->setValue(rambuffer); + uptime->setValue(readUptime()); + version->setValue(VERSION); } StaticJsonDocument<200> doc; @@ -336,6 +429,8 @@ void MQTTManager_::sendStats() doc["temp"] = buffer; snprintf(buffer, 5, "%.0f", CURRENT_HUM); doc["hum"] = buffer; + doc["uptime"] = readUptime(); + doc["wifi"] = WiFi.RSSI(); String jsonString; serializeJson(doc, jsonString); char topic[50]; diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index 993afea..798f98e 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -135,26 +135,20 @@ String MenuManager_::menutext() return String(TIME_PER_APP / 1000.0, 0) + "s"; case TimeFormatMenu: DisplayManager.drawMenuIndicator(timeFormatIndex, timeFormatCount, 0xFBC0); + + char display[9]; if (timeFormat[timeFormatIndex][2] == ' ') { - strcpy(display, timeFormat[timeFormatIndex]); - if (now % 2) - { - display[2] = ' '; - } - else - { - display[2] = ':'; - } - strftime(t, sizeof(t), display, localtime(&now)); - return t; + snprintf(display, sizeof(display), "%s", timeFormat[timeFormatIndex]); + display[2] = now % 2 ? ' ' : ':'; } else { - strftime(t, sizeof(t), timeFormat[timeFormatIndex], localtime(&now)); - return t; + snprintf(display, sizeof(display), "%s", timeFormat[timeFormatIndex]); } + strftime(t, sizeof(t), display, localtime(&now)); + return t; case DateFormatMenu: DisplayManager.drawMenuIndicator(dateFormatIndex, dateFormatCount, 0xFBC0); strftime(t, sizeof(t), dateFormat[dateFormatIndex], localtime(&now)); @@ -427,6 +421,8 @@ void MenuManager_::selectButtonLong() saveSettings(); break; case DateFormatMenu: + DATE_FORMAT = dateFormat[dateFormatIndex]; + saveSettings(); case WeekdayMenu: case TempMenu: saveSettings(); diff --git a/src/PeripheryManager.cpp b/src/PeripheryManager.cpp index 8cb86e2..4090edf 100644 --- a/src/PeripheryManager.cpp +++ b/src/PeripheryManager.cpp @@ -10,12 +10,25 @@ #include #define SOUND_OFF false -#define BATTERY_PIN 34 -#define BUZZER_PIN 15 -#define LDR_PIN 35 -#define BUTTON_UP_PIN 26 -#define BUTTON_DOWN_PIN 14 -#define BUTTON_SELECT_PIN 27 + +#ifdef ULANZI +// Pinouts für das ULANZI-Environment + #define BATTERY_PIN 34 + #define BUZZER_PIN 15 + #define LDR_PIN 35 + #define BUTTON_UP_PIN 26 + #define BUTTON_DOWN_PIN 14 + #define BUTTON_SELECT_PIN 27 +#else +// Pinouts für das WEMOS_D1_MINI32-Environment + #define BATTERY_PIN -1 + #define BUZZER_PIN -1 + #define LDR_PIN A0 + #define BUTTON_UP_PIN D0 + #define BUTTON_DOWN_PIN D4 + #define BUTTON_SELECT_PIN D8 +#endif + Adafruit_SHT31 sht31; EasyButton button_left(BUTTON_UP_PIN); @@ -45,7 +58,7 @@ float sampleSum = 0.0; float sampleAverage = 0.0; float brightnessPercent = 0.0; -unsigned long startTime; + // The getter for the instantiated singleton instance PeripheryManager_ &PeripheryManager_::getInstance() @@ -100,7 +113,6 @@ void PeripheryManager_::playBootSound() const int nNotes = 6; String notes[nNotes] = {"E5", "C5", "G4", "E4", "G4", "C5"}; const int timeUnit = 150; - // create a melody Melody melody = MelodyFactory.load("Nice Melody", timeUnit, notes, nNotes); player.playAsync(melody); } @@ -128,7 +140,7 @@ void fistStart() uint16_t ADCVALUE = analogRead(BATTERY_PIN); - BATTERY_PERCENT = min((int)map(ADCVALUE, 510, 660, 0, 100), 100); + BATTERY_PERCENT = min((int)map(ADCVALUE, 490, 660, 0, 100), 100); sht31.readBoth(&CURRENT_TEMP, &CURRENT_HUM); uint16_t LDRVALUE = analogRead(LDR_PIN); @@ -167,7 +179,7 @@ void PeripheryManager_::tick() { previousMillis_BatTempHum = currentMillis_BatTempHum; uint16_t ADCVALUE = analogRead(BATTERY_PIN); - BATTERY_PERCENT = min((int)map(ADCVALUE, 510, 665, 0, 100), 100); + BATTERY_PERCENT = min((int)map(ADCVALUE, 475, 665, 0, 100), 100); BATTERY_RAW = ADCVALUE; sht31.readBoth(&CURRENT_TEMP, &CURRENT_HUM); CURRENT_TEMP -= 9.0; @@ -200,16 +212,7 @@ void PeripheryManager_::tick() } } -void readUptime() -{ - unsigned long currentTime = millis(); - unsigned long elapsedTime = currentTime - startTime; - int hours = (elapsedTime / 1000) / 3600; - int minutes = ((elapsedTime / 1000) % 3600) / 60; - int seconds = (elapsedTime / 1000) % 60; - char timeString[10]; - sprintf(timeString, "%02d:%02d:%02d", hours, minutes, seconds); -} + const int MIN_ALARM_INTERVAL = 60; // 1 Minute time_t lastAlarmTime = 0; diff --git a/src/ServerManager.cpp b/src/ServerManager.cpp index fa4d178..2f2776e 100644 --- a/src/ServerManager.cpp +++ b/src/ServerManager.cpp @@ -63,7 +63,7 @@ void ServerManager_::setup() { WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS); } - IPAddress myIP = mws.startWiFi(150000, "AWTRIX LIGHT", "12345678"); + IPAddress myIP = mws.startWiFi(150000, uniqueID, "12345678"); isConnected = !(myIP == IPAddress(192, 168, 4, 1)); Serial.println(myIP.toString()); @@ -99,7 +99,7 @@ void ServerManager_::setup() mws.addHandler("/version", HTTP_GET, versionHandler); mws.begin(); - if (!MDNS.begin(MQTT_PREFIX.c_str())) + if (!MDNS.begin(uniqueID)) { Serial.println("Error starting mDNS"); return;