From b08e38f06cdf5dd3dbeef31149e684c46919ff49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20M=C3=BChl?= <31169771+Blueforcer@users.noreply.github.com> Date: Sun, 2 Apr 2023 00:56:24 +0200 Subject: [PATCH] v0.47 -Add update sensor to HA. AWL search for an update every hour. - Fixes duration in custom apps --- docs/api.md | 55 +++++++++++++-- src/Dictionary.cpp | 12 ++++ src/Dictionary.h | 11 ++- src/DisplayManager.cpp | 7 +- src/Globals.cpp | 5 +- src/Globals.h | 1 + src/MQTTManager.cpp | 32 ++++++++- src/MenuManager.cpp | 5 +- src/ServerManager.cpp | 7 +- src/Updater.cpp | 152 +++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 7 +- src/updater.h | 137 ++++++------------------------------- 12 files changed, 299 insertions(+), 132 deletions(-) create mode 100644 src/Updater.cpp diff --git a/docs/api.md b/docs/api.md index 5043187..85fa022 100644 --- a/docs/api.md +++ b/docs/api.md @@ -6,6 +6,13 @@ In MQTT awtrix send its stats every 10s to `[PREFIX]/stats` With HTTP, make GET request to `http://[IP]/api/stats` +## Update +Awtrix searches for an update every 1 Hour. If a new one is found it will be published to HA and in the stats. +You can start the update with update button in HA or: +| Topic | URL | Payload/Body | HTTP Header | HTTP method | +| --- | --- | --- |--- |--- | +| `[PREFIX]/doupdate` |`http://[IP]/api/doupdate` | JSON | empty payload/body | POST | + ## Add custom app create custom apps or notifications to display your own text and icons. Have a look at [this section](custom?id=custom-apps-and-notifications) @@ -64,10 +71,14 @@ 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. +### JSON Properties + +| Property | Description |Default | +|----------|-------------|-------------| +| name | The name of the app. If it's a native app, it can be one of "time", "date", "temp", "hum", or "bat". 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. | true | +| 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. | Last Item | + > You can also just send the information for one app. @@ -129,6 +140,8 @@ Change various settings related to the app display. | --- | --- | --- |--- | | `[PREFIX]/settings` |`http://[IP]/api/settings`| JSON | POST | + +#### JSON Properties Each property is optional; you do not need to send all. | Key | Type | Description | Value Range | Default | @@ -140,3 +153,37 @@ Each property is optional; you do not need to send all. | `brightness` | number | Determines the brightness of the matrix. | An integer between 0 and 255. | N/A | | `autobrightness` | boolean | Determines if automatic brightness control is active. | `true` or `false`. | N/A | | `autotransition` | boolean | Determines if automatic switching to the next app is active. | `true` or `false`. | N/A | + + +## Timer + +With AWTRIX Light, you can set a timer using MQTT. Simply send a JSON object to the topic **[PREFIX]/timer** to start a timer. + +When the timer goes off, the display will show a notification, and you can dismiss the timer by pressing the middle button. + +#### JSON Properties + +The JSON object has the following properties: + +| Key | Type | Description | +| --- | ---- | ----------- | +| `hours` | number | The number of hours after midnight when the timer should be triggered. | +| `minutes` | number | The number of minutes after the hour when the timer should be triggered. | +| `seconds` | number | The number of seconds after the minute when the timer should be triggered. | +| `sound` | string | The name of the sound file (without extension) to play when the timer is triggered. | + +Each value is optional, so you can set a timer for just minutes, or any combination of hours, minutes, and seconds. If you only want to start a timer in some minutes, just send the minutes. + +## Example + +Here's an example JSON object to start a timer for 1 hour, 30 minutes, and 10 seconds, with the sound "friends": + +```json +{ + "hours": 1, + "minutes": 30, + "seconds": 10, + "sound": "friends" +} +``` + diff --git a/src/Dictionary.cpp b/src/Dictionary.cpp index b90798c..307be12 100644 --- a/src/Dictionary.cpp +++ b/src/Dictionary.cpp @@ -69,6 +69,17 @@ const char HAtransID[] PROGMEM = {"%s_tra"}; const char HAtransName[] PROGMEM = {"Transition"}; const char HAtransIcon[] PROGMEM = {"mdi:swap-vertical"}; +const char HAupdateID[] PROGMEM = {"%s_upd"}; +const char HAupdateName[] PROGMEM = {"Update"}; +const char HAupdateClass[] PROGMEM = {"update"}; +const char HAupdateIcon[] PROGMEM = {"mdi:update"}; + + +const char HAdoUpID[] PROGMEM = {"%s_doupd"}; +const char HAdoUpName[] PROGMEM = {"Start Update"}; +const char HAdoUpIcon[] PROGMEM = {"mdi:update"}; + + const char HAsigID[] PROGMEM = {"%s_sig"}; const char HAsigIcon[] PROGMEM = {"mdi:sun-wireless"}; const char HAsigName[] PROGMEM = {"WiFi strength"}; @@ -104,3 +115,4 @@ const char TempKey[] PROGMEM = {"temp"}; const char HumKey[] PROGMEM = {"hum"}; const char UpTimeKey[] PROGMEM = {"uptime"}; const char SignalStrengthKey[] PROGMEM = {"wifi_signal"}; +const char UpdateKey[] PROGMEM = {"up_available"}; diff --git a/src/Dictionary.h b/src/Dictionary.h index 79132fc..3993788 100644 --- a/src/Dictionary.h +++ b/src/Dictionary.h @@ -77,10 +77,19 @@ extern const char HAupID[]; extern const char HAupName[]; extern const char HAupClass[]; +extern const char HAdoUpID[]; +extern const char HAdoUpName[]; +extern const char HAdoUpIcon[]; + extern const char HAtransID[]; extern const char HAtransName[]; extern const char HAtransIcon[]; +extern const char HAupdateID[]; +extern const char HAupdateName[]; +extern const char HAupdateClass[]; +extern const char HAupdateIcon[]; + extern const char HAbtnLID[]; extern const char HAbtnLName[]; @@ -106,5 +115,5 @@ extern const char TempKey[]; extern const char HumKey[]; extern const char UpTimeKey[]; extern const char SignalStrengthKey[]; - +extern const char UpdateKey[]; #endif diff --git a/src/DisplayManager.cpp b/src/DisplayManager.cpp index d0aa5b9..a709abb 100644 --- a/src/DisplayManager.cpp +++ b/src/DisplayManager.cpp @@ -293,7 +293,7 @@ void DisplayManager_::generateCustomPage(String name, const char *json) customApp.barSize = 0; } - customApp.duration = doc.containsKey("duration") ? doc["duration"].as() * 1000 : -1; + customApp.duration = doc.containsKey("duration") ? doc["duration"].as() * 1000 : 0; 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; @@ -514,7 +514,7 @@ void DisplayManager_::loadNativeApps() void DisplayManager_::setup() { - + TJpgDec.setCallback(jpg_output); FastLED.addLeds(leds, MATRIX_WIDTH * MATRIX_HEIGHT); gif.setMatrix(&matrix); @@ -891,8 +891,10 @@ String DisplayManager_::getStat() { StaticJsonDocument<200> doc; char buffer[5]; +#ifdef ULANZI doc[BatKey] = BATTERY_PERCENT; doc[BatRawKey] = BATTERY_RAW; +#endif snprintf(buffer, 5, "%.0f", CURRENT_LUX); doc[LuxKey] = buffer; doc[LDRRawKey] = LDR_RAW; @@ -903,6 +905,7 @@ String DisplayManager_::getStat() doc[HumKey] = buffer; doc[UpTimeKey] = PeripheryManager.readUptime(); doc[SignalStrengthKey] = WiFi.RSSI(); + doc[UpdateKey] = UPDATE_AVAILABLE; String jsonString; serializeJson(doc, jsonString); return jsonString; diff --git a/src/Globals.cpp b/src/Globals.cpp index 985bb2b..abdc1d4 100644 --- a/src/Globals.cpp +++ b/src/Globals.cpp @@ -121,7 +121,7 @@ IPAddress gateway; IPAddress subnet; IPAddress primaryDNS; IPAddress secondaryDNS; -const char *VERSION = "0.46"; +const char *VERSION = "0.47"; String MQTT_HOST = ""; uint16_t MQTT_PORT = 1883; String MQTT_USER; @@ -187,4 +187,5 @@ bool SOUND_ACTIVE; String BOOT_SOUND = ""; uint8_t VOLUME; uint8_t VOLUME_PERCENT; -int MATRIX_LAYOUT; \ No newline at end of file +int MATRIX_LAYOUT; +bool UPDATE_AVAILABLE = false; \ No newline at end of file diff --git a/src/Globals.h b/src/Globals.h index 6cb3e06..db7f469 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -71,6 +71,7 @@ extern String BOOT_SOUND; extern uint8_t VOLUME; extern uint8_t VOLUME_PERCENT; extern int MATRIX_LAYOUT; +extern bool UPDATE_AVAILABLE; void loadSettings(); void saveSettings(); #endif // Globals_H \ No newline at end of file diff --git a/src/MQTTManager.cpp b/src/MQTTManager.cpp index 301dce3..0748361 100644 --- a/src/MQTTManager.cpp +++ b/src/MQTTManager.cpp @@ -7,6 +7,7 @@ #include #include "Dictionary.h" #include "PeripheryManager.h" +#include "Updater.h" WiFiClient espClient; uint8_t lastBrightness; @@ -36,6 +37,8 @@ HASensor *ram = nullptr; HABinarySensor *btnleft = nullptr; HABinarySensor *btnmid = nullptr; HABinarySensor *btnright = nullptr; +HABinarySensor *update = nullptr; +HAButton *doUpdate = nullptr; // The getter for the instantiated singleton instance MQTTManager_ &MQTTManager_::getInstance() @@ -61,6 +64,11 @@ void onButtonCommand(HAButton *sender) { DisplayManager.previousApp(); } + else if (sender == doUpdate) + { + if (UPDATE_AVAILABLE) + Updater.updateFirmware(); + } } void onSwitchCommand(bool state, HASwitch *sender) @@ -183,6 +191,12 @@ void onMqttMessage(const char *topic, const uint8_t *payload, uint16_t length) DisplayManager.previousApp(); return; } + if (strTopic == MQTT_PREFIX + "/doupdate") + { + if (UPDATE_AVAILABLE) + Updater.updateFirmware(); + return; + } else if (strTopic.startsWith(MQTT_PREFIX + "/custom")) { @@ -212,6 +226,7 @@ void onMqttConnected() "/settings", "/previousapp", "/nextapp", + "/doupdate", "/nextapp", "/apps"}; for (const char *topic : topics) @@ -222,7 +237,6 @@ void onMqttConnected() Serial.println(F("MQTT Connected")); } - void connect() { mqtt.onMessage(onMqttMessage); @@ -241,7 +255,7 @@ void connect() } char matID[40], briID[40]; -char btnAID[40], btnBID[40], btnCID[40], appID[40], tempID[40], humID[40], luxID[40], verID[40], ramID[40], upID[40], sigID[40], btnLID[40], btnMID[40], btnRID[40], transID[40]; +char btnAID[40], btnBID[40], btnCID[40], appID[40], tempID[40], humID[40], luxID[40], verID[40], ramID[40], upID[40], sigID[40], btnLID[40], btnMID[40], btnRID[40], transID[40], updateID[40], doUpdateID[40]; #ifdef ULANZI char batID[40]; #endif @@ -299,6 +313,12 @@ void MQTTManager_::setup() dismiss->setIcon(HAbtnaIcon); dismiss->setName(HAbtnaName); + sprintf(doUpdateID, HAdoUpID, macStr); + doUpdate = new HAButton(doUpdateID); + doUpdate->setIcon(HAdoUpIcon); + doUpdate->setName(HAdoUpName); + doUpdate->onCommand(onButtonCommand); + sprintf(transID, HAtransID, macStr); transition = new HASwitch(transID); transition->setIcon(HAtransIcon); @@ -373,6 +393,12 @@ void MQTTManager_::setup() btnleft = new HABinarySensor(btnLID); btnleft->setName(HAbtnLName); + sprintf(updateID, HAupdateID, macStr); + update = new HABinarySensor(updateID); + update->setIcon(HAupdateIcon); + update->setName(HAupdateName); + update->setDeviceClass(HAupdateClass); + sprintf(btnMID, HAbtnMID, macStr); btnmid = new HABinarySensor(btnMID); btnmid->setName(HAbtnMName); @@ -456,6 +482,8 @@ void MQTTManager_::sendStats() uptime->setValue(PeripheryManager.readUptime()); version->setValue(VERSION); transition->setState(AUTO_TRANSITION, false); + + update->setState(UPDATE_AVAILABLE, false); } else { diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index 1abb672..d64d25d 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -6,6 +6,7 @@ #include #include #include +#include String menuText; int menuSelection; @@ -377,9 +378,9 @@ void MenuManager_::selectButton() #endif case 13: - if (FirmwareVersionCheck()) + if (Updater.checkUpdate(true)) { - updateFirmware(); + Updater.updateFirmware(); } break; default: diff --git a/src/ServerManager.cpp b/src/ServerManager.cpp index f34edab..3275051 100644 --- a/src/ServerManager.cpp +++ b/src/ServerManager.cpp @@ -9,6 +9,7 @@ #include #include #include "DisplayManager.h" +#include "Updater.h" WebServer server(80); FSWebServer mws(LittleFS, server); @@ -37,7 +38,6 @@ void saveHandler() webRequest->send(200); } - void ServerManager_::setup() { if (!local_IP.fromString(NET_IP) || !gateway.fromString(NET_GW) || !subnet.fromString(NET_SN) || !primaryDNS.fromString(NET_PDNS) || !secondaryDNS.fromString(NET_SDNS)) @@ -53,7 +53,7 @@ void ServerManager_::setup() if (isConnected) { - + mws.addOptionBox("Network"); mws.addOption("Static IP", NET_STATIC); mws.addOption("Local IP", NET_IP); @@ -97,6 +97,9 @@ void ServerManager_::setup() { DisplayManager.generateCustomPage(mws.webserver->arg("name"),mws.webserver->arg("plain").c_str()); mws.webserver->send(200,"OK"); }); mws.addHandler("/api/stats", HTTP_GET, []() { mws.webserver->sendContent(DisplayManager.getStat()); }); + mws.addHandler("/api/doupdate", HTTP_POST, []() + { if (UPDATE_AVAILABLE) + Updater.updateFirmware(); mws.webserver->send(200,"OK"); }); Serial.println("Webserver loaded"); } mws.addHandler("/version", HTTP_GET, versionHandler); diff --git a/src/Updater.cpp b/src/Updater.cpp new file mode 100644 index 0000000..4b6c2dc --- /dev/null +++ b/src/Updater.cpp @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include +#include "cert.h" +#include "DisplayManager.h" +#include +#include "Globals.h" + +#define URL_fw_Version "https://raw.githubusercontent.com/Blueforcer/awtrix-light/main/version" +#define URL_fw_Bin "https://raw.githubusercontent.com/Blueforcer/awtrix-light/main/docs/flasher/firmware/firmware.bin" + +Ticker UpdateTicker; + +// The getter for the instantiated singleton instance +Updater_ &Updater_::getInstance() +{ + static Updater_ instance; + return instance; +} + +// Initialize the global shared instance +Updater_ &Updater = Updater.getInstance(); + +void update_started() +{ +} + +void update_finished() +{ +} + +void update_progress(int cur, int total) +{ + DisplayManager.drawProgressBar(cur, total); +} + +void update_error(int err) +{ + DisplayManager.clear(); + DisplayManager.printText(0, 6, "FAIL", true, true); + DisplayManager.show(); +} + +void Updater_::updateFirmware() +{ + WiFiClientSecure client; + client.setCACert(rootCACertificate); + + httpUpdate.onStart(update_started); + httpUpdate.onEnd(update_finished); + httpUpdate.onProgress(update_progress); + httpUpdate.onError(update_error); + + t_httpUpdate_return ret = httpUpdate.update(client, URL_fw_Bin); + switch (ret) + { + case HTTP_UPDATE_FAILED: + Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s\n", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); + break; + + case HTTP_UPDATE_NO_UPDATES: + Serial.println("HTTP_UPDATE_NO_UPDATES"); + break; + + case HTTP_UPDATE_OK: + Serial.println("HTTP_UPDATE_OK"); + break; + } +} + +bool Updater_::checkUpdate(bool withScreen) +{ + if (withScreen) + { + DisplayManager.clear(); + DisplayManager.printText(0, 6, "CHECK", true, true); + DisplayManager.show(); + } + + String payload; + int httpCode; + String fwurl = ""; + fwurl += URL_fw_Version; + fwurl += "?"; + fwurl += String(rand()); + Serial.println(fwurl); + WiFiClientSecure *client = new WiFiClientSecure; + + if (client) + { + client->setCACert(rootCACertificate); + HTTPClient https; + + if (https.begin(*client, fwurl)) + { // HTTPS + Serial.print("[HTTPS] GET...\n"); + // start connection and send HTTP header + delay(100); + httpCode = https.GET(); + delay(100); + if (httpCode == HTTP_CODE_OK) // if version received + { + payload = https.getString(); // save received version + } + else + { + Serial.print("error in downloading version file:"); + Serial.println(httpCode); + } + https.end(); + } + delete client; + } + + if (httpCode == HTTP_CODE_OK) // if version received + { + payload.trim(); + if (payload.equals(VERSION)) + { + UPDATE_AVAILABLE = false; + Serial.printf("\nDevice already on latest firmware version: %s\n", VERSION); + if (withScreen) + { + DisplayManager.clear(); + DisplayManager.printText(0, 6, "NO UP :(", true, true); + DisplayManager.show(); + delay(1000); + } + return 0; + } + else + { + UPDATE_AVAILABLE = true; + return 1; + } + } + UPDATE_AVAILABLE = false; + return 0; +} + +void checkUpdateNoReturn() +{ + Serial.println("Check Update"); + Updater.getInstance().checkUpdate(false); +} + +void Updater_::setup() +{ + UpdateTicker.attach(3600, checkUpdateNoReturn); +} diff --git a/src/main.cpp b/src/main.cpp index 18b845a..0a2fa69 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,6 +36,7 @@ #include "MQTTManager.h" #include "ServerManager.h" #include "Globals.h" +#include "Updater.h" TaskHandle_t taskHandle; volatile bool StopTask = false; @@ -58,7 +59,7 @@ void BootAnimation(void *parameter) void setup() { PeripheryManager.setup(); - delay(500); + delay(1000); Serial.begin(9600); loadSettings(); ServerManager.loadSettings(); @@ -73,6 +74,8 @@ void setup() { MQTTManager.setup(); DisplayManager.loadNativeApps(); + Updater.setup(); + Updater.checkUpdate(false); StopTask = true; float x = 4; while (x >= -85) @@ -80,6 +83,7 @@ void setup() DisplayManager.HSVtext(x, 6, ("AWTRIX " + ServerManager.myIP.toString()).c_str(), true); x -= 0.18; } + } else { @@ -98,7 +102,6 @@ void loop() PeripheryManager.tick(); if (ServerManager.isConnected) { - MQTTManager.tick(); } } diff --git a/src/updater.h b/src/updater.h index 2bf31c0..923e3c8 100644 --- a/src/updater.h +++ b/src/updater.h @@ -1,122 +1,29 @@ -#include -#include -#include -#include -#include "cert.h" -#include "DisplayManager.h" +#ifndef UPDATER_h +#define UPDATER_h -#define URL_fw_Version "https://raw.githubusercontent.com/Blueforcer/awtrix-light/main/version" -#define URL_fw_Bin "https://raw.githubusercontent.com/Blueforcer/awtrix-light/main/docs/flasher/firmware/firmware.bin" +#include +#include +#ifdef ULANZI + #include "Adafruit_SHT31.h" +#else + #include "Adafruit_BME280.h" + #include "SoftwareSerial.h" + #include +#endif -void update_started() +class Updater_ { -} - -void update_finished() -{ - -} - -void update_progress(int cur, int total) -{ - DisplayManager.drawProgressBar(cur, total); -} - -void update_error(int err) -{ - DisplayManager.clear(); - DisplayManager.printText(0, 6, "FAIL", true, true); - DisplayManager.show(); -} - -void updateFirmware() -{ - WiFiClientSecure client; - client.setCACert(rootCACertificate); - - httpUpdate.onStart(update_started); - httpUpdate.onEnd(update_finished); - httpUpdate.onProgress(update_progress); - httpUpdate.onError(update_error); - - t_httpUpdate_return ret = httpUpdate.update(client, URL_fw_Bin); - switch (ret) - { - case HTTP_UPDATE_FAILED: - Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s\n", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); - break; - - case HTTP_UPDATE_NO_UPDATES: - Serial.println("HTTP_UPDATE_NO_UPDATES"); - break; - - case HTTP_UPDATE_OK: - Serial.println("HTTP_UPDATE_OK"); - break; - } -} +private: + Updater_() = default; -bool FirmwareVersionCheck() -{ - DisplayManager.clear(); - DisplayManager.printText(0, 6, "CHECK", true, true); - DisplayManager.show(); - String payload; - int httpCode; - String fwurl = ""; - fwurl += URL_fw_Version; - fwurl += "?"; - fwurl += String(rand()); - Serial.println(fwurl); - WiFiClientSecure *client = new WiFiClientSecure; +public: + static Updater_ &getInstance(); + void setup(); + bool checkUpdate(bool); + void updateFirmware(); +}; - if (client) - { - client->setCACert(rootCACertificate); - HTTPClient https; - - if (https.begin(*client, fwurl)) - { // HTTPS - Serial.print("[HTTPS] GET...\n"); - // start connection and send HTTP header - delay(100); - httpCode = https.GET(); - delay(100); - if (httpCode == HTTP_CODE_OK) // if version received - { - payload = https.getString(); // save received version - } - else - { - Serial.print("error in downloading version file:"); - Serial.println(httpCode); - } - https.end(); - } - delete client; - } - - if (httpCode == HTTP_CODE_OK) // if version received - { - payload.trim(); - if (payload.equals(VERSION)) - { - Serial.printf("\nDevice already on latest firmware version:%s\n", VERSION); - DisplayManager.clear(); - DisplayManager.printText(0, 6, "NO UP :(", true, true); - DisplayManager.show(); - delay(1000); - return 0; - } - else - { - Serial.println(payload); - Serial.println("New firmware detected"); - DisplayManager.printText(0, 6, payload.c_str(), true, true); - return 1; - } - } - return 0; -} +extern Updater_ &Updater; +#endif