Refactor LedHandler to a class

This commit is contained in:
Djuri 2025-01-05 21:19:28 +01:00
parent ac13098824
commit d023643090
Signed by: djuri
GPG key ID: 61B9B2DDE5AA3AC1
11 changed files with 931 additions and 935 deletions

View file

@ -1,4 +1,5 @@
#include "block_notify.hpp" #include "block_notify.hpp"
#include "led_handler.hpp"
char *wsServer; char *wsServer;
esp_websocket_client_handle_t blockNotifyClient = NULL; esp_websocket_client_handle_t blockNotifyClient = NULL;
@ -217,7 +218,7 @@ void processNewBlock(uint32_t newBlockHeight) {
if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD)) if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD))
{ {
vTaskDelay(pdMS_TO_TICKS(250)); // Wait until screens are updated vTaskDelay(pdMS_TO_TICKS(250)); // Wait until screens are updated
queueLedEffect(LED_FLASH_BLOCK_NOTIFY); getLedHandler().queueEffect(LED_FLASH_BLOCK_NOTIFY);
} }
} }
} }

View file

@ -1,4 +1,5 @@
#include "config.hpp" #include "config.hpp"
#include "led_handler.hpp"
#define MAX_ATTEMPTS_WIFI_CONNECTION 20 #define MAX_ATTEMPTS_WIFI_CONNECTION 20
@ -50,7 +51,8 @@ void setup()
setupDisplays(); setupDisplays();
if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER)) if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER))
{ {
queueLedEffect(LED_POWER_TEST); auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_POWER_TEST);
} }
{ {
std::lock_guard<std::mutex> lockMcp(mcpMutex); std::lock_guard<std::mutex> lockMcp(mcpMutex);
@ -60,7 +62,8 @@ void setup()
preferences.remove("txPower"); preferences.remove("txPower");
WiFi.eraseAP(); WiFi.eraseAP();
queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_EFFECT_WIFI_ERASE_SETTINGS);
} }
} }
@ -76,7 +79,8 @@ void setup()
else if (mcp1.read1(1) == LOW) else if (mcp1.read1(1) == LOW)
{ {
preferences.clear(); preferences.clear();
queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_EFFECT_WIFI_ERASE_SETTINGS);
nvs_flash_erase(); nvs_flash_erase();
delay(1000); delay(1000);
@ -116,13 +120,13 @@ void setup()
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT
if (!preferences.getBool("flAlwaysOn", DEFAULT_FL_ALWAYS_ON)) if (!preferences.getBool("flAlwaysOn", DEFAULT_FL_ALWAYS_ON))
{ {
frontlightFadeOutAll(preferences.getUInt("flEffectDelay"), true); auto& ledHandler = getLedHandler();
ledHandler.frontlightFadeOutAll(preferences.getUInt("flEffectDelay"), true);
flArray.allOFF(); flArray.allOFF();
} }
#endif #endif
forceFullRefresh(); forceFullRefresh();
} }
void setupWifi() void setupWifi()
@ -144,7 +148,8 @@ void setupWifi()
// if (!preferences.getBool("wifiConfigured", DEFAULT_WIFI_CONFIGURED) // if (!preferences.getBool("wifiConfigured", DEFAULT_WIFI_CONFIGURED)
{ {
queueLedEffect(LED_EFFECT_WIFI_WAIT_FOR_CONFIG); auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_EFFECT_WIFI_WAIT_FOR_CONFIG);
bool buttonPress = false; bool buttonPress = false;
{ {
@ -279,7 +284,8 @@ void syncTime()
while (!getLocalTime(&timeinfo)) while (!getLocalTime(&timeinfo))
{ {
queueLedEffect(LED_EFFECT_CONFIGURING); auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_EFFECT_CONFIGURING);
configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0, configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0,
NTP_SERVER); NTP_SERVER);
delay(500); delay(500);
@ -420,13 +426,14 @@ void setupTimers()
void finishSetup() void finishSetup()
{ {
auto& ledHandler = getLedHandler();
if (preferences.getBool("ledStatus", DEFAULT_LED_STATUS)) if (preferences.getBool("ledStatus", DEFAULT_LED_STATUS))
{ {
restoreLedState(); ledHandler.restoreLedState();
} }
else else
{ {
clearLeds(); ledHandler.clear();
} }
} }
@ -475,22 +482,9 @@ void setupHardware()
Serial.println(F("Error loading WebUI")); Serial.println(F("Error loading WebUI"));
} }
// { // Initialize LED handler
// File f = LittleFS.open("/qr.txt", "w"); auto& ledHandler = getLedHandler();
ledHandler.setup();
// if(f) {
// if (f.print("Hello")) {
// Serial.println(F("Written QR to FS"));
// Serial.printf("\nLittleFS free: %zu\n", LittleFS.totalBytes() - LittleFS.usedBytes());
// }
// } else {
// Serial.println(F("Can't write QR to FS"));
// }
// f.close();
// }
setupLeds();
WiFi.setHostname(getMyHostname().c_str()); WiFi.setHostname(getMyHostname().c_str());
if (!psramInit()) if (!psramInit())
@ -548,7 +542,8 @@ void setupHardware()
#endif #endif
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT
setupFrontlight(); // Initialize frontlight through LedHandler
ledHandler.initializeFrontlight();
Wire.beginTransmission(0x5C); Wire.beginTransmission(0x5C);
byte error = Wire.endTransmission(); byte error = Wire.endTransmission();
@ -570,6 +565,7 @@ void setupHardware()
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
{ {
static bool first_connect = true; static bool first_connect = true;
auto& ledHandler = getLedHandler(); // Get ledHandler reference once at the start
Serial.printf("[WiFi-event] event: %d\n", event); Serial.printf("[WiFi-event] event: %d\n", event);
@ -595,7 +591,7 @@ void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
if (!first_connect) if (!first_connect)
{ {
Serial.println(F("Disconnected from WiFi access point")); Serial.println(F("Disconnected from WiFi access point"));
queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); ledHandler.queueEffect(LED_EFFECT_WIFI_CONNECT_ERROR);
uint8_t reason = info.wifi_sta_disconnected.reason; uint8_t reason = info.wifi_sta_disconnected.reason;
if (reason) if (reason)
Serial.printf("Disconnect reason: %s, ", Serial.printf("Disconnect reason: %s, ",
@ -611,13 +607,13 @@ void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
Serial.print("Obtained IP address: "); Serial.print("Obtained IP address: ");
Serial.println(WiFi.localIP()); Serial.println(WiFi.localIP());
if (!first_connect) if (!first_connect)
queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS); ledHandler.queueEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS);
first_connect = false; first_connect = false;
break; break;
} }
case ARDUINO_EVENT_WIFI_STA_LOST_IP: case ARDUINO_EVENT_WIFI_STA_LOST_IP:
Serial.println(F("Lost IP address and IP address is reset to 0")); Serial.println(F("Lost IP address and IP address is reset to 0"));
queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR); ledHandler.queueEffect(LED_EFFECT_WIFI_CONNECT_ERROR);
WiFi.reconnect(); WiFi.reconnect();
break; break;
case ARDUINO_EVENT_WIFI_AP_START: case ARDUINO_EVENT_WIFI_AP_START:
@ -667,30 +663,6 @@ uint getLastTimeSync()
} }
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT
void setupFrontlight()
{
if (!flArray.begin(PCA9685_MODE1_AUTOINCR | PCA9685_MODE1_ALLCALL, PCA9685_MODE2_TOTEMPOLE))
{
Serial.println(F("FL driver error"));
return;
}
flArray.setFrequency(200);
Serial.println(F("FL driver active"));
if (!preferences.isKey("flMaxBrightness"))
{
preferences.putUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS);
}
if (!preferences.isKey("flEffectDelay"))
{
preferences.putUInt("flEffectDelay", DEFAULT_FL_EFFECT_DELAY);
}
if (!preferences.isKey("flFlashOnUpd"))
{
preferences.putBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE);
}
}
float getLightLevel() float getLightLevel()
{ {

View file

@ -52,10 +52,10 @@ void setupTimers();
void finishSetup(); void finishSetup();
void setupMcp(); void setupMcp();
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT
void setupFrontlight(); extern BH1750 bh1750;
extern bool hasLuxSensor;
float getLightLevel(); float getLightLevel();
bool hasLightLevel(); bool hasLightLevel();
extern PCA9685 flArray;
#endif #endif
String getMyHostname(); String getMyHostname();
@ -98,6 +98,10 @@ extern MCP23017 mcp1;
extern MCP23017 mcp2; extern MCP23017 mcp2;
#endif #endif
#ifdef HAS_FRONTLIGHT
extern PCA9685 flArray;
#endif
// Expose DataSourceType enum // Expose DataSourceType enum
extern DataSourceType getDataSource(); extern DataSourceType getDataSource();
extern void setDataSource(DataSourceType source); extern void setDataSource(DataSourceType source);

View file

@ -140,8 +140,6 @@ const GFXfont *FONT_SATSYMBOL;
std::mutex epdUpdateMutex; std::mutex epdUpdateMutex;
std::mutex epdMutex[NUM_SCREENS]; std::mutex epdMutex[NUM_SCREENS];
uint8_t qrcode[800];
#ifdef IS_BTCLOCK_V8 #ifdef IS_BTCLOCK_V8
#define EPD_TASK_STACK_SIZE 4096 #define EPD_TASK_STACK_SIZE 4096
#else #else
@ -159,8 +157,6 @@ void forceFullRefresh()
} }
} }
GFXfont font90;
void loadFonts(const String& fontName) { void loadFonts(const String& fontName) {
if (fontName == FontNames::ANTONIO) { if (fontName == FontNames::ANTONIO) {
// Load Antonio fonts // Load Antonio fonts
@ -628,35 +624,45 @@ bool renderIcon(const uint dispNum, const String &text, bool partial)
void renderQr(const uint dispNum, const String &text, bool partial) void renderQr(const uint dispNum, const String &text, bool partial)
{ {
#ifdef USE_QR #ifdef USE_QR
// Dynamically allocate QR buffer
uint8_t* qrcode = (uint8_t*)malloc(qrcodegen_BUFFER_LEN_MAX);
if (!qrcode) {
log_e("Failed to allocate QR buffer");
return;
}
uint8_t tempBuffer[800]; uint8_t tempBuffer[800];
bool ok = qrcodegen_encodeText( bool ok = qrcodegen_encodeText(
text.substring(2).c_str(), tempBuffer, qrcode, qrcodegen_Ecc_LOW, text.substring(2).c_str(), tempBuffer, qrcode, qrcodegen_Ecc_LOW,
qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true); qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true);
const int size = qrcodegen_getSize(qrcode); if (ok) {
const int size = qrcodegen_getSize(qrcode);
const int padding = floor(float(displays[dispNum].width() - (size * 4)) / 2);
const int paddingY =
floor(float(displays[dispNum].height() - (size * 4)) / 2);
displays[dispNum].setRotation(2);
const int padding = floor(float(displays[dispNum].width() - (size * 4)) / 2); displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(),
const int paddingY = displays[dispNum].height());
floor(float(displays[dispNum].height() - (size * 4)) / 2); displays[dispNum].fillScreen(GxEPD_WHITE);
displays[dispNum].setRotation(2); const int border = 0;
displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), for (int y = -border; y < size * 4 + border; y++)
displays[dispNum].height());
displays[dispNum].fillScreen(GxEPD_WHITE);
const int border = 0;
for (int y = -border; y < size * 4 + border; y++)
{
for (int x = -border; x < size * 4 + border; x++)
{ {
displays[dispNum].drawPixel( for (int x = -border; x < size * 4 + border; x++)
padding + x, paddingY + y, {
qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) displays[dispNum].drawPixel(
? GxEPD_BLACK padding + x, paddingY + y,
: GxEPD_WHITE); qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4))
? GxEPD_BLACK
: GxEPD_WHITE);
}
} }
} }
// Free the buffer after we're done
free(qrcode);
#endif #endif
} }

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,7 @@
#include <Arduino.h> #include <Arduino.h>
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/task.h> #include <freertos/task.h>
#include <memory>
#include "lib/shared.hpp" #include "lib/shared.hpp"
#include "lib/webserver.hpp" #include "lib/webserver.hpp"
@ -15,12 +16,11 @@
#define NEOPIXEL_COUNT 4 #define NEOPIXEL_COUNT 4
#endif #endif
// LED effect constants
const int LED_FLASH_ERROR = 0; const int LED_FLASH_ERROR = 0;
const int LED_FLASH_SUCCESS = 1; const int LED_FLASH_SUCCESS = 1;
const int LED_FLASH_UPDATE = 2; const int LED_FLASH_UPDATE = 2;
const int LED_EFFECT_CONFIGURING = 10; const int LED_EFFECT_CONFIGURING = 10;
const int LED_FLASH_BLOCK_NOTIFY = 4; const int LED_FLASH_BLOCK_NOTIFY = 4;
const int LED_EFFECT_START_TIMER = 5; const int LED_EFFECT_START_TIMER = 5;
const int LED_EFFECT_PAUSE_TIMER = 6; const int LED_EFFECT_PAUSE_TIMER = 6;
@ -30,61 +30,15 @@ const int LED_EFFECT_WIFI_CONNECTING = 101;
const int LED_EFFECT_WIFI_CONNECT_ERROR = 102; const int LED_EFFECT_WIFI_CONNECT_ERROR = 102;
const int LED_EFFECT_WIFI_CONNECT_SUCCESS = 103; const int LED_EFFECT_WIFI_CONNECT_SUCCESS = 103;
const int LED_EFFECT_WIFI_ERASE_SETTINGS = 104; const int LED_EFFECT_WIFI_ERASE_SETTINGS = 104;
const int LED_PROGRESS_25 = 200; const int LED_PROGRESS_25 = 200;
const int LED_PROGRESS_50 = 201; const int LED_PROGRESS_50 = 201;
const int LED_PROGRESS_75 = 202; const int LED_PROGRESS_75 = 202;
const int LED_PROGRESS_100 = 203; const int LED_PROGRESS_100 = 203;
const int LED_DATA_PRICE_ERROR = 300; const int LED_DATA_PRICE_ERROR = 300;
const int LED_DATA_BLOCK_ERROR = 301; const int LED_DATA_BLOCK_ERROR = 301;
const int LED_EFFECT_NOSTR_ZAP = 400; const int LED_EFFECT_NOSTR_ZAP = 400;
const int LED_FLASH_IDENTIFY = 990; const int LED_FLASH_IDENTIFY = 990;
const int LED_POWER_TEST = 999; const int LED_POWER_TEST = 999;
extern TaskHandle_t ledTaskHandle;
extern Adafruit_NeoPixel pixels;
void ledTask(void *pvParameters);
void setupLeds();
void setupLedTask();
void blinkDelay(int d, int times);
void blinkDelayColor(int d, int times, uint r, uint g, uint b);
void blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2);
void clearLeds();
void saveLedState();
void restoreLedState();
QueueHandle_t getLedTaskQueue();
bool queueLedEffect(uint effect);
void setLights(int r, int g, int b);
void setLights(uint32_t color);
void ledRainbow(int wait);
void ledTheaterChaseRainbow(int wait);
void ledTheaterChase(uint32_t color, int wait);
Adafruit_NeoPixel getPixels();
void lightningStrike();
#ifdef HAS_FRONTLIGHT
void frontlightFlash(int flDelayTime);
void frontlightFadeInAll();
void frontlightFadeOutAll();
void frontlightFadeIn(uint num);
void frontlightFadeOut(uint num);
std::vector<uint16_t> frontlightGetStatus();
void frontlightSetBrightness(uint brightness);
bool frontlightIsOn();
void frontlightFadeInAll(int flDelayTime);
void frontlightFadeInAll(int flDelayTime, bool staggered);
void frontlightFadeOutAll(int flDelayTime);
void frontlightFadeOutAll(int flDelayTime, bool staggered);
void frontlightFadeIn(uint num, int flDelayTime);
void frontlightFadeOut(uint num, int flDelayTime);
#endif
// Do Not Disturb mode settings // Do Not Disturb mode settings
struct DNDTimeRange { struct DNDTimeRange {
@ -94,12 +48,88 @@ struct DNDTimeRange {
uint8_t endMinute; uint8_t endMinute;
}; };
extern bool dndEnabled; class LedHandler {
extern bool dndTimeBasedEnabled; public:
extern DNDTimeRange dndTimeRange; static LedHandler& getInstance();
// Delete copy constructor and assignment operator
LedHandler(const LedHandler&) = delete;
LedHandler& operator=(const LedHandler&) = delete;
void setDNDEnabled(bool enabled); void setup();
void setDNDTimeBasedEnabled(bool enabled); void setupTask();
void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute); bool queueEffect(uint effect);
bool isDNDActive(); void clear();
bool isTimeInDNDRange(uint8_t hour, uint8_t minute); void setLights(int r, int g, int b);
void setLights(uint32_t color);
void saveLedState();
void restoreLedState();
QueueHandle_t getTaskQueue() const { return ledTaskQueue; }
Adafruit_NeoPixel& getPixels() { return pixels; }
// DND methods
void setDNDEnabled(bool enabled);
void setDNDTimeBasedEnabled(bool enabled);
void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute);
bool isDNDActive() const;
bool isTimeInDNDRange(uint8_t hour, uint8_t minute) const;
// DND getters
bool isDNDEnabled() const { return dndEnabled; }
bool isDNDTimeBasedEnabled() const { return dndTimeBasedEnabled; }
uint8_t getDNDStartHour() const { return dndTimeRange.startHour; }
uint8_t getDNDStartMinute() const { return dndTimeRange.startMinute; }
uint8_t getDNDEndHour() const { return dndTimeRange.endHour; }
uint8_t getDNDEndMinute() const { return dndTimeRange.endMinute; }
// Effect methods
void rainbow(int wait);
void theaterChase(uint32_t color, int wait);
void theaterChaseRainbow(int wait);
void lightningStrike();
void blinkDelay(int d, int times);
void blinkDelayColor(int d, int times, uint r, uint g, uint b);
void blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2);
#ifdef HAS_FRONTLIGHT
void frontlightFlash(int flDelayTime);
void frontlightFadeInAll();
void frontlightFadeOutAll();
void frontlightFadeIn(uint num);
void frontlightFadeOut(uint num);
std::vector<uint16_t> frontlightGetStatus();
void frontlightSetBrightness(uint brightness);
bool frontlightIsOn() const { return frontlightOn; }
void frontlightFadeInAll(int flDelayTime, bool staggered = false);
void frontlightFadeOutAll(int flDelayTime, bool staggered = false);
void frontlightFadeIn(uint num, int flDelayTime);
void frontlightFadeOut(uint num, int flDelayTime);
void initializeFrontlight();
#endif
private:
LedHandler(); // Private constructor for singleton
void loadDNDSettings();
static void ledTask(void* pvParameters);
Adafruit_NeoPixel pixels;
TaskHandle_t ledTaskHandle;
QueueHandle_t ledTaskQueue;
uint ledTaskParams;
// DND members
bool dndEnabled;
bool dndTimeBasedEnabled;
DNDTimeRange dndTimeRange;
#ifdef HAS_FRONTLIGHT
static constexpr uint16_t FL_FADE_STEP = 25;
bool frontlightOn;
bool flInTransition;
#endif
};
// Global accessor function
inline LedHandler& getLedHandler() {
return LedHandler::getInstance();
}

View file

@ -1,4 +1,5 @@
#include "nostr_notify.hpp" #include "nostr_notify.hpp"
#include "led_handler.hpp"
std::vector<nostr::NostrPool *> pools; std::vector<nostr::NostrPool *> pools;
nostr::Transport *transport; nostr::Transport *transport;
@ -286,7 +287,7 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event)
vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250)); vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250));
if (preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP)) if (preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP))
{ {
queueLedEffect(LED_EFFECT_NOSTR_ZAP); getLedHandler().queueEffect(LED_EFFECT_NOSTR_ZAP);
} }
if (timerPeriod > 0) if (timerPeriod > 0)
{ {

View file

@ -1,4 +1,5 @@
#include "ota.hpp" #include "ota.hpp"
#include "led_handler.hpp"
TaskHandle_t taskOtaHandle = NULL; TaskHandle_t taskOtaHandle = NULL;
bool isOtaUpdating = false; bool isOtaUpdating = false;
@ -31,6 +32,9 @@ void setupOTA()
void onOTAProgress(unsigned int progress, unsigned int total) void onOTAProgress(unsigned int progress, unsigned int total)
{ {
uint percentage = progress / (total / 100); uint percentage = progress / (total / 100);
auto& ledHandler = getLedHandler();
auto& pixels = ledHandler.getPixels();
pixels.fill(pixels.Color(0, 255, 0)); pixels.fill(pixels.Color(0, 255, 0));
if (percentage < 100) if (percentage < 100)
{ {
@ -84,15 +88,15 @@ void handleOTATask(void *parameter)
{ {
if (msg.updateType == UPDATE_ALL) { if (msg.updateType == UPDATE_ALL) {
isOtaUpdating = true; isOtaUpdating = true;
queueLedEffect(LED_FLASH_UPDATE); getLedHandler().queueEffect(LED_FLASH_UPDATE);
int resultWebUi = downloadUpdateHandler(UPDATE_WEBUI); int resultWebUi = downloadUpdateHandler(UPDATE_WEBUI);
queueLedEffect(LED_FLASH_UPDATE); getLedHandler().queueEffect(LED_FLASH_UPDATE);
int resultFw = downloadUpdateHandler(UPDATE_FIRMWARE); int resultFw = downloadUpdateHandler(UPDATE_FIRMWARE);
if (resultWebUi == 0 && resultFw == 0) { if (resultWebUi == 0 && resultFw == 0) {
ESP.restart(); ESP.restart();
} else { } else {
queueLedEffect(LED_FLASH_ERROR); getLedHandler().queueEffect(LED_FLASH_ERROR);
vTaskDelay(pdMS_TO_TICKS(3000)); vTaskDelay(pdMS_TO_TICKS(3000));
ESP.restart(); ESP.restart();
} }

View file

@ -1,4 +1,5 @@
#include "timers.hpp" #include "timers.hpp"
#include "led_handler.hpp"
esp_timer_handle_t screenRotateTimer; esp_timer_handle_t screenRotateTimer;
esp_timer_handle_t minuteTimer; esp_timer_handle_t minuteTimer;
@ -49,11 +50,11 @@ void setTimerActive(bool status) {
if (status) { if (status) {
esp_timer_start_periodic(screenRotateTimer, esp_timer_start_periodic(screenRotateTimer,
getTimerSeconds() * usPerSecond); getTimerSeconds() * usPerSecond);
queueLedEffect(LED_EFFECT_START_TIMER); getLedHandler().queueEffect(LED_EFFECT_START_TIMER);
preferences.putBool("timerActive", true); preferences.putBool("timerActive", true);
} else { } else {
esp_timer_stop(screenRotateTimer); esp_timer_stop(screenRotateTimer);
queueLedEffect(LED_EFFECT_PAUSE_TIMER); getLedHandler().queueEffect(LED_EFFECT_PAUSE_TIMER);
preferences.putBool("timerActive", false); preferences.putBool("timerActive", false);
} }

View file

@ -1,4 +1,6 @@
#include "webserver.hpp" #include "webserver.hpp"
#include "lib/led_handler.hpp"
#include "lib/shared.hpp"
static const char* JSON_CONTENT = "application/json"; static const char* JSON_CONTENT = "application/json";
@ -231,6 +233,7 @@ void asyncFirmwareUpdateHandler(AsyncWebServerRequest *request, String filename,
JsonDocument getStatusObject() JsonDocument getStatusObject()
{ {
auto& ledHandler = getLedHandler();
JsonDocument root; JsonDocument root;
root["currentScreen"] = ScreenHandler::getCurrentScreen(); root["currentScreen"] = ScreenHandler::getCurrentScreen();
@ -238,25 +241,21 @@ JsonDocument getStatusObject()
root["timerRunning"] = isTimerActive(); root["timerRunning"] = isTimerActive();
root["isOTAUpdating"] = getIsOTAUpdating(); root["isOTAUpdating"] = getIsOTAUpdating();
root["espUptime"] = esp_timer_get_time() / 1000000; root["espUptime"] = esp_timer_get_time() / 1000000;
// root["currentPrice"] = getPrice();
// root["currentBlockHeight"] = getBlockHeight();
root["espFreeHeap"] = ESP.getFreeHeap(); root["espFreeHeap"] = ESP.getFreeHeap();
root["espHeapSize"] = ESP.getHeapSize(); root["espHeapSize"] = ESP.getHeapSize();
// root["espFreePsram"] = ESP.getFreePsram();
// root["espPsramSize"] = ESP.getPsramSize();
JsonObject conStatus = root["connectionStatus"].to<JsonObject>(); JsonObject conStatus = root["connectionStatus"].to<JsonObject>();
conStatus["price"] = isPriceNotifyConnected(); conStatus["price"] = isPriceNotifyConnected();
conStatus["blocks"] = isBlockNotifyConnected(); conStatus["blocks"] = isBlockNotifyConnected();
conStatus["V2"] = V2Notify::isV2NotifyConnected(); conStatus["V2"] = V2Notify::isV2NotifyConnected();
conStatus["nostr"] = nostrConnected(); conStatus["nostr"] = nostrConnected();
root["rssi"] = WiFi.RSSI(); root["rssi"] = WiFi.RSSI();
root["currency"] = getCurrencyCode(ScreenHandler::getCurrentCurrency()); root["currency"] = getCurrencyCode(ScreenHandler::getCurrentCurrency());
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT
std::vector<uint16_t> statuses = frontlightGetStatus(); std::vector<uint16_t> statuses = ledHandler.frontlightGetStatus();
uint16_t arr[NUM_SCREENS]; uint16_t arr[NUM_SCREENS];
std::copy(statuses.begin(), statuses.end(), arr); std::copy(statuses.begin(), statuses.end(), arr);
@ -270,22 +269,24 @@ JsonDocument getStatusObject()
#endif #endif
// Add DND status // Add DND status
root["dnd"]["enabled"] = dndEnabled; root["dnd"]["enabled"] = ledHandler.isDNDEnabled();
root["dnd"]["timeBasedEnabled"] = dndTimeBasedEnabled; root["dnd"]["timeBasedEnabled"] = ledHandler.isDNDTimeBasedEnabled();
root["dnd"]["startTime"] = String(dndTimeRange.startHour) + ":" + root["dnd"]["startTime"] = String(ledHandler.getDNDStartHour()) + ":" +
(dndTimeRange.startMinute < 10 ? "0" : "") + String(dndTimeRange.startMinute); (ledHandler.getDNDStartMinute() < 10 ? "0" : "") + String(ledHandler.getDNDStartMinute());
root["dnd"]["endTime"] = String(dndTimeRange.endHour) + ":" + root["dnd"]["endTime"] = String(ledHandler.getDNDEndHour()) + ":" +
(dndTimeRange.endMinute < 10 ? "0" : "") + String(dndTimeRange.endMinute); (ledHandler.getDNDEndMinute() < 10 ? "0" : "") + String(ledHandler.getDNDEndMinute());
root["dnd"]["active"] = isDNDActive(); root["dnd"]["active"] = ledHandler.isDNDActive();
return root; return root;
} }
JsonDocument getLedStatusObject() JsonDocument getLedStatusObject()
{ {
auto& ledHandler = getLedHandler();
auto& pixels = ledHandler.getPixels();
JsonDocument root; JsonDocument root;
JsonArray colors = root["data"].to<JsonArray>(); JsonArray colors = root["data"].to<JsonArray>();
// Adafruit_NeoPixel pix = getPixels();
for (uint i = 0; i < pixels.numPixels(); i++) for (uint i = 0; i < pixels.numPixels(); i++)
{ {
@ -295,13 +296,7 @@ JsonDocument getLedStatusObject()
uint blue = pixColor & 0xFF; uint blue = pixColor & 0xFF;
char hexColor[8]; char hexColor[8];
snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X", red, green, blue); snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X", red, green, blue);
colors.add(hexColor);
JsonObject object = colors.add<JsonObject>();
object["red"] = red;
object["green"] = green;
object["blue"] = blue;
object["hex"] = hexColor;
} }
return root; return root;
@ -621,24 +616,26 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json)
// Handle DND settings // Handle DND settings
if (settings.containsKey("dnd")) { if (settings.containsKey("dnd")) {
JsonObject dndObj = settings["dnd"]; JsonObject dndObj = settings["dnd"];
auto& ledHandler = getLedHandler();
if (dndObj.containsKey("timeBasedEnabled")) { if (dndObj.containsKey("timeBasedEnabled")) {
setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as<bool>()); ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as<bool>());
} }
if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") && if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") &&
dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) { dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) {
setDNDTimeRange( ledHandler.setDNDTimeRange(
dndObj["startHour"].as<uint8_t>(), dndObj["startHour"].as<uint8_t>(),
dndObj["startMinute"].as<uint8_t>(), dndObj["startMinute"].as<uint8_t>(),
dndObj["endHour"].as<uint8_t>(), dndObj["endHour"].as<uint8_t>(),
dndObj["endMinute"].as<uint8_t>() dndObj["endMinute"].as<uint8_t>());
);
} }
} }
request->send(HTTP_OK); request->send(HTTP_OK);
if (settingsChanged) if (settingsChanged)
{ {
queueLedEffect(LED_FLASH_SUCCESS); auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_FLASH_SUCCESS);
} }
} }
@ -659,7 +656,8 @@ void onApiRestart(AsyncWebServerRequest *request)
void onApiIdentify(AsyncWebServerRequest *request) void onApiIdentify(AsyncWebServerRequest *request)
{ {
queueLedEffect(LED_FLASH_IDENTIFY); auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_FLASH_IDENTIFY);
request->send(HTTP_OK); request->send(HTTP_OK);
} }
@ -797,12 +795,13 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["ceDisableSSL"] = preferences.getBool("ceDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL); root["ceDisableSSL"] = preferences.getBool("ceDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL);
// Add DND settings // Add DND settings
root["dnd"]["enabled"] = dndEnabled; auto& ledHandler = getLedHandler();
root["dnd"]["timeBasedEnabled"] = dndTimeBasedEnabled; root["dnd"]["enabled"] = ledHandler.isDNDEnabled();
root["dnd"]["startHour"] = dndTimeRange.startHour; root["dnd"]["timeBasedEnabled"] = ledHandler.isDNDTimeBasedEnabled();
root["dnd"]["startMinute"] = dndTimeRange.startMinute; root["dnd"]["startHour"] = ledHandler.getDNDStartHour();
root["dnd"]["endHour"] = dndTimeRange.endHour; root["dnd"]["startMinute"] = ledHandler.getDNDStartMinute();
root["dnd"]["endMinute"] = dndTimeRange.endMinute; root["dnd"]["endHour"] = ledHandler.getDNDEndHour();
root["dnd"]["endMinute"] = ledHandler.getDNDEndMinute();
AsyncResponseStream *response = AsyncResponseStream *response =
request->beginResponseStream(JSON_CONTENT); request->beginResponseStream(JSON_CONTENT);
@ -929,7 +928,8 @@ void onApiRestartDataSources(AsyncWebServerRequest *request)
void onApiLightsOff(AsyncWebServerRequest *request) void onApiLightsOff(AsyncWebServerRequest *request)
{ {
setLights(0, 0, 0); auto& ledHandler = getLedHandler();
ledHandler.setLights(0, 0, 0);
request->send(HTTP_OK); request->send(HTTP_OK);
} }
@ -944,13 +944,15 @@ void onApiLightsSetColor(AsyncWebServerRequest *request)
if (rgbColor.compareTo("off") == 0) if (rgbColor.compareTo("off") == 0)
{ {
setLights(0, 0, 0); auto& ledHandler = getLedHandler();
ledHandler.setLights(0, 0, 0);
} }
else else
{ {
uint r, g, b; uint r, g, b;
sscanf(rgbColor.c_str(), "%02x%02x%02x", &r, &g, &b); sscanf(rgbColor.c_str(), "%02x%02x%02x", &r, &g, &b);
setLights(r, g, b); auto& ledHandler = getLedHandler();
ledHandler.setLights(r, g, b);
} }
JsonDocument doc; JsonDocument doc;
@ -968,6 +970,9 @@ void onApiLightsSetColor(AsyncWebServerRequest *request)
void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json) void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json)
{ {
auto& ledHandler = getLedHandler();
auto& pixels = ledHandler.getPixels();
JsonArray lights = json.as<JsonArray>(); JsonArray lights = json.as<JsonArray>();
if (lights.size() != pixels.numPixels()) if (lights.size() != pixels.numPixels())
@ -1016,7 +1021,7 @@ void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json)
} }
pixels.show(); pixels.show();
saveLedState(); ledHandler.saveLedState();
request->send(HTTP_OK); request->send(HTTP_OK);
} }
@ -1080,19 +1085,21 @@ void onApiShowCurrency(AsyncWebServerRequest *request)
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT
void onApiFrontlightOn(AsyncWebServerRequest *request) void onApiFrontlightOn(AsyncWebServerRequest *request)
{ {
frontlightFadeInAll(); auto& ledHandler = getLedHandler();
ledHandler.frontlightFadeInAll();
request->send(HTTP_OK); request->send(HTTP_OK);
} }
void onApiFrontlightStatus(AsyncWebServerRequest *request) void onApiFrontlightStatus(AsyncWebServerRequest *request)
{ {
auto& ledHandler = getLedHandler();
AsyncResponseStream *response = AsyncResponseStream *response =
request->beginResponseStream(JSON_CONTENT); request->beginResponseStream(JSON_CONTENT);
JsonDocument root; JsonDocument root;
std::vector<uint16_t> statuses = frontlightGetStatus(); std::vector<uint16_t> statuses = ledHandler.frontlightGetStatus();
uint16_t arr[NUM_SCREENS]; uint16_t arr[NUM_SCREENS];
std::copy(statuses.begin(), statuses.end(), arr); std::copy(statuses.begin(), statuses.end(), arr);
@ -1105,7 +1112,8 @@ void onApiFrontlightStatus(AsyncWebServerRequest *request)
void onApiFrontlightFlash(AsyncWebServerRequest *request) void onApiFrontlightFlash(AsyncWebServerRequest *request)
{ {
frontlightFlash(preferences.getUInt("flEffectDelay")); auto& ledHandler = getLedHandler();
ledHandler.frontlightFlash(preferences.getUInt("flEffectDelay"));
request->send(HTTP_OK); request->send(HTTP_OK);
} }
@ -1114,7 +1122,8 @@ void onApiFrontlightSetBrightness(AsyncWebServerRequest *request)
{ {
if (request->hasParam("b")) if (request->hasParam("b"))
{ {
frontlightSetBrightness(request->getParam("b")->value().toInt()); auto& ledHandler = getLedHandler();
ledHandler.frontlightSetBrightness(request->getParam("b")->value().toInt());
request->send(HTTP_OK); request->send(HTTP_OK);
} }
else else
@ -1125,21 +1134,51 @@ void onApiFrontlightSetBrightness(AsyncWebServerRequest *request)
void onApiFrontlightOff(AsyncWebServerRequest *request) void onApiFrontlightOff(AsyncWebServerRequest *request)
{ {
frontlightFadeOutAll(); auto& ledHandler = getLedHandler();
ledHandler.frontlightFadeOutAll();
request->send(HTTP_OK); request->send(HTTP_OK);
} }
#endif #endif
void onApiDNDTimeBasedEnable(AsyncWebServerRequest *request) {
auto& ledHandler = getLedHandler();
ledHandler.setDNDTimeBasedEnabled(true);
request->send(200);
}
void onApiDNDTimeBasedDisable(AsyncWebServerRequest *request) {
auto& ledHandler = getLedHandler();
ledHandler.setDNDTimeBasedEnabled(false);
request->send(200);
}
void onApiDNDSetTimeRange(AsyncWebServerRequest *request) {
if (request->hasParam("startHour") && request->hasParam("startMinute") &&
request->hasParam("endHour") && request->hasParam("endMinute")) {
auto& ledHandler = getLedHandler();
uint8_t startHour = request->getParam("startHour")->value().toInt();
uint8_t startMinute = request->getParam("startMinute")->value().toInt();
uint8_t endHour = request->getParam("endHour")->value().toInt();
uint8_t endMinute = request->getParam("endMinute")->value().toInt();
ledHandler.setDNDTimeRange(startHour, startMinute, endHour, endMinute);
request->send(200);
} else {
request->send(400);
}
}
void onApiDNDStatus(AsyncWebServerRequest *request) { void onApiDNDStatus(AsyncWebServerRequest *request) {
auto& ledHandler = getLedHandler();
JsonDocument doc; JsonDocument doc;
doc["enabled"] = dndEnabled; doc["enabled"] = ledHandler.isDNDEnabled();
doc["timeBasedEnabled"] = dndTimeBasedEnabled; doc["timeBasedEnabled"] = ledHandler.isDNDTimeBasedEnabled();
doc["startTime"] = String(dndTimeRange.startHour) + ":" + doc["startTime"] = String(ledHandler.getDNDStartHour()) + ":" +
(dndTimeRange.startMinute < 10 ? "0" : "") + String(dndTimeRange.startMinute); (ledHandler.getDNDStartMinute() < 10 ? "0" : "") + String(ledHandler.getDNDStartMinute());
doc["endTime"] = String(dndTimeRange.endHour) + ":" + doc["endTime"] = String(ledHandler.getDNDEndHour()) + ":" +
(dndTimeRange.endMinute < 10 ? "0" : "") + String(dndTimeRange.endMinute); (ledHandler.getDNDEndMinute() < 10 ? "0" : "") + String(ledHandler.getDNDEndMinute());
doc["active"] = isDNDActive(); doc["active"] = ledHandler.isDNDActive();
String response; String response;
serializeJson(doc, response); serializeJson(doc, response);
@ -1147,11 +1186,92 @@ void onApiDNDStatus(AsyncWebServerRequest *request) {
} }
void onApiDNDEnable(AsyncWebServerRequest *request) { void onApiDNDEnable(AsyncWebServerRequest *request) {
setDNDEnabled(true); auto& ledHandler = getLedHandler();
ledHandler.setDNDEnabled(true);
request->send(200); request->send(200);
} }
void onApiDNDDisable(AsyncWebServerRequest *request) { void onApiDNDDisable(AsyncWebServerRequest *request) {
setDNDEnabled(false); auto& ledHandler = getLedHandler();
ledHandler.setDNDEnabled(false);
request->send(200); request->send(200);
}
void onApiLightsGet(AsyncWebServerRequest *request)
{
auto& ledHandler = getLedHandler();
auto& pixels = ledHandler.getPixels();
DynamicJsonDocument doc(1024);
JsonArray lights = doc.createNestedArray("lights");
for (uint i = 0; i < pixels.numPixels(); i++)
{
uint32_t pixColor = pixels.getPixelColor(pixels.numPixels() - i - 1);
JsonObject light = lights.createNestedObject();
light["r"] = (uint8_t)(pixColor >> 16);
light["g"] = (uint8_t)(pixColor >> 8);
light["b"] = (uint8_t)pixColor;
}
String output;
serializeJson(doc, output);
request->send(200, "application/json", output);
}
void onApiLightsPost(AsyncWebServerRequest *request, uint8_t *data, size_t len,
size_t index, size_t total)
{
auto& ledHandler = getLedHandler();
auto& pixels = ledHandler.getPixels();
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, data);
if (error)
{
request->send(400);
return;
}
JsonArray lights = doc["lights"];
if (lights.size() != pixels.numPixels())
{
request->send(400);
return;
}
for (uint i = 0; i < pixels.numPixels(); i++)
{
JsonObject light = lights[i];
uint8_t red = light["r"];
uint8_t green = light["g"];
uint8_t blue = light["b"];
pixels.setPixelColor((pixels.numPixels() - i - 1),
pixels.Color(red, green, blue));
}
pixels.show();
request->send(200);
}
void onApiSettings(AsyncWebServerRequest *request, JsonVariant &json)
{
JsonObject settings = json.as<JsonObject>();
auto& ledHandler = getLedHandler();
if (settings.containsKey("dnd")) {
JsonObject dndObj = settings["dnd"];
if (dndObj.containsKey("timeBasedEnabled")) {
ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as<bool>());
}
if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") &&
dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) {
ledHandler.setDNDTimeRange(
dndObj["startHour"].as<uint8_t>(),
dndObj["startMinute"].as<uint8_t>(),
dndObj["endHour"].as<uint8_t>(),
dndObj["endMinute"].as<uint8_t>());
}
}
} }

View file

@ -18,6 +18,7 @@
#define WEBSERVER_H #define WEBSERVER_H
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"
#include "lib/config.hpp" #include "lib/config.hpp"
#include "lib/led_handler.hpp"
uint wifiLostConnection; uint wifiLostConnection;
uint priceNotifyLostConnection = 0; uint priceNotifyLostConnection = 0;
@ -58,13 +59,14 @@ void handleFrontlight() {
if (hasLightLevel() && preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0) { if (hasLightLevel() && preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0) {
uint lightLevel = getLightLevel(); uint lightLevel = getLightLevel();
uint luxThreshold = preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE); uint luxThreshold = preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE);
auto& ledHandler = getLedHandler();
if (lightLevel <= 1 && preferences.getBool("flOffWhenDark", DEFAULT_FL_OFF_WHEN_DARK)) { if (lightLevel <= 1 && preferences.getBool("flOffWhenDark", DEFAULT_FL_OFF_WHEN_DARK)) {
if (frontlightIsOn()) frontlightFadeOutAll(); if (ledHandler.frontlightIsOn()) ledHandler.frontlightFadeOutAll();
} else if (lightLevel < luxThreshold && !frontlightIsOn()) { } else if (lightLevel < luxThreshold && !ledHandler.frontlightIsOn()) {
frontlightFadeInAll(); ledHandler.frontlightFadeInAll();
} else if (frontlightIsOn() && lightLevel > luxThreshold) { } else if (ledHandler.frontlightIsOn() && lightLevel > luxThreshold) {
frontlightFadeOutAll(); ledHandler.frontlightFadeOutAll();
} }
} }
#endif #endif
@ -100,9 +102,7 @@ void checkMissedBlocks() {
} }
} }
void monitorDataConnections() { void monitorDataConnections() {
// Price notification monitoring // Price notification monitoring
if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE) && !isPriceNotifyConnected()) { if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE) && !isPriceNotifyConnected()) {
handlePriceNotifyDisconnection(); handlePriceNotifyDisconnection();
@ -137,7 +137,6 @@ extern "C" void app_main() {
bool thirdPartySource = getDataSource() == THIRD_PARTY_SOURCE; bool thirdPartySource = getDataSource() == THIRD_PARTY_SOURCE;
while (true) { while (true) {
if (eventSourceTaskHandle != NULL) { if (eventSourceTaskHandle != NULL) {
xTaskNotifyGive(eventSourceTaskHandle); xTaskNotifyGive(eventSourceTaskHandle);