diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 111a7a7..37b5b2f 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -181,45 +181,42 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) } void processNewBlock(uint32_t newBlockHeight) { - if (newBlockHeight < currentBlockHeight) - return; + if (currentBlockHeight == newBlockHeight) + { + return; + } - currentBlockHeight = newBlockHeight; - - // Serial.printf("New block found: %d\r\n", block["height"].as()); - preferences.putUInt("blockHeight", currentBlockHeight); + currentBlockHeight = newBlockHeight; lastBlockUpdate = esp_timer_get_time() / 1000000; if (workQueue != nullptr) { - WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); - // xTaskNotifyGive(blockUpdateTaskHandle); + WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + } - if (ScreenHandler::getCurrentScreen() != SCREEN_BLOCK_HEIGHT && - preferences.getBool("stealFocus", DEFAULT_STEAL_FOCUS)) - { + if (ScreenHandler::getCurrentScreen() != SCREEN_BLOCK_HEIGHT && + preferences.getBool("stealFocus", DEFAULT_STEAL_FOCUS)) + { uint64_t timerPeriod = 0; if (isTimerActive()) { - // store timer periode before making inactive to prevent artifacts - timerPeriod = getTimerSeconds(); - esp_timer_stop(screenRotateTimer); + timerPeriod = getTimerSeconds(); + esp_timer_stop(screenRotateTimer); } ScreenHandler::setCurrentScreen(SCREEN_BLOCK_HEIGHT); if (timerPeriod > 0) { - esp_timer_start_periodic(screenRotateTimer, + esp_timer_start_periodic(screenRotateTimer, timerPeriod * usPerSecond); } vTaskDelay(pdMS_TO_TICKS(315*NUM_SCREENS)); // Extra delay because of screen switching - } + } - 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 getLedHandler().queueEffect(LED_FLASH_BLOCK_NOTIFY); - } } } diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 5a3fa7d..6354a7a 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -48,7 +48,7 @@ void setup() setupPreferences(); setupHardware(); - setupDisplays(); + EPDManager::getInstance().initialize(); if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER)) { auto& ledHandler = getLedHandler(); @@ -115,7 +115,7 @@ void setup() ButtonHandler::setup(); setupOTA(); - waitUntilNoneBusy(); + EPDManager::getInstance().waitUntilNoneBusy(); #ifdef HAS_FRONTLIGHT if (!preferences.getBool("flAlwaysOn", DEFAULT_FL_ALWAYS_ON)) @@ -126,7 +126,7 @@ void setup() } #endif - forceFullRefresh(); + EPDManager::getInstance().forceFullRefresh(); } void setupWifi() @@ -181,8 +181,8 @@ void setupWifi() wifiManager->getConfigPortalSSID().c_str(), softAP_password.c_str()); // delay(6000); - setFgColor(GxEPD_BLACK); - setBgColor(GxEPD_WHITE); + EPDManager::getInstance().setForegroundColor(GxEPD_BLACK); + EPDManager::getInstance().setBackgroundColor(GxEPD_WHITE); const String qrText = "qrWIFI:S:" + wifiManager->getConfigPortalSSID() + ";T:WPA;P:" + softAP_password.c_str() + ";;"; const String explainText = "*SSID: *\r\n" + @@ -212,58 +212,22 @@ void setupWifi() #endif "\r\n\r\n*FW build date:*\r\n" + formattedDate, qrText}; - setEpdContent(epdContent); }); + + EPDManager::getInstance().setContent(epdContent); }); wm.setSaveConfigCallback([]() { preferences.putBool("wifiConfigured", true); delay(1000); - // just restart after succes + // just restart after success ESP.restart(); }); bool ac = wm.autoConnect(softAP_SSID.c_str(), softAP_password.c_str()); - - // waitUntilNoneBusy(); - // std::array epdContent = {"Welcome!", - // "Bienvenidos!", "Use\r\nweb-interface\r\npara configurar", "Use\r\nla - // interfaz web\r\npara configurar", "Or - // restart\r\nwhile\r\nholding\r\n2nd button\r\r\nto start\r\n QR-config", - // "O reinicie\r\nmientras\r\n mantiene presionado\r\nel segundo - // botón\r\r\npara iniciar\r\nQR-config", ""}; setEpdContent(epdContent); - // esp_task_wdt_init(30, false); - // uint count = 0; - // while (WiFi.status() != WL_CONNECTED) - // { - // if (Serial.available() > 0) - // { - // uint8_t b = Serial.read(); - - // if (parse_improv_serial_byte(x_position, b, x_buffer, - // onImprovCommandCallback, onImprovErrorCallback)) - // { - // x_buffer[x_position++] = b; - // } - // else - // { - // x_position = 0; - // } - // } - // count++; - - // if (count > 2000000) { - // queueLedEffect(LED_EFFECT_HEARTBEAT); - // count = 0; - // } - // } - // esp_task_wdt_deinit(); - // esp_task_wdt_reset(); } - - - setFgColor(preferences.getUInt("fgColor", isWhiteVersion() ? GxEPD_BLACK : GxEPD_WHITE)); - setBgColor(preferences.getUInt("bgColor", isWhiteVersion() ? GxEPD_WHITE : GxEPD_BLACK)); + EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", isWhiteVersion() ? GxEPD_BLACK : GxEPD_WHITE)); + EPDManager::getInstance().setBackgroundColor(preferences.getUInt("bgColor", isWhiteVersion() ? GxEPD_WHITE : GxEPD_BLACK)); } // else // { @@ -299,8 +263,8 @@ void setupPreferences() { preferences.begin("btclock", false); - setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); - setBgColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); + EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); + EPDManager::getInstance().setBackgroundColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT)); setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD); diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index 9808aa7..fa389c6 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -1,455 +1,278 @@ #include "epd.hpp" +// Initialize static members #ifdef IS_BTCLOCK_REV_B -Native_Pin EPD_CS[NUM_SCREENS] = { - Native_Pin(2), - Native_Pin(4), - Native_Pin(6), - Native_Pin(10), - Native_Pin(38), - Native_Pin(21), - Native_Pin(17), +Native_Pin EPDManager::EPD_DC(14); +std::array EPDManager::EPD_CS = { + Native_Pin(2), Native_Pin(4), Native_Pin(6), Native_Pin(10), + Native_Pin(38), Native_Pin(21), Native_Pin(17) }; -Native_Pin EPD_BUSY[NUM_SCREENS] = { - Native_Pin(3), - Native_Pin(5), - Native_Pin(7), - Native_Pin(9), - Native_Pin(37), - Native_Pin(18), - Native_Pin(16), +std::array EPDManager::EPD_BUSY = { + Native_Pin(3), Native_Pin(5), Native_Pin(7), Native_Pin(9), + Native_Pin(37), Native_Pin(18), Native_Pin(16) }; -MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { - MCP23X17_Pin(mcp1, 8), - MCP23X17_Pin(mcp1, 9), - MCP23X17_Pin(mcp1, 10), - MCP23X17_Pin(mcp1, 11), - MCP23X17_Pin(mcp1, 12), - MCP23X17_Pin(mcp1, 13), - MCP23X17_Pin(mcp1, 14), +std::array EPDManager::EPD_RESET = { + MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14) }; - -Native_Pin EPD_DC = Native_Pin(14); -#elif IS_BTCLOCK_V8 -Native_Pin EPD_DC = Native_Pin(38); - -MCP23X17_Pin EPD_BUSY[NUM_SCREENS] = { - MCP23X17_Pin(mcp1, 8), - MCP23X17_Pin(mcp1, 9), - MCP23X17_Pin(mcp1, 10), - MCP23X17_Pin(mcp1, 11), - MCP23X17_Pin(mcp1, 12), - MCP23X17_Pin(mcp1, 13), - MCP23X17_Pin(mcp1, 14), - MCP23X17_Pin(mcp1, 4), +#elif defined(IS_BTCLOCK_V8) +Native_Pin EPDManager::EPD_DC(38); +std::array EPDManager::EPD_BUSY = { + MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14), MCP23X17_Pin(mcp1, 4) }; - -MCP23X17_Pin EPD_CS[NUM_SCREENS] = { +std::array EPDManager::EPD_CS = { MCP23X17_Pin(mcp2, 8), MCP23X17_Pin(mcp2, 10), MCP23X17_Pin(mcp2, 12), MCP23X17_Pin(mcp2, 14), MCP23X17_Pin(mcp2, 0), MCP23X17_Pin(mcp2, 2), - MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6)}; - -MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { - MCP23X17_Pin(mcp2, 9), - MCP23X17_Pin(mcp2, 11), - MCP23X17_Pin(mcp2, 13), - MCP23X17_Pin(mcp2, 15), - MCP23X17_Pin(mcp2, 1), - MCP23X17_Pin(mcp2, 3), - MCP23X17_Pin(mcp2, 5), - MCP23X17_Pin(mcp2, 7), + MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6) +}; +std::array EPDManager::EPD_RESET = { + MCP23X17_Pin(mcp2, 9), MCP23X17_Pin(mcp2, 11), MCP23X17_Pin(mcp2, 13), + MCP23X17_Pin(mcp2, 15), MCP23X17_Pin(mcp2, 1), MCP23X17_Pin(mcp2, 3), + MCP23X17_Pin(mcp2, 5), MCP23X17_Pin(mcp2, 7) }; #else -Native_Pin EPD_CS[NUM_SCREENS] = { - Native_Pin(2), - Native_Pin(4), - Native_Pin(6), - Native_Pin(10), - Native_Pin(33), - Native_Pin(21), - Native_Pin(17), -#if NUM_SCREENS == 9 - // MCP23X17_Pin(mcp2, 7), - Native_Pin(-1), - Native_Pin(-1), -#endif +Native_Pin EPDManager::EPD_DC(14); +std::array EPDManager::EPD_CS = { + Native_Pin(2), Native_Pin(4), Native_Pin(6), Native_Pin(10), + Native_Pin(33), Native_Pin(21), Native_Pin(17) }; -Native_Pin EPD_BUSY[NUM_SCREENS] = { - Native_Pin(3), - Native_Pin(5), - Native_Pin(7), - Native_Pin(9), - Native_Pin(37), - Native_Pin(18), - Native_Pin(16), +std::array EPDManager::EPD_BUSY = { + Native_Pin(3), Native_Pin(5), Native_Pin(7), Native_Pin(9), + Native_Pin(37), Native_Pin(18), Native_Pin(16) }; -MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = { - MCP23X17_Pin(mcp1, 8), - MCP23X17_Pin(mcp1, 9), - MCP23X17_Pin(mcp1, 10), - MCP23X17_Pin(mcp1, 11), - MCP23X17_Pin(mcp1, 12), - MCP23X17_Pin(mcp1, 13), - MCP23X17_Pin(mcp1, 14), +std::array EPDManager::EPD_RESET = { + MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10), + MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13), + MCP23X17_Pin(mcp1, 14) }; - -Native_Pin EPD_DC = Native_Pin(14); #endif -GxEPD2_BW displays[NUM_SCREENS] = { - EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET_MPD[0], &EPD_BUSY[0]), - EPD_CLASS(&EPD_CS[1], &EPD_DC, &EPD_RESET_MPD[1], &EPD_BUSY[1]), - EPD_CLASS(&EPD_CS[2], &EPD_DC, &EPD_RESET_MPD[2], &EPD_BUSY[2]), - EPD_CLASS(&EPD_CS[3], &EPD_DC, &EPD_RESET_MPD[3], &EPD_BUSY[3]), - EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET_MPD[4], &EPD_BUSY[4]), - EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET_MPD[5], &EPD_BUSY[5]), - EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET_MPD[6], &EPD_BUSY[6]), -#ifdef IS_BTCLOCK_V8 - EPD_CLASS(&EPD_CS[7], &EPD_DC, &EPD_RESET_MPD[7], &EPD_BUSY[7]), -#endif -}; - -std::array currentEpdContent; -std::array epdContent; -uint32_t lastFullRefresh[NUM_SCREENS]; -TaskHandle_t tasks[NUM_SCREENS]; -// TaskHandle_t epdTaskHandle = NULL; - -#define UPDATE_QUEUE_SIZE 14 -QueueHandle_t updateQueue; - -// SemaphoreHandle_t epdUpdateSemaphore[NUM_SCREENS]; - -int fgColor = GxEPD_WHITE; -int bgColor = GxEPD_BLACK; - -struct FontFamily { - GFXfont* big; - GFXfont* medium; - GFXfont* small; -}; - -FontFamily antonioFonts = {nullptr, nullptr, nullptr}; -FontFamily oswaldFonts = {nullptr, nullptr, nullptr}; - -const GFXfont *FONT_SMALL; -const GFXfont *FONT_BIG; -const GFXfont *FONT_MEDIUM; -const GFXfont *FONT_SATSYMBOL; - -std::mutex epdUpdateMutex; -std::mutex epdMutex[NUM_SCREENS]; - -#ifdef IS_BTCLOCK_V8 -#define EPD_TASK_STACK_SIZE 4096 -#else -#define EPD_TASK_STACK_SIZE 2048 -#endif - -#define BUSY_TIMEOUT_COUNT 200 -#define BUSY_RETRY_DELAY pdMS_TO_TICKS(10) - -void forceFullRefresh() -{ - for (uint i = 0; i < NUM_SCREENS; i++) - { - lastFullRefresh[i] = NULL; - } +EPDManager& EPDManager::getInstance() { + static EPDManager instance; + return instance; } -void loadFonts(const String& fontName) { - if (fontName == FontNames::ANTONIO) { - // Load Antonio fonts - antonioFonts.big = FontLoader::loadCompressedFont(Antonio_SemiBold90pt7b_Properties); - antonioFonts.medium = FontLoader::loadCompressedFont(Antonio_SemiBold40pt7b_Properties); - antonioFonts.small = FontLoader::loadCompressedFont(Antonio_SemiBold20pt7b_Properties); - - FONT_BIG = antonioFonts.big; - FONT_MEDIUM = antonioFonts.medium; - FONT_SMALL = antonioFonts.small; - } else if (fontName == FontNames::OSWALD) { - // Load Oswald fonts - oswaldFonts.big = FontLoader::loadCompressedFont(Oswald_Medium80pt7b_Properties); - oswaldFonts.medium = FontLoader::loadCompressedFont(Oswald_Medium30pt7b_Properties); - oswaldFonts.small = FontLoader::loadCompressedFont(Oswald_Medium20pt7b_Properties); - - FONT_BIG = oswaldFonts.big; - FONT_MEDIUM = oswaldFonts.medium; - FONT_SMALL = oswaldFonts.small; - } - - FONT_SATSYMBOL = FontLoader::loadCompressedFont(Satoshi_Symbol90pt7b_Properties); -} - -void setupDisplays() { - // Load fonts based on preference - String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME); - loadFonts(fontName); - - // Initialize displays - std::lock_guard lockMcp(mcpMutex); - for (uint i = 0; i < NUM_SCREENS; i++) { - displays[i].init(0, true, 30); - } - - // Create update queue and task - updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); - xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE * 2, NULL, 11, NULL); - - // Create display update tasks - for (uint i = 0; i < NUM_SCREENS; i++) { - int *taskParam = new int; - *taskParam = i; - xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), EPD_TASK_STACK_SIZE, taskParam, 11, &tasks[i]); - } - - // Check for storage mode (prevents burn-in) - if (mcp1.read1(0) == LOW) { - setFgColor(GxEPD_BLACK); - setBgColor(GxEPD_WHITE); - epdContent.fill(""); - } else { - // Initialize with custom text or default - String customText = preferences.getString("displayText", DEFAULT_BOOT_TEXT); - std::array newContent; - newContent.fill(" "); - - for (size_t i = 0; i < std::min(customText.length(), (size_t)NUM_SCREENS); i++) { - newContent[i] = String(customText[i]); +EPDManager::EPDManager() + : currentContent{} + , content{} + , lastFullRefresh{} + , tasks{} + , updateQueue{nullptr} + , antonioFonts{nullptr, nullptr, nullptr} + , oswaldFonts{nullptr, nullptr, nullptr} + , fontSmall{nullptr} + , fontBig{nullptr} + , fontMedium{nullptr} + , fontSatsymbol{nullptr} + , bgColor{GxEPD_BLACK} + , fgColor{GxEPD_WHITE} + , displays{ + #ifdef IS_BTCLOCK_V8 + EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET[0], &EPD_BUSY[0]), + EPD_CLASS(&EPD_CS[1], &EPD_DC, &EPD_RESET[1], &EPD_BUSY[1]), + EPD_CLASS(&EPD_CS[2], &EPD_DC, &EPD_RESET[2], &EPD_BUSY[2]), + EPD_CLASS(&EPD_CS[3], &EPD_DC, &EPD_RESET[3], &EPD_BUSY[3]), + EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET[4], &EPD_BUSY[4]), + EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET[5], &EPD_BUSY[5]), + EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET[6], &EPD_BUSY[6]), + EPD_CLASS(&EPD_CS[7], &EPD_DC, &EPD_RESET[7], &EPD_BUSY[7]) + #else + EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET[0], &EPD_BUSY[0]), + EPD_CLASS(&EPD_CS[1], &EPD_DC, &EPD_RESET[1], &EPD_BUSY[1]), + EPD_CLASS(&EPD_CS[2], &EPD_DC, &EPD_RESET[2], &EPD_BUSY[2]), + EPD_CLASS(&EPD_CS[3], &EPD_DC, &EPD_RESET[3], &EPD_BUSY[3]), + EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET[4], &EPD_BUSY[4]), + EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET[5], &EPD_BUSY[5]), + EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET[6], &EPD_BUSY[6]) + #endif } - - epdContent = newContent; - } - - setEpdContent(epdContent); +{ } -void setEpdContent(std::array newEpdContent) -{ - setEpdContent(newEpdContent, false); -} - -void setEpdContent(std::array newEpdContent) -{ - std::array conv; - - for (size_t i = 0; i < newEpdContent.size(); ++i) - { - conv[i] = String(newEpdContent[i].c_str()); +EPDManager::~EPDManager() { + // Clean up tasks + for (auto& task : tasks) { + if (task != nullptr) { + vTaskDelete(task); + } } - return setEpdContent(conv); + // Clean up queue + if (updateQueue != nullptr) { + vQueueDelete(updateQueue); + } + + // Clean up fonts + delete antonioFonts.big; + delete antonioFonts.medium; + delete antonioFonts.small; + delete oswaldFonts.big; + delete oswaldFonts.medium; + delete oswaldFonts.small; } -void setEpdContent(std::array newEpdContent, - bool forceUpdate) -{ - std::lock_guard lock(epdUpdateMutex); +void EPDManager::initialize() { + // Load fonts based on preference + String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME); + loadFonts(fontName); + // Initialize displays + std::lock_guard lockMcp(mcpMutex); + for (auto& display : displays) { + display.init(0, true, 30); + } + + // Create update queue and task + updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); + xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE * 2, nullptr, 11, nullptr); + + // Create display update tasks + for (size_t i = 0; i < NUM_SCREENS; i++) { + auto* taskParam = new int(i); + xTaskCreate(updateDisplayTask, ("EpdUpd" + String(i)).c_str(), EPD_TASK_STACK_SIZE, + taskParam, 11, &tasks[i]); + } + + // Check for storage mode (prevents burn-in) + if (mcp1.read1(0) == LOW) { + setForegroundColor(GxEPD_BLACK); + setBackgroundColor(GxEPD_WHITE); + content.fill(""); + } else { + // Initialize with custom text or default + String customText = preferences.getString("displayText", DEFAULT_BOOT_TEXT); + std::array newContent; + newContent.fill(" "); + + for (size_t i = 0; i < std::min(customText.length(), (size_t)NUM_SCREENS); i++) { + newContent[i] = String(customText[i]); + } + + content = newContent; + } + + setContent(content); +} + +void EPDManager::loadFonts(const String& fontName) { + if (fontName == FontNames::ANTONIO) { + // Load Antonio fonts + antonioFonts.big = FontLoader::loadCompressedFont(Antonio_SemiBold90pt7b_Properties); + antonioFonts.medium = FontLoader::loadCompressedFont(Antonio_SemiBold40pt7b_Properties); + antonioFonts.small = FontLoader::loadCompressedFont(Antonio_SemiBold20pt7b_Properties); + + fontBig = antonioFonts.big; + fontMedium = antonioFonts.medium; + fontSmall = antonioFonts.small; + } else if (fontName == FontNames::OSWALD) { + // Load Oswald fonts + oswaldFonts.big = FontLoader::loadCompressedFont(Oswald_Medium80pt7b_Properties); + oswaldFonts.medium = FontLoader::loadCompressedFont(Oswald_Medium30pt7b_Properties); + oswaldFonts.small = FontLoader::loadCompressedFont(Oswald_Medium20pt7b_Properties); + + fontBig = oswaldFonts.big; + fontMedium = oswaldFonts.medium; + fontSmall = oswaldFonts.small; + } + + fontSatsymbol = FontLoader::loadCompressedFont(Satoshi_Symbol90pt7b_Properties); +} + +void EPDManager::forceFullRefresh() { + std::fill(lastFullRefresh.begin(), lastFullRefresh.end(), 0); +} + +void EPDManager::setContent(const std::array& newContent, bool forceUpdate) { + std::lock_guard lock(updateMutex); waitUntilNoneBusy(); - for (uint i = 0; i < NUM_SCREENS; i++) - { - if (newEpdContent[i].compareTo(currentEpdContent[i]) != 0 || forceUpdate) - { - epdContent[i] = newEpdContent[i]; - UpdateDisplayTaskItem dispUpdate = {i}; + for (size_t i = 0; i < NUM_SCREENS; i++) { + if (newContent[i].compareTo(currentContent[i]) != 0 || forceUpdate) { + content[i] = newContent[i]; + UpdateDisplayTaskItem dispUpdate{static_cast(i)}; xQueueSend(updateQueue, &dispUpdate, portMAX_DELAY); } } } -void prepareDisplayUpdateTask(void *pvParameters) -{ - UpdateDisplayTaskItem receivedItem; - - while (1) - { - // Wait for a work item to be available in the queue - if (xQueueReceive(updateQueue, &receivedItem, portMAX_DELAY)) - { - uint epdIndex = receivedItem.dispNum; - std::lock_guard lock(epdMutex[epdIndex]); - // displays[epdIndex].init(0, false); // Little longer reset duration - // because of MCP - - bool updatePartial = true; - - if (epdContent[epdIndex].length() > 1 && strstr(epdContent[epdIndex].c_str(), "/") != NULL) - { - String top = epdContent[epdIndex].substring( - 0, epdContent[epdIndex].indexOf("/")); - String bottom = epdContent[epdIndex].substring( - epdContent[epdIndex].indexOf("/") + 1); - splitText(epdIndex, top, bottom, updatePartial); - } - else if (epdContent[epdIndex].startsWith(F("qr"))) - { - renderQr(epdIndex, epdContent[epdIndex], updatePartial); - } - else if (epdContent[epdIndex].startsWith(F("mdi"))) - { - bool updated = renderIcon(epdIndex, epdContent[epdIndex], updatePartial); - if (!updated) - { - continue; - } - } - else if (epdContent[epdIndex].length() > 5) - { - renderText(epdIndex, epdContent[epdIndex], updatePartial); - } - else - { - if (epdContent[epdIndex].length() == 2) - { - showChars(epdIndex, epdContent[epdIndex], updatePartial, FONT_BIG); - } - else if (epdContent[epdIndex].length() > 1 && epdContent[epdIndex].indexOf(".") == -1) - { - if (epdContent[epdIndex].equals("STS")) - { - showDigit(epdIndex, 'S', updatePartial, - FONT_SATSYMBOL); - } - else - { - showChars(epdIndex, epdContent[epdIndex], updatePartial, - FONT_MEDIUM); - } - } - else - { - - showDigit(epdIndex, epdContent[epdIndex].c_str()[0], updatePartial, - FONT_BIG); - } - } - - xTaskNotifyGive(tasks[epdIndex]); - } +void EPDManager::setContent(const std::array& newContent) { + std::array conv; + for (size_t i = 0; i < newContent.size(); ++i) { + conv[i] = String(newContent[i].c_str()); } + setContent(conv); } -extern "C" void updateDisplay(void *pvParameters) noexcept -{ - const int epdIndex = *(int *)pvParameters; - delete (int *)pvParameters; +std::array EPDManager::getCurrentContent() const { + return currentContent; +} - for (;;) - { - // Wait for the task notification - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - - std::lock_guard lock(epdMutex[epdIndex]); - - { - std::lock_guard lockMcp(mcpMutex); - - displays[epdIndex].init(0, false, 40); - } - uint count = 0; - while (EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) - { - vTaskDelay(pdMS_TO_TICKS(100)); +void EPDManager::waitUntilNoneBusy() { + for (size_t i = 0; i < NUM_SCREENS; i++) { + uint32_t count = 0; + while (EPD_BUSY[i].digitalRead()) { count++; - } - - bool updatePartial = true; - - // Full Refresh every x minutes - if (!lastFullRefresh[epdIndex] || - (millis() - lastFullRefresh[epdIndex]) > - (preferences.getUInt("fullRefreshMin", - DEFAULT_MINUTES_FULL_REFRESH) * - 60 * 1000)) - { - updatePartial = false; - } - - char tries = 0; - while (tries < 3) - { - if (displays[epdIndex].displayWithReturn(updatePartial)) - { - displays[epdIndex].powerOff(); - currentEpdContent[epdIndex] = epdContent[epdIndex]; - if (!updatePartial) - lastFullRefresh[epdIndex] = millis(); - - if (eventSourceTaskHandle != NULL) - xTaskNotifyGive(eventSourceTaskHandle); + vTaskDelay(BUSY_RETRY_DELAY); + if (count == BUSY_TIMEOUT_COUNT) { + vTaskDelay(pdMS_TO_TICKS(100)); + } else if (count > BUSY_TIMEOUT_COUNT + 5) { + log_e("Display %d busy timeout", i); break; } - - vTaskDelay(pdMS_TO_TICKS(100)); - tries++; } } } -void splitText(const uint dispNum, const String &top, const String &bottom, - bool partial) -{ - if (preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) - { +void EPDManager::setupDisplay(uint dispNum, const GFXfont* font) { + displays[dispNum].setRotation(2); + displays[dispNum].setFont(font); + displays[dispNum].setTextColor(fgColor); + displays[dispNum].fillScreen(bgColor); +} + +void EPDManager::splitText(uint dispNum, const String& top, const String& bottom, bool partial) { + if (preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) { displays[dispNum].setRotation(1); - } - else - { + } else { displays[dispNum].setRotation(2); } - displays[dispNum].setFont(FONT_SMALL); - displays[dispNum].setTextColor(getFgColor()); + displays[dispNum].setFont(fontSmall); + displays[dispNum].setTextColor(fgColor); // Top text int16_t ttbx, ttby; uint16_t ttbw, ttbh; displays[dispNum].getTextBounds(top, 0, 0, &ttbx, &ttby, &ttbw, &ttbh); uint16_t tx = ((displays[dispNum].width() - ttbw) / 2) - ttbx; - uint16_t ty = - ((displays[dispNum].height() - ttbh) / 2) - ttby - ttbh / 2 - 12; + uint16_t ty = ((displays[dispNum].height() - ttbh) / 2) - ttby - ttbh / 2 - 12; // Bottom text int16_t tbbx, tbby; uint16_t tbbw, tbbh; displays[dispNum].getTextBounds(bottom, 0, 0, &tbbx, &tbby, &tbbw, &tbbh); uint16_t bx = ((displays[dispNum].width() - tbbw) / 2) - tbbx; - uint16_t by = - ((displays[dispNum].height() - tbbh) / 2) - tbby + tbbh / 2 + 12; + uint16_t by = ((displays[dispNum].height() - tbbh) / 2) - tbby + tbbh / 2 + 12; - // Make separator as wide as the shortest text. - uint16_t lineWidth, lineX; - if (tbbw < ttbh) - lineWidth = tbbw; - else - lineWidth = ttbw; - lineX = round((displays[dispNum].width() - lineWidth) / 2); + // Make separator as wide as the shortest text + uint16_t lineWidth = (tbbw < ttbh) ? tbbw : ttbw; + uint16_t lineX = round((displays[dispNum].width() - lineWidth) / 2); - displays[dispNum].fillScreen(getBgColor()); + displays[dispNum].fillScreen(bgColor); displays[dispNum].setCursor(tx, ty); displays[dispNum].print(top); displays[dispNum].fillRoundRect(lineX, displays[dispNum].height() / 2 - 3, - lineWidth, 6, 3, getFgColor()); + lineWidth, 6, 3, fgColor); displays[dispNum].setCursor(bx, by); displays[dispNum].print(bottom); } -// Consolidate common display setup code into a helper function -void setupDisplay(const uint dispNum, const GFXfont *font) -{ - displays[dispNum].setRotation(2); - displays[dispNum].setFont(font); - displays[dispNum].setTextColor(getFgColor()); - displays[dispNum].fillScreen(getBgColor()); -} - -void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) -{ +void EPDManager::showDigit(uint dispNum, char chr, bool partial, const GFXfont* font) { String str(chr); - if (chr == '.') - { + if (chr == '.') { str = "!"; } @@ -465,56 +288,34 @@ void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) displays[dispNum].setCursor(x, y); displays[dispNum].print(str); - if (chr == '.') - { + if (chr == '.') { displays[dispNum].fillRect(0, 0, displays[dispNum].width(), - round(displays[dispNum].height() * 0.67), getBgColor()); + round(displays[dispNum].height() * 0.67), bgColor); } } -int16_t calculateDescent(const GFXfont *font) -{ - int16_t maxDescent = 0; - for (uint16_t i = font->first; i <= font->last; i++) - { - GFXglyph *glyph = &font->glyph[i - font->first]; - int16_t descent = glyph->yOffset; - if (descent > maxDescent) - { - maxDescent = descent; - } - } - return maxDescent; -} - -void showChars(const uint dispNum, const String &chars, bool partial, - const GFXfont *font) -{ +void EPDManager::showChars(uint dispNum, const String& chars, bool partial, const GFXfont* font) { setupDisplay(dispNum, font); int16_t tbx, tby; uint16_t tbw, tbh; displays[dispNum].getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh); - // center the bounding box by transposition of the origin: + // Center the bounding box by transposition of the origin uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; - for (int i = 0; i < chars.length(); i++) - { + for (size_t i = 0; i < chars.length(); i++) { char c = chars[i]; - if (c == '.' || c == ',') - { + if (c == '.' || c == ',') { // For the dot, calculate its specific descent - GFXglyph *dotGlyph = &font->glyph[c - font->first]; + GFXglyph* dotGlyph = &font->glyph[c - font->first]; int16_t dotDescent = dotGlyph->yOffset; // Draw the dot with adjusted y-position displays[dispNum].setCursor(x, y + dotDescent + dotGlyph->height + 8); displays[dispNum].print(c); - } - else - { + } else { // For other characters, use the original y-position displays[dispNum].setCursor(x, y); displays[dispNum].print(c); @@ -525,104 +326,70 @@ void showChars(const uint dispNum, const String &chars, bool partial, } } -int getBgColor() { return bgColor; } - -int getFgColor() { return fgColor; } - -void setBgColor(int color) { bgColor = color; } - -void setFgColor(int color) { fgColor = color; } - -std::array getCurrentEpdContent() -{ - return currentEpdContent; -} -void renderText(const uint dispNum, const String &text, bool partial) -{ +bool EPDManager::renderIcon(uint dispNum, const String& text, bool partial) { displays[dispNum].setRotation(2); displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); - displays[dispNum].fillScreen(GxEPD_WHITE); - displays[dispNum].setTextColor(GxEPD_BLACK); - displays[dispNum].setCursor(0, 50); - - std::stringstream ss; - ss.str(text.c_str()); - - std::string line; - - while (std::getline(ss, line, '\n')) - { - if (line.rfind("*", 0) == 0) - { - line.erase(std::remove(line.begin(), line.end(), '*'), line.end()); - - displays[dispNum].setFont(&FreeSansBold9pt7b); - displays[dispNum].println(line.c_str()); - } - else - { - displays[dispNum].setFont(&FreeSans9pt7b); - displays[dispNum].println(line.c_str()); - } - } -} - -bool renderIcon(const uint dispNum, const String &text, bool partial) -{ - displays[dispNum].setRotation(2); - - displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); - displays[dispNum].fillScreen(getBgColor()); - displays[dispNum].setTextColor(getFgColor()); + displays[dispNum].height()); + displays[dispNum].fillScreen(bgColor); + displays[dispNum].setTextColor(fgColor); uint iconIndex = 0; uint width = 122; uint height = 122; - if (text.endsWith("rocket")) - { + + if (text.endsWith("rocket")) { iconIndex = 1; - } - else if (text.endsWith("lnbolt")) - { + } else if (text.endsWith("lnbolt")) { iconIndex = 2; - } - else if (text.endsWith("bitaxe")) - { + } else if (text.endsWith("bitaxe")) { width = 88; height = 220; iconIndex = 3; - } - else if (text.endsWith("miningpool")) - { + } else if (text.endsWith("miningpool")) { LogoData logo = MiningPoolStatsFetch::getInstance().getLogo(); - - if (logo.size == 0) - { + if (logo.size == 0) { Serial.println(F("No logo found")); return false; } int x_offset = (displays[dispNum].width() - logo.width) / 2; int y_offset = (displays[dispNum].height() - logo.height) / 2; - // Close the file - - displays[dispNum].drawInvertedBitmap(x_offset, y_offset, logo.data, logo.width, logo.height, getFgColor()); + displays[dispNum].drawInvertedBitmap(x_offset, y_offset, logo.data, + logo.width, logo.height, fgColor); return true; } int x_offset = (displays[dispNum].width() - width) / 2; int y_offset = (displays[dispNum].height() - height) / 2; - - displays[dispNum].drawInvertedBitmap(x_offset, y_offset, epd_icons_allArray[iconIndex], width, height, getFgColor()); - + displays[dispNum].drawInvertedBitmap(x_offset, y_offset, epd_icons_allArray[iconIndex], + width, height, fgColor); return true; - // displays[dispNum].drawInvertedBitmap(0,0, getOceanIcon(), 122, 250, getFgColor()); } -void renderQr(const uint dispNum, const String &text, bool partial) -{ +void EPDManager::renderText(uint dispNum, const String& text, bool partial) { + displays[dispNum].setRotation(2); + displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), + displays[dispNum].height()); + displays[dispNum].fillScreen(GxEPD_WHITE); + displays[dispNum].setTextColor(GxEPD_BLACK); + displays[dispNum].setCursor(0, 50); + + std::stringstream ss; + ss.str(text.c_str()); + std::string line; + + while (std::getline(ss, line, '\n')) { + if (line.rfind("*", 0) == 0) { + line.erase(std::remove(line.begin(), line.end(), '*'), line.end()); + displays[dispNum].setFont(&FreeSansBold9pt7b); + } else { + displays[dispNum].setFont(&FreeSans9pt7b); + } + displays[dispNum].println(line.c_str()); + } +} + +void EPDManager::renderQr(uint dispNum, const String& text, bool partial) { #ifdef USE_QR // Dynamically allocate QR buffer uint8_t* qrcode = (uint8_t*)malloc(qrcodegen_BUFFER_LEN_MAX); @@ -639,19 +406,15 @@ void renderQr(const uint dispNum, const String &text, bool partial) 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); + const int paddingY = floor(float(displays[dispNum].height() - (size * 4)) / 2); + displays[dispNum].setRotation(2); - displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); + 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++) - { + for (int y = 0; y < size * 4; y++) { + for (int x = 0; x < size * 4; x++) { displays[dispNum].drawPixel( padding + x, paddingY + y, qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) @@ -661,30 +424,114 @@ void renderQr(const uint dispNum, const String &text, bool partial) } } - // Free the buffer after we're done free(qrcode); #endif } -void waitUntilNoneBusy() -{ - for (int i = 0; i < NUM_SCREENS; i++) - { - uint count = 0; - while (EPD_BUSY[i].digitalRead()) - { - count++; - vTaskDelay(BUSY_RETRY_DELAY); +int16_t EPDManager::calculateDescent(const GFXfont* font) { + int16_t maxDescent = 0; + for (uint16_t i = font->first; i <= font->last; i++) { + GFXglyph* glyph = &font->glyph[i - font->first]; + int16_t descent = glyph->yOffset; + if (descent > maxDescent) { + maxDescent = descent; + } + } + return maxDescent; +} - if (count == BUSY_TIMEOUT_COUNT) - { - vTaskDelay(pdMS_TO_TICKS(100)); - } - else if (count > BUSY_TIMEOUT_COUNT + 5) - { - log_e("Display %d busy timeout", i); +void EPDManager::updateDisplayTask(void* pvParameters) noexcept { + auto& instance = EPDManager::getInstance(); + const int epdIndex = *(int*)pvParameters; + delete (int*)pvParameters; + + for (;;) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + std::lock_guard lock(instance.displayMutexes[epdIndex]); + { + std::lock_guard lockMcp(mcpMutex); + instance.displays[epdIndex].init(0, false, 40); + } + + uint32_t count = 0; + while (instance.EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) { + vTaskDelay(pdMS_TO_TICKS(100)); + count++; + } + + bool updatePartial = true; + if (!instance.lastFullRefresh[epdIndex] || + (millis() - instance.lastFullRefresh[epdIndex]) > + (preferences.getUInt("fullRefreshMin", DEFAULT_MINUTES_FULL_REFRESH) * 60 * 1000)) { + updatePartial = false; + } + + char tries = 0; + while (tries < 3) { + if (instance.displays[epdIndex].displayWithReturn(updatePartial)) { + instance.displays[epdIndex].powerOff(); + instance.currentContent[epdIndex] = instance.content[epdIndex]; + if (!updatePartial) { + instance.lastFullRefresh[epdIndex] = millis(); + } + + if (eventSourceTaskHandle != nullptr) { + xTaskNotifyGive(eventSourceTaskHandle); + } break; } + + vTaskDelay(pdMS_TO_TICKS(100)); + tries++; + } + } +} + +void EPDManager::prepareDisplayUpdateTask(void* pvParameters) { + auto& instance = EPDManager::getInstance(); + UpdateDisplayTaskItem receivedItem; + + for (;;) { + if (xQueueReceive(instance.updateQueue, &receivedItem, portMAX_DELAY)) { + uint epdIndex = receivedItem.dispNum; + std::lock_guard lock(instance.displayMutexes[epdIndex]); + + bool updatePartial = true; + + if (instance.content[epdIndex].length() > 1 && + strstr(instance.content[epdIndex].c_str(), "/") != nullptr) { + String top = instance.content[epdIndex].substring( + 0, instance.content[epdIndex].indexOf("/")); + String bottom = instance.content[epdIndex].substring( + instance.content[epdIndex].indexOf("/") + 1); + instance.splitText(epdIndex, top, bottom, updatePartial); + } else if (instance.content[epdIndex].startsWith(F("qr"))) { + instance.renderQr(epdIndex, instance.content[epdIndex], updatePartial); + } else if (instance.content[epdIndex].startsWith(F("mdi"))) { + if (!instance.renderIcon(epdIndex, instance.content[epdIndex], updatePartial)) { + continue; + } + } else if (instance.content[epdIndex].length() > 5) { + instance.renderText(epdIndex, instance.content[epdIndex], updatePartial); + } else { + if (instance.content[epdIndex].length() == 2) { + instance.showChars(epdIndex, instance.content[epdIndex], updatePartial, instance.fontBig); + } else if (instance.content[epdIndex].length() > 1 && + instance.content[epdIndex].indexOf(".") == -1) { + if (instance.content[epdIndex].equals("STS")) { + instance.showDigit(epdIndex, 'S', updatePartial, instance.fontSatsymbol); + } else { + instance.showChars(epdIndex, instance.content[epdIndex], updatePartial, + instance.fontMedium); + } + } else { + instance.showDigit(epdIndex, instance.content[epdIndex].c_str()[0], + updatePartial, instance.fontBig); + } + } + + xTaskNotifyGive(instance.tasks[epdIndex]); } } } \ No newline at end of file diff --git a/src/lib/epd.hpp b/src/lib/epd.hpp index 4796776..80bed83 100644 --- a/src/lib/epd.hpp +++ b/src/lib/epd.hpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "fonts/fonts.hpp" #include "lib/config.hpp" @@ -32,39 +34,102 @@ #include "qrcodegen.h" #endif -typedef struct { - char dispNum; -} UpdateDisplayTaskItem; +struct UpdateDisplayTaskItem { + char dispNum; +}; -void forceFullRefresh(); -void setupDisplays(); -void loadFonts(const String& fontName); +struct FontFamily { + GFXfont* big; + GFXfont* medium; + GFXfont* small; +}; -void splitText(const uint dispNum, const String &top, const String &bottom, - bool partial); +class EPDManager { +public: + static EPDManager& getInstance(); -void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font); -void showChars(const uint dispNum, const String &chars, bool partial, - const GFXfont *font); + // Delete copy constructor and assignment operator + EPDManager(const EPDManager&) = delete; + EPDManager& operator=(const EPDManager&) = delete; -extern "C" void updateDisplay(void *pvParameters) noexcept; -void updateDisplayAlt(int epdIndex); -void prepareDisplayUpdateTask(void *pvParameters); + void initialize(); + void forceFullRefresh(); + void loadFonts(const String& fontName); + void setContent(const std::array& newContent, bool forceUpdate = false); + void setContent(const std::array& newContent); + std::array getCurrentContent() const; -int getBgColor(); -int getFgColor(); -void setBgColor(int color); -void setFgColor(int color); + int getBackgroundColor() const { return bgColor; } + int getForegroundColor() const { return fgColor; } + void setBackgroundColor(int color) { bgColor = color; } + void setForegroundColor(int color) { fgColor = color; } + void waitUntilNoneBusy(); -bool renderIcon(const uint dispNum, const String &text, bool partial); -void renderText(const uint dispNum, const String &text, bool partial); -void renderQr(const uint dispNum, const String &text, bool partial); +private: + EPDManager(); // Private constructor for singleton + ~EPDManager(); // Private destructor -void setEpdContent(std::array newEpdContent, - bool forceUpdate); -void setEpdContent(std::array newEpdContent); + void setupDisplay(uint dispNum, const GFXfont* font); + void splitText(uint dispNum, const String& top, const String& bottom, bool partial); + void showDigit(uint dispNum, char chr, bool partial, const GFXfont* font); + void showChars(uint dispNum, const String& chars, bool partial, const GFXfont* font); + bool renderIcon(uint dispNum, const String& text, bool partial); + void renderText(uint dispNum, const String& text, bool partial); + void renderQr(uint dispNum, const String& text, bool partial); + int16_t calculateDescent(const GFXfont* font); -void setEpdContent(std::array newEpdContent); + static void updateDisplayTask(void* pvParameters) noexcept; + static void prepareDisplayUpdateTask(void* pvParameters); -std::array getCurrentEpdContent(); -void waitUntilNoneBusy(); \ No newline at end of file + // Member variables + std::array currentContent; + std::array content; + std::array lastFullRefresh; + std::array tasks; + QueueHandle_t updateQueue; + + FontFamily antonioFonts; + FontFamily oswaldFonts; + const GFXfont* fontSmall; + const GFXfont* fontBig; + const GFXfont* fontMedium; + const GFXfont* fontSatsymbol; + + int bgColor; + int fgColor; + + std::mutex updateMutex; + std::array displayMutexes; + + // Pin configurations based on board version + #ifdef IS_BTCLOCK_REV_B + static Native_Pin EPD_DC; + static std::array EPD_CS; + static std::array EPD_BUSY; + static std::array EPD_RESET; + #elif defined(IS_BTCLOCK_V8) + static Native_Pin EPD_DC; + static std::array EPD_BUSY; + static std::array EPD_CS; + static std::array EPD_RESET; + #else + static Native_Pin EPD_DC; + static std::array EPD_CS; + static std::array EPD_BUSY; + static std::array EPD_RESET; + #endif + + // Display array + std::array, NUM_SCREENS> displays; + + static constexpr size_t UPDATE_QUEUE_SIZE = 14; + static constexpr uint32_t BUSY_TIMEOUT_COUNT = 200; + static constexpr TickType_t BUSY_RETRY_DELAY = pdMS_TO_TICKS(10); + static constexpr size_t EPD_TASK_STACK_SIZE = + #ifdef IS_BTCLOCK_V8 + 4096 + #else + 2048 + #endif + ; +}; \ No newline at end of file diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 3c996a2..6d9cce9 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -283,7 +283,7 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) } ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); - setEpdContent(textEpdContent); + EPDManager::getInstance().setContent(textEpdContent); vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250)); if (preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP)) { @@ -294,4 +294,20 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) esp_timer_start_periodic(screenRotateTimer, timerPeriod * usPerSecond); } -} \ No newline at end of file +} + +// void onNostrEvent(const String &subId, const nostr::Event &event) { +// // This is the callback that will be called when a new event is received +// if (event.kind == 9735) { +// // Parse the zap amount from the event +// uint16_t amount = parseZapAmount(event); +// if (amount > 0) { +// std::array zapContent = parseZapNotify(amount, true); +// EPDManager::getInstance().setContent(zapContent); + +// if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD)) { +// getLedHandler().queueEffect(LED_FLASH_BLOCK_NOTIFY); +// } +// } +// } +// } \ No newline at end of file diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index b42a505..59497c7 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -57,10 +57,10 @@ void onOTAProgress(unsigned int progress, unsigned int total) void onOTAStart() { - forceFullRefresh(); + EPDManager::getInstance().forceFullRefresh(); std::array epdContent = {"U", "P", "D", "A", "T", "E", "!"}; - setEpdContent(epdContent); + EPDManager::getInstance().setContent(epdContent); // Stop all timers esp_timer_stop(screenRotateTimer); esp_timer_stop(minuteTimer); diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 0692c1d..6b3241e 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -203,7 +203,7 @@ void ScreenHandler::showSystemStatusScreen() { String((int)round(ESP.getFreeHeap() / 1024)) + "/" + (int)round(ESP.getHeapSize() / 1024); setCurrentScreen(SCREEN_CUSTOM); - setEpdContent(sysStatusEpdContent); + EPDManager::getInstance().setContent(sysStatusEpdContent); } // Keep these as free functions @@ -222,7 +222,7 @@ void workerTask(void *pvParameters) { taskEpdContent = (currentScreenValue == SCREEN_BITAXE_HASHRATE) ? parseBitaxeHashRate(BitAxeFetch::getInstance().getHashRate()) : parseBitaxeBestDiff(BitAxeFetch::getInstance().getBestDiff()); - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); break; } @@ -235,7 +235,7 @@ void workerTask(void *pvParameters) { parseMiningPoolStatsDailyEarnings(MiningPoolStatsFetch::getInstance().getDailyEarnings(), MiningPoolStatsFetch::getInstance().getPool()->getDailyEarningsLabel(), *MiningPoolStatsFetch::getInstance().getPool()); - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); break; } @@ -256,13 +256,13 @@ void workerTask(void *pvParameters) { preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); } - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); break; } case TASK_FEE_UPDATE: { if (currentScreenValue == SCREEN_BLOCK_FEE_RATE) { taskEpdContent = parseBlockFees(static_cast(getBlockMedianFee())); - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); } break; } @@ -275,7 +275,7 @@ void workerTask(void *pvParameters) { if (currentScreenValue == SCREEN_HALVING_COUNTDOWN || currentScreenValue == SCREEN_BLOCK_HEIGHT) { - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); } break; } @@ -302,7 +302,7 @@ void workerTask(void *pvParameters) { for (uint i = 1; i < NUM_SCREENS; i++) { taskEpdContent[i] = timeString[i]; } - setEpdContent(taskEpdContent); + EPDManager::getInstance().setContent(taskEpdContent); } break; @@ -330,8 +330,6 @@ void setupTasks() { xTaskCreate(taskScreenRotate, "rotateScreen", 4096, NULL, tskIDLE_PRIORITY, &taskScreenRotateTaskHandle); - waitUntilNoneBusy(); - if (findScreenIndexByValue(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)) != -1) ScreenHandler::setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)); } diff --git a/src/lib/shared.cpp b/src/lib/shared.cpp index a776750..aa67768 100644 --- a/src/lib/shared.cpp +++ b/src/lib/shared.cpp @@ -179,4 +179,5 @@ void HttpHelper::end(HTTPClient* http) { http->end(); delete http; } -} \ No newline at end of file +} + diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 78fb9ea..479a52f 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -16,6 +16,8 @@ #include #include +#include +#include #include "defaults.hpp" @@ -118,4 +120,5 @@ private: static WiFiClientSecure secureClient; static bool certBundleSet; static WiFiClient insecureClient; -}; \ No newline at end of file +}; + diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 469e6e1..c0007dd 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -106,6 +106,11 @@ namespace V2Notify JsonDocument doc; DeserializationError error = deserializeMsgPack(doc, payload, length); + if (error) { + Serial.println(F("Error deserializing message")); + break; + } + V2Notify::handleV2Message(doc); break; } diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 4897811..382faa9 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -309,7 +309,7 @@ void eventSourceUpdate() { doc["leds"] = getLedStatusObject()["data"]; // Get current EPD content directly as array - std::array epdContent = getCurrentEpdContent(); + std::array epdContent = EPDManager::getInstance().getCurrentContent(); // Add EPD content arrays JsonArray data = doc["data"].to(); @@ -336,7 +336,7 @@ void onApiStatus(AsyncWebServerRequest *request) JsonDocument root = getStatusObject(); // Get current EPD content directly as array - std::array epdContent = getCurrentEpdContent(); + std::array epdContent = EPDManager::getInstance().getCurrentContent(); // Add EPD content arrays JsonArray data = root["data"].to(); @@ -378,11 +378,9 @@ void onApiActionTimerRestart(AsyncWebServerRequest *request) */ void onApiFullRefresh(AsyncWebServerRequest *request) { - forceFullRefresh(); - std::array newEpdContent = getCurrentEpdContent(); - - setEpdContent(newEpdContent, true); - + EPDManager::getInstance().forceFullRefresh(); + std::array newEpdContent = EPDManager::getInstance().getCurrentContent(); + EPDManager::getInstance().setContent(newEpdContent, true); request->send(HTTP_OK); } @@ -429,7 +427,7 @@ void onApiShowText(AsyncWebServerRequest *request) textEpdContent[i] = t[i]; } - setEpdContent(textEpdContent); + EPDManager::getInstance().setContent(textEpdContent); } ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); request->send(HTTP_OK); @@ -447,7 +445,7 @@ void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json) i++; } - setEpdContent(epdContent); + EPDManager::getInstance().setContent(epdContent); ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); request->send(HTTP_OK); @@ -475,13 +473,13 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) if (inverted) { preferences.putUInt("fgColor", GxEPD_WHITE); preferences.putUInt("bgColor", GxEPD_BLACK); - setFgColor(GxEPD_WHITE); - setBgColor(GxEPD_BLACK); + EPDManager::getInstance().setForegroundColor(GxEPD_WHITE); + EPDManager::getInstance().setBackgroundColor(GxEPD_BLACK); } else { preferences.putUInt("fgColor", GxEPD_BLACK); preferences.putUInt("bgColor", GxEPD_WHITE); - setFgColor(GxEPD_BLACK); - setBgColor(GxEPD_WHITE); + EPDManager::getInstance().setForegroundColor(GxEPD_BLACK); + EPDManager::getInstance().setBackgroundColor(GxEPD_WHITE); } Serial.printf("Setting invertedColor to %d\r\n", inverted); settingsChanged = true; @@ -680,7 +678,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) JsonDocument root; root["numScreens"] = NUM_SCREENS; - root["invertedColor"] = preferences.getBool("invertedColor", getFgColor() == GxEPD_WHITE); + root["invertedColor"] = preferences.getBool("invertedColor", EPDManager::getInstance().getForegroundColor() == GxEPD_WHITE); root["timerSeconds"] = getTimerSeconds(); root["timerRunning"] = isTimerActive(); root["minSecPriceUpd"] = preferences.getUInt( @@ -818,7 +816,7 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) const AsyncWebParameter *fgColor = request->getParam("fgColor", true); uint32_t color = strtol(fgColor->value().c_str(), NULL, 16); preferences.putUInt("fgColor", color); - setFgColor(color); + EPDManager::getInstance().setForegroundColor(color); // Serial.print(F("Setting foreground color to ")); // Serial.println(fgColor->value().c_str()); settingsChanged = true; @@ -829,7 +827,7 @@ bool processEpdColorSettings(AsyncWebServerRequest *request) uint32_t color = strtol(bgColor->value().c_str(), NULL, 16); preferences.putUInt("bgColor", color); - setBgColor(color); + EPDManager::getInstance().setBackgroundColor(color); // Serial.print(F("Setting background color to ")); // Serial.println(bgColor->value().c_str()); settingsChanged = true; @@ -1202,7 +1200,7 @@ void onApiLightsGet(AsyncWebServerRequest *request) auto& ledHandler = getLedHandler(); auto& pixels = ledHandler.getPixels(); - DynamicJsonDocument doc(1024); + JsonDocument doc; JsonArray lights = doc.createNestedArray("lights"); for (uint i = 0; i < pixels.numPixels(); i++) @@ -1225,7 +1223,7 @@ void onApiLightsPost(AsyncWebServerRequest *request, uint8_t *data, size_t len, auto& ledHandler = getLedHandler(); auto& pixels = ledHandler.getPixels(); - DynamicJsonDocument doc(1024); + JsonDocument doc; DeserializationError error = deserializeJson(doc, data); if (error) {