v0.45
- Introduces an function that allows users to add, remove, and rearrange multiple apps on the device at once via MQTT. This provides greater flexibility and customization options. Please note that this function is experimental and should be used with caution. https://blueforcer.github.io/awtrix-light/#/mqtt?id=addremove-and-rearange-apps
This commit is contained in:
@@ -10,11 +10,11 @@
|
||||
|
||||
- Features
|
||||
- [Apps](apps.md)
|
||||
- [Custom Pages & Notifications](custom.md)
|
||||
- [Alarm clock](alarm.md)
|
||||
- [Timer](timer.md)
|
||||
- [Icons](icons.md)
|
||||
- [Sounds](sounds.md)
|
||||
|
||||
- MQTT
|
||||
- [Custom Pages & Notifications](custom.md)
|
||||
- [Commands](mqtt.md)
|
||||
|
||||
@@ -9,7 +9,7 @@ The JSON object has the following properties:
|
||||
|
||||
| Key | Type | Description | Default |
|
||||
| --- | ---- | ----------- | ------- |
|
||||
| `pos` | number | defines the position of your custompage in the loop, starting at 1 for the first position. This will only apply with your first push. You cant change the position afterwards. For that you need to delete it and add it again. | At the end of the loop |
|
||||
| `pos` | number | defines the position of your custompage in the loop, starting at 0 for the first position. This will only apply with your first push. You cant change the position afterwards with [this function](mqtt?id=addremove-and-rearange-apps) |
|
||||
| `text` | string | The text to display on the page. | |
|
||||
| `icon` | string | The icon ID or filename (without extension) to display on the page. | |
|
||||
| `repeat` | number | Sets how many times the text should be scrolled through the matrix before the display ends. | 1 |
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
|
||||
<script src="https://unpkg.com/docsify-copy-code@2"></script>
|
||||
<script src="//unpkg.com/docsify-share/build/index.min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/prismjs@1.22/components/prism-json.min.js"></script>
|
||||
<script src="//unpkg.com/prismjs/components/prism-json.min.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
70
docs/mqtt.md
70
docs/mqtt.md
@@ -31,6 +31,76 @@ Built-in app names are:
|
||||
|
||||
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.
|
||||
|
||||
## Add/remove and rearange apps
|
||||
|
||||
|
||||
| Topic |
|
||||
| --- |
|
||||
| `[PREFIX]/apps` |
|
||||
|
||||
!> This function provides users with the ability to manage the apps on their device by adding, removing, and rearranging them. However, as it is an experimental feature, caution should be exercised, particularly when attempting to rearrange multiple apps at once, as this can lead to unintended consequences due to the resulting shifts in position of other apps.
|
||||
|
||||
By using this function, users can add or remove native apps, as well as custom apps, from the device.
|
||||
However, it is important to note that custom apps are only temporarily loaded into memory and cannot be added again using this function.
|
||||
To add a custom app again, you must send it to awtrix via mqtt again.
|
||||
|
||||
Additionally, you can rearrange the position of all apps on the device by specifying a new position in the JSON array.
|
||||
This provides flexibility in organizing apps according to personal preference.
|
||||
|
||||
The JSON payload is an array of objects, where each object represents an app to be displayed on awtrix. Each app object contains the following fields:
|
||||
|
||||
`"name"`: The name of the app ("time", "date", "temp", "hum", "bat") are the native apps.
|
||||
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.
|
||||
`"show"`: A boolean indicating whether the app should be shown on the screen or not. If not present, the app is considered active by default.
|
||||
`"pos"`: An integer indicating the position of the app in the list. If not present, the app will be added to the end of the list.
|
||||
|
||||
> You can also just send the information for one app.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name":"time",
|
||||
"show":true,
|
||||
"pos":3
|
||||
},
|
||||
{
|
||||
"name":"date",
|
||||
"pos":0
|
||||
},
|
||||
{
|
||||
"name":"temp",
|
||||
"pos":2
|
||||
},
|
||||
{
|
||||
"name":"hum",
|
||||
"show":true,
|
||||
"pos":0
|
||||
},
|
||||
{
|
||||
"name":"bat",
|
||||
"show":false
|
||||
},
|
||||
{
|
||||
"name":"github",
|
||||
"show":true,
|
||||
"pos":4
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
|
||||
In this example,
|
||||
- The "time" app is active and should be displayed in position 3.
|
||||
- The "date" app should be displayed in position 0.
|
||||
- The "temp" app should be displayed in position 2.
|
||||
- The "hum" app should be displayed at first position.
|
||||
- The "bat" app is inactive and will be removed,
|
||||
- and the "github" app is active and should be displayed in position 4.
|
||||
|
||||
|
||||
|
||||
|
||||
## Change Settings
|
||||
Change various settings related to the app display.
|
||||
|
||||
|
||||
@@ -93,14 +93,11 @@ void MatrixDisplayUi::setApps(const std::vector<std::pair<String, AppCallback>>
|
||||
{
|
||||
delete[] AppFunctions;
|
||||
AppCount = appPairs.size();
|
||||
Serial.println(AppCount);
|
||||
AppFunctions = new AppCallback[AppCount];
|
||||
|
||||
for (size_t i = 0; i < AppCount; ++i)
|
||||
{
|
||||
AppFunctions[i] = appPairs[i].second;
|
||||
}
|
||||
|
||||
this->resetState();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ board = esp32dev
|
||||
board_build.partitions = awtrix_partition.csv
|
||||
upload_speed = 921600
|
||||
framework = arduino
|
||||
build_flags = -DULANZI
|
||||
build_flags = -DULANZI -D MQTT_MAX_PACKET_SIZE=1024
|
||||
lib_deps =
|
||||
adafruit/Adafruit SHT31 Library@^2.2.0
|
||||
bblanchon/ArduinoJson@^6.20.0
|
||||
@@ -29,7 +29,7 @@ board = wemos_d1_mini32
|
||||
board_build.partitions = awtrix_partition.csv
|
||||
upload_speed = 921600
|
||||
framework = arduino
|
||||
build_flags = -DAWTRIX_UPGRADE
|
||||
build_flags = -DAWTRIX_UPGRADE -D MQTT_MAX_PACKET_SIZE=1024
|
||||
lib_deps =
|
||||
adafruit/Adafruit SHT31 Library@^2.2.0
|
||||
bblanchon/ArduinoJson@^6.20.0
|
||||
|
||||
@@ -245,9 +245,9 @@ void removeCustomApp(const String &name)
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayManager_::generateCustomPage(String name, String payload)
|
||||
void DisplayManager_::generateCustomPage(String name, const char *json)
|
||||
{
|
||||
if (payload == "" && customApps.count(name))
|
||||
if (json == "" && customApps.count(name))
|
||||
{
|
||||
customApps.erase(customApps.find(name));
|
||||
removeCustomApp(name);
|
||||
@@ -255,7 +255,7 @@ void DisplayManager_::generateCustomPage(String name, String payload)
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(1024);
|
||||
DeserializationError error = deserializeJson(doc, payload);
|
||||
DeserializationError error = deserializeJson(doc, json);
|
||||
if (error)
|
||||
return;
|
||||
|
||||
@@ -300,9 +300,30 @@ void DisplayManager_::generateCustomPage(String name, String payload)
|
||||
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 (doc.containsKey("color"))
|
||||
{
|
||||
auto color = doc["color"];
|
||||
if (color.is<String>())
|
||||
{
|
||||
customApp.color = hexToRgb565(color.as<String>());
|
||||
}
|
||||
else if (color.is<JsonArray>() && color.size() == 3)
|
||||
{
|
||||
uint8_t r = color[0];
|
||||
uint8_t g = color[1];
|
||||
uint8_t b = color[2];
|
||||
customApp.color = (r << 11) | (g << 5) | b;
|
||||
}
|
||||
else
|
||||
{
|
||||
customApp.color = TEXTCOLOR_565;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
customApp.color = TEXTCOLOR_565;
|
||||
}
|
||||
|
||||
if (currentCustomApp != name)
|
||||
{
|
||||
@@ -346,10 +367,10 @@ void DisplayManager_::generateCustomPage(String name, String payload)
|
||||
customApps[name] = customApp;
|
||||
}
|
||||
|
||||
void DisplayManager_::generateNotification(String payload)
|
||||
void DisplayManager_::generateNotification(const char *json)
|
||||
{
|
||||
StaticJsonDocument<1024> doc;
|
||||
deserializeJson(doc, payload);
|
||||
deserializeJson(doc, json);
|
||||
|
||||
notify.duration = doc.containsKey("duration") ? doc["duration"].as<int>() * 1000 : TIME_PER_APP;
|
||||
notify.text = utf8ascii(doc["text"].as<String>());
|
||||
@@ -388,9 +409,29 @@ void DisplayManager_::generateNotification(String payload)
|
||||
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;
|
||||
if (doc.containsKey("color"))
|
||||
{
|
||||
auto color = doc["color"];
|
||||
if (color.is<String>())
|
||||
{
|
||||
notify.color = hexToRgb565(color.as<String>());
|
||||
}
|
||||
else if (color.is<JsonArray>() && color.size() == 3)
|
||||
{
|
||||
uint8_t r = color[0];
|
||||
uint8_t g = color[1];
|
||||
uint8_t b = color[2];
|
||||
notify.color = (r << 11) | (g << 5) | b;
|
||||
}
|
||||
else
|
||||
{
|
||||
notify.color = TEXTCOLOR_565;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
notify.color = TEXTCOLOR_565;
|
||||
}
|
||||
|
||||
if (doc.containsKey("icon"))
|
||||
{
|
||||
@@ -607,10 +648,10 @@ void DisplayManager_::gererateTimer(String Payload)
|
||||
TimerTicker.attach_ms(interval, timerCallback);
|
||||
}
|
||||
|
||||
void DisplayManager_::switchToApp(String Payload)
|
||||
void DisplayManager_::switchToApp(const char *json)
|
||||
{
|
||||
DynamicJsonDocument doc(512);
|
||||
DeserializationError error = deserializeJson(doc, Payload);
|
||||
DeserializationError error = deserializeJson(doc, json);
|
||||
if (error)
|
||||
return;
|
||||
String name = doc["name"].as<String>();
|
||||
@@ -620,10 +661,10 @@ void DisplayManager_::switchToApp(String Payload)
|
||||
ui.transitionToApp(index);
|
||||
}
|
||||
|
||||
void DisplayManager_::setNewSettings(String Payload)
|
||||
void DisplayManager_::setNewSettings(const char *json)
|
||||
{
|
||||
DynamicJsonDocument doc(512);
|
||||
DeserializationError error = deserializeJson(doc, Payload);
|
||||
DeserializationError error = deserializeJson(doc, json);
|
||||
if (error)
|
||||
return;
|
||||
TIME_PER_APP = doc.containsKey("apptime") ? doc["apptime"] : TIME_PER_APP;
|
||||
@@ -715,3 +756,123 @@ void DisplayManager_::drawBarChart(int16_t x, int16_t y, const int data[], byte
|
||||
matrix.fillRect(x1, y1 + y, barWidth, barHeight, color);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayManager_::updateAppVector(const char *json)
|
||||
{
|
||||
// Parse the JSON input
|
||||
DynamicJsonDocument doc(1024);
|
||||
auto error = deserializeJson(doc, json);
|
||||
if (error)
|
||||
{
|
||||
// If parsing fails, print an error message and return
|
||||
Serial.print("Failed to parse JSON: ");
|
||||
Serial.println(error.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new vectors to store updated apps
|
||||
std::vector<std::pair<String, AppCallback>> newApps;
|
||||
std::vector<String> activeApps;
|
||||
|
||||
// Loop through all apps in the JSON input
|
||||
for (const auto &app : doc.as<JsonArray>())
|
||||
{
|
||||
// Get the app name, active status, and position (if specified)
|
||||
String name = app["name"].as<String>();
|
||||
bool show = true;
|
||||
int position = -1;
|
||||
|
||||
if (app.containsKey("show"))
|
||||
{
|
||||
show = app["show"].as<bool>();
|
||||
}
|
||||
if (app.containsKey("pos"))
|
||||
{
|
||||
position = app["pos"].as<int>();
|
||||
}
|
||||
|
||||
// Find the corresponding AppCallback function based on the app name
|
||||
AppCallback callback;
|
||||
if (name == "time")
|
||||
{
|
||||
callback = TimeApp;
|
||||
SHOW_TIME = show;
|
||||
}
|
||||
else if (name == "date")
|
||||
{
|
||||
callback = DateApp;
|
||||
SHOW_DATE = show;
|
||||
}
|
||||
else if (name == "temp")
|
||||
{
|
||||
callback = TempApp;
|
||||
SHOW_TEMP = show;
|
||||
}
|
||||
else if (name == "hum")
|
||||
{
|
||||
callback = HumApp;
|
||||
SHOW_HUM = show;
|
||||
}
|
||||
else if (name == "bat")
|
||||
{
|
||||
callback = BatApp;
|
||||
SHOW_BAT = show;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the app is not one of the built-in apps, check if it's already in the vector
|
||||
int appIndex = findAppIndexByName(name);
|
||||
if (appIndex >= 0)
|
||||
{
|
||||
// The app is in the vector, so we can move it to a new position or remove it
|
||||
auto it = Apps.begin() + appIndex;
|
||||
if (show)
|
||||
{
|
||||
if (position >= 0 && static_cast<size_t>(position) < newApps.size())
|
||||
{
|
||||
Apps.erase(it);
|
||||
newApps.insert(newApps.begin() + position, std::make_pair(name, it->second));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the app is being removed, also remove it from the customApps map
|
||||
if (customApps.count(name))
|
||||
{
|
||||
customApps.erase(customApps.find(name));
|
||||
removeCustomApp(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (show)
|
||||
{
|
||||
// Add the app to the new vector
|
||||
if (position >= 0 && static_cast<size_t>(position) < newApps.size())
|
||||
{
|
||||
newApps.insert(newApps.begin() + position, std::make_pair(name, callback));
|
||||
}
|
||||
else
|
||||
{
|
||||
newApps.emplace_back(name, callback);
|
||||
}
|
||||
}
|
||||
|
||||
activeApps.push_back(name);
|
||||
}
|
||||
|
||||
// Loop through all apps currently in the vector
|
||||
for (const auto &app : Apps)
|
||||
{
|
||||
// If the app is not in the updated activeApps vector, add it to the newApps vector
|
||||
if (std::find(activeApps.begin(), activeApps.end(), app.first) == activeApps.end())
|
||||
{
|
||||
newApps.push_back(app);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the apps vector, set it in the UI, and save settings
|
||||
Apps = std::move(newApps);
|
||||
ui.setApps(Apps);
|
||||
}
|
||||
@@ -45,18 +45,19 @@ public:
|
||||
void setTextColor(uint16_t color);
|
||||
void setFPS(uint8_t);
|
||||
void MatrixState(bool);
|
||||
void generateNotification(String);
|
||||
void generateCustomPage(String, String);
|
||||
void generateNotification(const char *json);
|
||||
void generateCustomPage(String, const char *json);
|
||||
void printText(int16_t x, int16_t y, const char *text, bool centered, bool ignoreUppercase);
|
||||
bool setAutoTransition(bool active);
|
||||
void switchToApp(String Payload);
|
||||
void setNewSettings(String Payload);
|
||||
void switchToApp(const char *json);
|
||||
void setNewSettings(const char *json);
|
||||
void drawGIF(uint16_t x, uint16_t y, fs::File gifFile);
|
||||
void drawJPG(uint16_t x, uint16_t y, fs::File jpgFile);
|
||||
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);
|
||||
void drawBarChart(int16_t x, int16_t y, const int data[], byte dataSize, bool withIcon, uint16_t color);
|
||||
void updateAppVector(const char *json);
|
||||
};
|
||||
|
||||
extern DisplayManager_ &DisplayManager;
|
||||
|
||||
@@ -65,7 +65,7 @@ IPAddress gateway;
|
||||
IPAddress subnet;
|
||||
IPAddress primaryDNS;
|
||||
IPAddress secondaryDNS;
|
||||
const char *VERSION = "0.44";
|
||||
const char *VERSION = "0.45";
|
||||
String MQTT_HOST = "";
|
||||
uint16_t MQTT_PORT = 1883;
|
||||
String MQTT_USER;
|
||||
|
||||
@@ -128,21 +128,22 @@ void onBrightnessCommand(uint8_t brightness, HALight *sender)
|
||||
void onMqttMessage(const char *topic, const uint8_t *payload, uint16_t length)
|
||||
{
|
||||
String strTopic = String(topic);
|
||||
String strPayload = String((const char *)payload).substring(0, length);
|
||||
|
||||
char *payloadCopy = new char[length + 1];
|
||||
memcpy(payloadCopy, payload, length);
|
||||
payloadCopy[length] = '\0';
|
||||
if (strTopic == MQTT_PREFIX + "/notify")
|
||||
{
|
||||
if (payload[0] != '{' || payload[length - 1] != '}')
|
||||
{
|
||||
return;
|
||||
}
|
||||
DisplayManager.generateNotification(strPayload);
|
||||
DisplayManager.generateNotification(payloadCopy);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strTopic == MQTT_PREFIX + "/timer")
|
||||
{
|
||||
DisplayManager.gererateTimer(strPayload);
|
||||
DisplayManager.gererateTimer(payloadCopy);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -152,15 +153,21 @@ void onMqttMessage(const char *topic, const uint8_t *payload, uint16_t length)
|
||||
return;
|
||||
}
|
||||
|
||||
if (strTopic == MQTT_PREFIX + "/apps")
|
||||
{
|
||||
DisplayManager.updateAppVector(payloadCopy);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strTopic == MQTT_PREFIX + "/switch")
|
||||
{
|
||||
DisplayManager.switchToApp(strPayload);
|
||||
DisplayManager.switchToApp(payloadCopy);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strTopic == MQTT_PREFIX + "/settings")
|
||||
{
|
||||
DisplayManager.setNewSettings(strPayload);
|
||||
DisplayManager.setNewSettings(payloadCopy);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,26 +192,34 @@ void onMqttMessage(const char *topic, const uint8_t *payload, uint16_t length)
|
||||
topic_str = topic_str.substring(prefix.length());
|
||||
}
|
||||
|
||||
DisplayManager.generateCustomPage(topic_str, strPayload);
|
||||
DisplayManager.generateCustomPage(topic_str, payloadCopy);
|
||||
return;
|
||||
}
|
||||
delete[] payloadCopy;
|
||||
}
|
||||
|
||||
void onMqttConnected()
|
||||
{
|
||||
String prefix = MQTT_PREFIX;
|
||||
mqtt.subscribe((prefix + String("/brightness")).c_str());
|
||||
mqtt.subscribe((prefix + String("/notify/dismiss")).c_str());
|
||||
mqtt.subscribe((prefix + String("/notify")).c_str());
|
||||
mqtt.subscribe((prefix + String("/timer")).c_str());
|
||||
mqtt.subscribe((prefix + String("/custom/#")).c_str());
|
||||
mqtt.subscribe((prefix + String("/switch")).c_str());
|
||||
mqtt.subscribe((prefix + String("/settings")).c_str());
|
||||
mqtt.subscribe((prefix + String("/previousapp")).c_str());
|
||||
mqtt.subscribe((prefix + String("/nextapp")).c_str());
|
||||
Serial.println("MQTT Connected");
|
||||
const char* topics[] PROGMEM = {
|
||||
"/brightness",
|
||||
"/notify/dismiss",
|
||||
"/notify",
|
||||
"/timer",
|
||||
"/custom/#",
|
||||
"/switch",
|
||||
"/settings",
|
||||
"/previousapp",
|
||||
"/nextapp",
|
||||
"/nextapp",
|
||||
"/apps"
|
||||
};
|
||||
for (const char* topic : topics) {
|
||||
String fullTopic = prefix + topic;
|
||||
mqtt.subscribe(fullTopic.c_str());
|
||||
}
|
||||
Serial.println(F("MQTT Connected"));
|
||||
}
|
||||
|
||||
void connect()
|
||||
{
|
||||
mqtt.onMessage(onMqttMessage);
|
||||
@@ -212,12 +227,12 @@ void connect()
|
||||
|
||||
if (MQTT_USER == "" || MQTT_PASS == "")
|
||||
{
|
||||
Serial.println("Connecting to MQTT w/o login");
|
||||
Serial.println(F("Connecting to MQTT w/o login"));
|
||||
mqtt.begin(MQTT_HOST.c_str(), MQTT_PORT, nullptr, nullptr, MQTT_PREFIX.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("Connecting to MQTT with login");
|
||||
Serial.println(F("Connecting to MQTT with login"));
|
||||
mqtt.begin(MQTT_HOST.c_str(), MQTT_PORT, MQTT_USER.c_str(), MQTT_PASS.c_str(), MQTT_PREFIX.c_str());
|
||||
}
|
||||
}
|
||||
@@ -365,7 +380,7 @@ void MQTTManager_::setup()
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("Homeassistant discovery disabled");
|
||||
Serial.println(F("Homeassistant discovery disabled"));
|
||||
mqtt.disableHA();
|
||||
}
|
||||
connect();
|
||||
|
||||
Reference in New Issue
Block a user