- App transistion is now inacitve if there is only 1 app
- Adds bargraph to notify and customapps
- Every awtrix now gets a unique id for AP, MQTT and HA
- Adds firmware as HA sensor
- Adds wifi strength as HA sensor
- Adds ram usage as HA sensor
- Adds version as HA sensor
- Adds uptime as ISO 8601 as HA sensot
- HA discorvery now gets correct device classes
- fixes bug where date formats are not saved

closes #24
closes #23
closes #21
This commit is contained in:
Stephan Mühl
2023-03-29 00:24:38 +02:00
parent 3de324605e
commit c51c769cb5
16 changed files with 1217 additions and 967 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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<std::pair<String, AppCallback>> &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;
}

View File

@@ -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<std::pair<String, AppCallback>> &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();

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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<String, AppCallback> &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<String>() + ".txt");
customApp.sound = ("/" + doc["sound"].as<String>() + ".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<String>());
customFrame.color = doc.containsKey("color") ? doc["color"].is<String>() ? hexToRgb565(doc["color"]) : doc["color"].is<JsonArray>() ? hexToRgb565(doc["color"].as<String>())
: 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<int>();
i++;
}
customApp.barSize = i;
}
else
{
customApp.barSize = 0;
}
customFrame.repeat = doc.containsKey("repeat") ? doc["repeat"].as<uint8_t>() : -1;
int pos = doc.containsKey("pos") ? doc["pos"].as<uint8_t>() : -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<String>());
customApp.color = doc.containsKey("color") ? doc["color"].is<String>() ? hexToRgb565(doc["color"]) : doc["color"].is<JsonArray>() ? hexToRgb565(doc["color"].as<String>())
: TEXTCOLOR_565
: TEXTCOLOR_565;
if (currentCustomApp != name)
{
customApp.scrollposition = 9;
}
customApp.repeat = doc.containsKey("repeat") ? doc["repeat"].as<uint8_t>() : -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<uint8_t>() : -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<String>() + ".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<int>();
i++;
}
notify.barSize = i;
}
else
{
notify.barSize = 0;
}
notify.color = doc.containsKey("color") ? doc["color"].is<String>() ? hexToRgb565(doc["color"]) : doc["color"].is<JsonArray>() ? hexToRgb565(doc["color"].as<String>())
: 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);
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -1,8 +1,18 @@
#include "Globals.h"
#include "Preferences.h"
#include <WiFi.h>
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;

View File

@@ -2,6 +2,7 @@
#define GLOBALS_H
#include <Arduino.h>
extern const char *uniqueID;
extern const char *VERSION;
extern IPAddress local_IP;
extern IPAddress gateway;

View File

@@ -6,24 +6,32 @@
#include <WiFi.h>
#include <ArduinoJson.h>
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];

View File

@@ -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();

View File

@@ -10,12 +10,25 @@
#include <MenuManager.h>
#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;

View File

@@ -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;