- Expose buttons to HA
- HA Switch to control the transition
- Icons are not reloaded if its the same as before
This commit is contained in:
Stephan Mühl
2023-03-30 12:34:53 +02:00
parent 9381b2a33b
commit 249074d022
12 changed files with 92 additions and 30 deletions

View File

@@ -9,6 +9,7 @@
- [Onscreen](onscreen.md) - [Onscreen](onscreen.md)
- Features - Features
- [Apps](apps.md)
- [Custom Pages & Notifications](custom.md) - [Custom Pages & Notifications](custom.md)
- [Alarm clock](alarm.md) - [Alarm clock](alarm.md)
- [Timer](timer.md) - [Timer](timer.md)

38
docs/apps.md Normal file
View File

@@ -0,0 +1,38 @@
# Apps
The AWTRIX Light system comes equipped with several built-in applications, including Time, Date, Temperature, Humidity, and Battery status.
As it is designed to integrate seamlessly with your smart home ecosystem, additional applications can be created using MQTT.
There are numerous benefits to this approach:
- **Personalization:** Customize each application to suit your preferences and needs.
- **Flexibility:** Develop your own applications without the need to modify the firmware.
- **Efficient resource management:** Save valuable flash memory space on the ESP module.
- **Adaptability:** No need to rewrite the firmware if an API undergoes changes.
You can use any system you like wich is able to build json strings and send them to a mqtt topic.
[Node-RED](https://nodered.org/) serves as an ideal software solution for creating these applications.
It is available as a standalone program or as a plugin for Home Assistant and ioBroker, allowing you to further enhance the capabilities of your AWTRIX Light system.
Here is a demo of an Youtube App as NodeRED Flow:
```json
[{"id":"2a59d30d07abe14f","type":"group","z":"54b42d8d.cda474","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["f0f17299.3736c","dc7878f9.4756c8","f234aae371d72680","555bb8624b88c9c3","69c388146e28049d","a349ade5a57f7537"],"x":34,"y":39,"w":892,"h":122},{"id":"f0f17299.3736c","type":"inject","z":"54b42d8d.cda474","g":"2a59d30d07abe14f","name":"","props":[],"repeat":"3600","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":130,"y":120,"wires":[["a349ade5a57f7537"]]},{"id":"dc7878f9.4756c8","type":"http request","z":"54b42d8d.cda474","g":"2a59d30d07abe14f","name":"","method":"GET","ret":"obj","paytoqs":"query","url":"https://youtube.googleapis.com/youtube/v3/channels","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":430,"y":120,"wires":[["f234aae371d72680"]]},{"id":"f234aae371d72680","type":"function","z":"54b42d8d.cda474","g":"2a59d30d07abe14f","name":"parser","func":"var json = msg.payload;\nvar subscriberCount = json.items[0].statistics.subscriberCount;\n\nmsg.payload = { \"text\": subscriberCount, \"icon\": 5029};\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":590,"y":120,"wires":[["555bb8624b88c9c3"]]},{"id":"555bb8624b88c9c3","type":"mqtt out","z":"54b42d8d.cda474","g":"2a59d30d07abe14f","name":"","topic":"ulanzi/custom/youtube","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"346df2a95aac5785","x":800,"y":120,"wires":[]},{"id":"69c388146e28049d","type":"comment","z":"54b42d8d.cda474","g":"2a59d30d07abe14f","name":"Youtube Follower","info":"Just enter your channelID and Youtube API key in the \"Data\" node and set your AWTRIX MQTT prefix.\nUses Icon 5029 (LM)","x":140,"y":80,"wires":[]},{"id":"a349ade5a57f7537","type":"function","z":"54b42d8d.cda474","g":"2a59d30d07abe14f","name":"Data","func":"msg.payload = { \"id\": \"UCpGLALzRO0uaasWTsm9M99w\", \"key\": \"XXX\", \"part\":\"statistics\"}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":270,"y":120,"wires":[["dc7878f9.4756c8"]]},{"id":"346df2a95aac5785","type":"mqtt-broker","name":"","broker":"localhost","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""}]
```
This Node-RED flow retrieves and displays the subscriber count of a specified YouTube channel on an AWTRIX light device. The flow consists of the following nodes:
1. **Inject**: This node triggers the flow periodically (every hour) or manually.
2. **Data (Function)**: This node contains the YouTube channel ID and the YouTube API key. Replace "XXX" with your YouTube API key and Youtube ID. The node constructs a payload containing the channel ID, API key, and required statistics and sends it to the "HTTP request" node.
3. **HTTP request**: This node sends a GET request to the YouTube API to retrieve the channel's statistics. The response is returned as a JavaScript object and passed to the "parser" node.
4. **parser (Function)**: This node extracts the subscriber count from the received channel statistics and constructs a payload containing the count and an icon (Icon 5029). The payload is sent to the "MQTT out" node.
5. **MQTT out**: This node publishes the payload to the MQTT topic "ulanzi/custom/youtube" on a local MQTT broker. You also have to change the topic in this node to fit your mqtt prefix.
6. **Comment (Youtube Follower)**: This node contains additional information about the flow. It does not affect the flow's functionality.
To use this flow, replace the "XXX" in the "Data" node with your YouTube API key and ensure that the MQTT broker settings in the "MQTT out" node are correct.
The flow will then retrieve the subscriber count of the specified YouTube channel and display it on your AWTRIX device along with the icon.
This Flow uses icon 5029 from LM (Just download it from the awtrix webinterface). You can change the icon in the flow to your favorite one.

View File

@@ -11,4 +11,4 @@ To download an icon, simply enter the ID of a LaMetric or AWTRIX 2.0 icon in the
AWTRIX 2.0 icons can be found in the respective software. In a future update, non-AWTRIX 2.0 users will also get access to the database. AWTRIX 2.0 icons can be found in the respective software. In a future update, non-AWTRIX 2.0 users will also get access to the database.
You can also create your own icon and place it in the "ICONS" folder via the web interface file browser. You can also create your own icon and place it in the "ICONS" folder via the web interface file browser.
The icon needs to be a GIF or JPEG with a resolution of 8x8. The icon needs to be a GIF (.gif) or JPG (.jpg) with a resolution of 8x8.

View File

@@ -20,11 +20,22 @@
coverpage: true, coverpage: true,
loadNavbar: true, loadNavbar: true,
mergeNavbar: true, mergeNavbar: true,
repo: 'https://github.com/Blueforcer/awtrix-light' repo: 'https://github.com/Blueforcer/awtrix-light',
share: {
reddit: true,
linkedin: true,
facebook: true,
twitter: true,
whatsapp: true,
telegram: true,
}
} }
</script> </script>
<!-- Docsify v4 --> <!-- Docsify v4 -->
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script> <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>
</body> </body>
</html> </html>

View File

@@ -236,18 +236,6 @@ void MatrixDisplayUi::drawApp()
int16_t x, y, x1, y1; int16_t x, y, x1, y1;
switch (this->appAnimationDirection) switch (this->appAnimationDirection)
{ {
case SLIDE_LEFT:
x = -32 * progress;
y = 0;
x1 = x + 32;
y1 = 0;
break;
case SLIDE_RIGHT:
x = 32 * progress;
y = 0;
x1 = x - 32;
y1 = 0;
break;
case SLIDE_UP: case SLIDE_UP:
x = 0; x = 0;
y = -8 * progress; y = -8 * progress;

View File

@@ -41,9 +41,7 @@
enum AnimationDirection enum AnimationDirection
{ {
SLIDE_UP, SLIDE_UP,
SLIDE_DOWN, SLIDE_DOWN
SLIDE_LEFT,
SLIDE_RIGHT
}; };
enum AppState enum AppState
@@ -79,7 +77,7 @@ private:
FastLED_NeoMatrix *matrix; FastLED_NeoMatrix *matrix;
// Values for the Apps // Values for the Apps
AnimationDirection appAnimationDirection = SLIDE_RIGHT; AnimationDirection appAnimationDirection = SLIDE_DOWN;
int8_t lastTransitionDirection = 1; int8_t lastTransitionDirection = 1;

View File

@@ -65,6 +65,10 @@ const char HAluxUnit[] PROGMEM = {"lx"};
const char HAverID[] PROGMEM = {"%s_ver"}; const char HAverID[] PROGMEM = {"%s_ver"};
const char HAverName[] PROGMEM = {"Version"}; const char HAverName[] PROGMEM = {"Version"};
const char HAtransID[] PROGMEM = {"%s_tra"};
const char HAtransName[] PROGMEM = {"Transition"};
const char HAtransIcon[] PROGMEM = {"mdi:swap-vertical"};
const char HAsigID[] PROGMEM = {"%s_sig"}; const char HAsigID[] PROGMEM = {"%s_sig"};
const char HAsigIcon[] PROGMEM = {"mdi:sun-wireless"}; const char HAsigIcon[] PROGMEM = {"mdi:sun-wireless"};
const char HAsigName[] PROGMEM = {"WiFi strength"}; const char HAsigName[] PROGMEM = {"WiFi strength"};
@@ -84,7 +88,7 @@ const char HAbtnMName[] PROGMEM = {"Button select"};
const char HAbtnRID[] PROGMEM = {"%s_btnR"}; const char HAbtnRID[] PROGMEM = {"%s_btnR"};
const char HAbtnRName[] PROGMEM = {"Button right"}; const char HAbtnRName[] PROGMEM = {"Button right"};
const char HAramRID[] PROGMEM = {"%s_btnR"}; const char HAramRID[] PROGMEM = {"%s_ram"};
const char HAramIcon[] PROGMEM = {"mdi:application-cog"}; const char HAramIcon[] PROGMEM = {"mdi:application-cog"};
const char HAramName[] PROGMEM = {"Free ram"}; const char HAramName[] PROGMEM = {"Free ram"};
const char HAramClass[] PROGMEM = {"data_size"}; const char HAramClass[] PROGMEM = {"data_size"};

View File

@@ -75,6 +75,10 @@ extern const char HAupID[];
extern const char HAupName[]; extern const char HAupName[];
extern const char HAupClass[]; extern const char HAupClass[];
extern const char HAtransID[];
extern const char HAtransName[];
extern const char HAtransIcon[];
extern const char HAbtnLID[]; extern const char HAbtnLID[];
extern const char HAbtnLName[]; extern const char HAbtnLName[];

View File

@@ -73,6 +73,7 @@ void DisplayManager_::MatrixState(bool on)
bool DisplayManager_::setAutoTransition(bool active) bool DisplayManager_::setAutoTransition(bool active)
{ {
if (ui.AppCount < 2) if (ui.AppCount < 2)
{ {
ui.disablesetAutoTransition(); ui.disablesetAutoTransition();
@@ -315,7 +316,6 @@ void DisplayManager_::generateCustomPage(String name, String payload)
String iconFileName = String(doc["icon"].as<String>()); String iconFileName = String(doc["icon"].as<String>());
if (customApp.icon && String(customApp.icon.name()).startsWith(iconFileName)) if (customApp.icon && String(customApp.icon.name()).startsWith(iconFileName))
{ {
} }
else else
{ {

View File

@@ -65,7 +65,7 @@ IPAddress gateway;
IPAddress subnet; IPAddress subnet;
IPAddress primaryDNS; IPAddress primaryDNS;
IPAddress secondaryDNS; IPAddress secondaryDNS;
const char *VERSION = "0.42"; const char *VERSION = "0.43";
String MQTT_HOST = ""; String MQTT_HOST = "";
uint16_t MQTT_PORT = 1883; uint16_t MQTT_PORT = 1883;
String MQTT_USER; String MQTT_USER;

View File

@@ -12,7 +12,7 @@ unsigned long startTime;
WiFiClient espClient; WiFiClient espClient;
uint8_t lastBrightness; uint8_t lastBrightness;
HADevice device; HADevice device;
HAMqtt mqtt(espClient, device, 18); HAMqtt mqtt(espClient, device, 19);
unsigned long reconnectTimer = 0; unsigned long reconnectTimer = 0;
const unsigned long reconnectInterval = 30000; // 30 Sekunden const unsigned long reconnectInterval = 30000; // 30 Sekunden
@@ -22,7 +22,7 @@ HASelect *BriMode = nullptr;
HAButton *dismiss = nullptr; HAButton *dismiss = nullptr;
HAButton *nextApp = nullptr; HAButton *nextApp = nullptr;
HAButton *prevApp = nullptr; HAButton *prevApp = nullptr;
HASwitch *transition = nullptr;
HASensor *curApp = nullptr; HASensor *curApp = nullptr;
HASensor *battery = nullptr; HASensor *battery = nullptr;
HASensor *temperature = nullptr; HASensor *temperature = nullptr;
@@ -62,6 +62,14 @@ void onButtonCommand(HAButton *sender)
} }
} }
void onSwitchCommand(bool state, HASwitch *sender)
{
AUTO_TRANSITION = state;
DisplayManager.setAutoTransition(state);
saveSettings();
sender->setState(state);
}
void onSelectCommand(int8_t index, HASelect *sender) void onSelectCommand(int8_t index, HASelect *sender)
{ {
switch (index) switch (index)
@@ -215,7 +223,7 @@ void connect()
} }
char matID[40], briID[40]; 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], btnLID[40], btnMID[40], btnRID[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], btnLID[40], btnMID[40], btnRID[40], transID[40];
void MQTTManager_::setup() void MQTTManager_::setup()
{ {
@@ -268,6 +276,17 @@ void MQTTManager_::setup()
dismiss->setIcon(HAbtnaIcon); dismiss->setIcon(HAbtnaIcon);
dismiss->setName(HAbtnaName); dismiss->setName(HAbtnaName);
sprintf(transID, HAtransID, macStr);
transition = new HASwitch(transID);
transition->setIcon(HAtransIcon);
transition->setName(HAtransName);
transition->onCommand(onSwitchCommand);
sprintf(appID, HAappID, macStr);
curApp = new HASensor(appID);
curApp->setIcon(HAappIcon);
curApp->setName(HAappName);
sprintf(btnBID, HAbtnbID, macStr); sprintf(btnBID, HAbtnbID, macStr);
nextApp = new HAButton(btnBID); nextApp = new HAButton(btnBID);
nextApp->setIcon(HAbtnbIcon); nextApp->setIcon(HAbtnbIcon);
@@ -282,11 +301,6 @@ void MQTTManager_::setup()
nextApp->onCommand(onButtonCommand); nextApp->onCommand(onButtonCommand);
prevApp->onCommand(onButtonCommand); prevApp->onCommand(onButtonCommand);
sprintf(appID, HAappID, macStr);
curApp = new HASensor(appID);
curApp->setIcon(HAappIcon);
curApp->setName(HAappName);
sprintf(tempID, HAtempID, macStr); sprintf(tempID, HAtempID, macStr);
temperature = new HASensor(tempID); temperature = new HASensor(tempID);
temperature->setIcon(HAtempIcon); temperature->setIcon(HAtempIcon);
@@ -430,6 +444,10 @@ void MQTTManager_::sendStats()
ram->setValue(rambuffer); ram->setValue(rambuffer);
uptime->setValue(readUptime()); uptime->setValue(readUptime());
version->setValue(VERSION); version->setValue(VERSION);
transition->setState(AUTO_TRANSITION, false);
}
else
{
} }
StaticJsonDocument<200> doc; StaticJsonDocument<200> doc;

View File

@@ -225,7 +225,7 @@ void PeripheryManager_::checkAlarms()
DeserializationError error = deserializeJson(doc, file); DeserializationError error = deserializeJson(doc, file);
if (error) if (error)
{ {
Serial.println("Failed to read Alarm file"); Serial.println(F("Failed to read Alarm file"));
return; return;
} }
JsonArray alarms = doc["alarms"]; JsonArray alarms = doc["alarms"];