From 17fef80253137d39c08156fd04f4988ee43b8f0f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 26 Dec 2024 23:08:46 +0100 Subject: [PATCH] Make ScreenHandler class, fix button handling --- src/lib/bitaxe_fetch.cpp | 2 +- src/lib/block_notify.cpp | 4 +- src/lib/button_handler.cpp | 160 +++++--- src/lib/button_handler.hpp | 34 ++ src/lib/config.cpp | 6 +- src/lib/mining_pool_stats_fetch.cpp | 4 +- src/lib/nostr_notify.cpp | 2 +- src/lib/price_notify.cpp | 6 +- src/lib/screen_handler.cpp | 614 +++++++++++++--------------- src/lib/screen_handler.hpp | 44 +- src/lib/webserver.cpp | 18 +- 11 files changed, 484 insertions(+), 410 deletions(-) diff --git a/src/lib/bitaxe_fetch.cpp b/src/lib/bitaxe_fetch.cpp index 71ab90e..f439cb4 100644 --- a/src/lib/bitaxe_fetch.cpp +++ b/src/lib/bitaxe_fetch.cpp @@ -36,7 +36,7 @@ void taskBitaxeFetch(void *pvParameters) bitaxeHashrate = std::to_string(static_cast(std::round(doc["hashRate"].as()))); bitaxeBestDiff = doc["bestDiff"].as(); - if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BITAXE_HASHRATE || getCurrentScreen() == SCREEN_BITAXE_BESTDIFF)) + if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_BESTDIFF)) { WorkItem priceUpdate = {TASK_BITAXE_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 48989bd..e41fc4b 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -200,7 +200,7 @@ void processNewBlock(uint newBlockHeight) { xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); // xTaskNotifyGive(blockUpdateTaskHandle); - if (getCurrentScreen() != SCREEN_BLOCK_HEIGHT && + if (ScreenHandler::getCurrentScreen() != SCREEN_BLOCK_HEIGHT && preferences.getBool("stealFocus", DEFAULT_STEAL_FOCUS)) { uint64_t timerPeriod = 0; @@ -210,7 +210,7 @@ void processNewBlock(uint newBlockHeight) { timerPeriod = getTimerSeconds(); esp_timer_stop(screenRotateTimer); } - setCurrentScreen(SCREEN_BLOCK_HEIGHT); + ScreenHandler::setCurrentScreen(SCREEN_BLOCK_HEIGHT); if (timerPeriod > 0) { esp_timer_start_periodic(screenRotateTimer, diff --git a/src/lib/button_handler.cpp b/src/lib/button_handler.cpp index 61e6c6d..ff7e259 100644 --- a/src/lib/button_handler.cpp +++ b/src/lib/button_handler.cpp @@ -1,8 +1,7 @@ #include "button_handler.hpp" TaskHandle_t buttonTaskHandle = NULL; -const TickType_t debounceDelay = pdMS_TO_TICKS(50); -TickType_t lastDebounceTime = 0; +ButtonState buttonStates[4]; #ifdef IS_BTCLOCK_V8 #define BTN_1 256 @@ -17,65 +16,134 @@ TickType_t lastDebounceTime = 0; #endif void buttonTask(void *parameter) { - while (1) { - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - + while (1) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + TickType_t currentTime = xTaskGetTickCount(); + + std::lock_guard lock(mcpMutex); + + if (!digitalRead(MCP_INT_PIN)) { + uint16_t intFlags = mcp1.getInterruptFlagRegister(); + uint16_t intCap = mcp1.getInterruptCaptureRegister(); + + // Check button states + if (intFlags & BTN_1) handleButtonPress(0); + if (intFlags & BTN_2) handleButtonPress(1); + if (intFlags & BTN_3) handleButtonPress(2); + if (intFlags & BTN_4) handleButtonPress(3); + + // Check for button releases + for (int i = 0; i < 4; i++) { + if (buttonStates[i].isPressed) { + bool currentlyPressed = false; + switch (i) { + case 0: currentlyPressed = (intCap & BTN_1); break; + case 1: currentlyPressed = (intCap & BTN_2); break; + case 2: currentlyPressed = (intCap & BTN_3); break; + case 3: currentlyPressed = (intCap & BTN_4); break; + } + if (!currentlyPressed) { + handleButtonRelease(i); + } + } + } + + // Check for long press on pressed buttons + for (int i = 0; i < 4; i++) { + if (buttonStates[i].isPressed && !buttonStates[i].longPressHandled) { + if ((currentTime - buttonStates[i].pressStartTime) >= longPressDelay) { + handleLongPress(i); + buttonStates[i].longPressHandled = true; + } + } + } + } + + // Clear interrupt state + while (!digitalRead(MCP_INT_PIN)) { + mcp1.getInterruptCaptureRegister(); + delay(1); + } + } +} + +void handleButtonPress(int buttonIndex) { TickType_t currentTime = xTaskGetTickCount(); + ButtonState &state = buttonStates[buttonIndex]; - if ((currentTime - lastDebounceTime) >= debounceDelay) { - lastDebounceTime = currentTime; - - std::lock_guard lock(mcpMutex); - - if (!digitalRead(MCP_INT_PIN)) { - uint16_t intFlags = mcp1.getInterruptFlagRegister(); - uint16_t intCap = mcp1.getInterruptCaptureRegister(); - - // Check each button individually - if (intFlags & BTN_1) handleButton1(); - if (intFlags & BTN_2) handleButton2(); - if (intFlags & BTN_3) handleButton3(); - if (intFlags & BTN_4) handleButton4(); - } + if ((currentTime - state.lastPressTime) >= debounceDelay) { + state.isPressed = true; + state.pressStartTime = currentTime; + state.longPressHandled = false; + + // Check for double click + if ((currentTime - state.lastPressTime) <= doubleClickDelay) { + state.clickCount++; + if (state.clickCount == 2) { + handleDoubleClick(buttonIndex); + state.clickCount = 0; + } + } else { + state.clickCount = 1; + } + + state.lastPressTime = currentTime; } +} + +void handleButtonRelease(int buttonIndex) { + TickType_t currentTime = xTaskGetTickCount(); + ButtonState &state = buttonStates[buttonIndex]; - // Clear interrupt state - while (!digitalRead(MCP_INT_PIN)) { - std::lock_guard lock(mcpMutex); - mcp1.getInterruptCaptureRegister(); - delay(1); // Small delay to prevent tight loop + state.isPressed = false; + + // If this wasn't a long press or double click, handle as single click + if (!state.longPressHandled && state.clickCount == 1 && + (currentTime - state.pressStartTime) < longPressDelay) { + handleSingleClick(buttonIndex); + state.clickCount = 0; } - } } -// Helper functions to handle each button -void handleButton1() { - toggleTimerActive(); +// Button action handlers +void handleSingleClick(int buttonIndex) { + switch (buttonIndex) { + case 0: + toggleTimerActive(); + break; + case 1: + Serial.println("Button 2 single click"); + ScreenHandler::nextScreen(); + break; + case 2: + Serial.println("Button 3 single click"); + ScreenHandler::previousScreen(); + break; + case 3: + ScreenHandler::showSystemStatusScreen(); + break; + } } -void handleButton2() { - nextScreen(); +void handleDoubleClick(int buttonIndex) { + Serial.printf("Button %d double clicked\n", buttonIndex + 1); } -void handleButton3() { - previousScreen(); -} - -void handleButton4() { - showSystemStatusScreen(); +void handleLongPress(int buttonIndex) { + Serial.printf("Button %d long press detected\n", buttonIndex + 1); } void IRAM_ATTR handleButtonInterrupt() { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xTaskNotifyFromISR(buttonTaskHandle, 0, eNoAction, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken == pdTRUE) { - portYIELD_FROM_ISR(); - } + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xTaskNotifyFromISR(buttonTaskHandle, 0, eNoAction, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken == pdTRUE) { + portYIELD_FROM_ISR(); + } } void setupButtonTask() { - xTaskCreate(buttonTask, "ButtonTask", 3072, NULL, tskIDLE_PRIORITY, - &buttonTaskHandle); // Create the FreeRTOS task - // Use interrupt instead of task - attachInterrupt(MCP_INT_PIN, handleButtonInterrupt, CHANGE); + xTaskCreate(buttonTask, "ButtonTask", 3072, NULL, tskIDLE_PRIORITY, + &buttonTaskHandle); + attachInterrupt(MCP_INT_PIN, handleButtonInterrupt, FALLING); } diff --git a/src/lib/button_handler.hpp b/src/lib/button_handler.hpp index ed8ae38..f3f41a1 100644 --- a/src/lib/button_handler.hpp +++ b/src/lib/button_handler.hpp @@ -18,3 +18,37 @@ void handleButton1(); void handleButton2(); void handleButton3(); void handleButton4(); + +// New features +const TickType_t debounceDelay = pdMS_TO_TICKS(50); +const TickType_t doubleClickDelay = pdMS_TO_TICKS(300); // Maximum time between clicks for double click +const TickType_t longPressDelay = pdMS_TO_TICKS(1000); // Time to hold for long press + +// Track timing for each button +struct ButtonState { + TickType_t lastPressTime = 0; + TickType_t pressStartTime = 0; + bool isPressed = false; + uint8_t clickCount = 0; + bool longPressHandled = false; +}; + +extern ButtonState buttonStates[4]; + +#ifdef IS_BTCLOCK_V8 +#define BTN_1 256 +#define BTN_2 512 +#define BTN_3 1024 +#define BTN_4 2048 +#else +#define BTN_1 2048 +#define BTN_2 1024 +#define BTN_3 512 +#define BTN_4 256 +#endif + +void handleButtonPress(int buttonIndex); +void handleButtonRelease(int buttonIndex); +void handleSingleClick(int buttonIndex); +void handleDoubleClick(int buttonIndex); +void handleLongPress(int buttonIndex); diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 08f645e..4ae7e4b 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -285,9 +285,9 @@ void setupPreferences() setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD); if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE)) - setCurrentCurrency(preferences.getUChar("lastCurrency", CURRENCY_USD)); + ScreenHandler::setCurrentCurrency(preferences.getUChar("lastCurrency", CURRENCY_USD)); else - setCurrentCurrency(CURRENCY_USD); + ScreenHandler::setCurrentCurrency(CURRENCY_USD); if (!preferences.isKey("flDisable")) { preferences.putBool("flDisable", isWhiteVersion() ? false : true); @@ -482,7 +482,7 @@ void setupHardware() Serial.printf("Error setting pin %d to input pull up\n", i); } // Enable interrupt on CHANGE for each pin - if (!mcp1.enableInterrupt(i, CHANGE)) { + if (!mcp1.enableInterrupt(i, FALLING)) { Serial.printf("Error enabling interrupt for pin %d\n", i); } } diff --git a/src/lib/mining_pool_stats_fetch.cpp b/src/lib/mining_pool_stats_fetch.cpp index b8fed5c..9687e03 100644 --- a/src/lib/mining_pool_stats_fetch.cpp +++ b/src/lib/mining_pool_stats_fetch.cpp @@ -57,7 +57,7 @@ void taskMiningPoolStatsFetch(void *pvParameters) miningPoolStatsDailyEarnings = 0; // or any other default value } - if (workQueue != nullptr && (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS)) + if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS)) { WorkItem priceUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); @@ -78,7 +78,7 @@ void downloadMiningPoolLogoTask(void *pvParameters) { PoolFactory::downloadPoolLogo(poolName, poolInterface.get()); // If we're on the mining pool stats screen, trigger a display update - if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) { + if (ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) { WorkItem priceUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); } diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index a919e0e..20bdd01 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -276,7 +276,7 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) timerPeriod = getTimerSeconds(); esp_timer_stop(screenRotateTimer); } - setCurrentScreen(SCREEN_CUSTOM); + ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); setEpdContent(textEpdContent); vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250)); diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index ab5192b..780f651 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -133,9 +133,9 @@ void processNewPrice(uint newPrice, char currency) } lastUpdateMap[currency] = currentTime; // if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) { - if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || - getCurrentScreen() == SCREEN_SATS_PER_CURRENCY || - getCurrentScreen() == SCREEN_MARKET_CAP)) + if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BTC_TICKER || + ScreenHandler::getCurrentScreen() == SCREEN_SATS_PER_CURRENCY || + ScreenHandler::getCurrentScreen() == SCREEN_MARKET_CAP)) { WorkItem priceUpdate = {TASK_PRICE_UPDATE, currency}; xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index ea55e02..8c7213c 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -1,343 +1,315 @@ #include "screen_handler.hpp" -// TaskHandle_t priceUpdateTaskHandle; -// TaskHandle_t blockUpdateTaskHandle; -// TaskHandle_t timeUpdateTaskHandle; TaskHandle_t taskScreenRotateTaskHandle; TaskHandle_t workerTaskHandle; - - -std::array taskEpdContent = {}; -std::string priceString; - -#define WORK_QUEUE_SIZE 10 QueueHandle_t workQueue = NULL; -uint currentScreen = SCREEN_BLOCK_HEIGHT; -uint currentCurrency = CURRENCY_USD; +// Initialize static members +uint ScreenHandler::currentScreen = SCREEN_BLOCK_HEIGHT; +uint ScreenHandler::currentCurrency = CURRENCY_USD; -void workerTask(void *pvParameters) { - WorkItem receivedItem; +std::array taskEpdContent = {}; - while (1) { - // Wait for a work item to be available in the queue - if (xQueueReceive(workQueue, &receivedItem, portMAX_DELAY)) { - // Process the work item based on its type - switch (receivedItem.type) { - case TASK_BITAXE_UPDATE: { - if (getCurrentScreen() == SCREEN_BITAXE_HASHRATE) { - taskEpdContent = - parseBitaxeHashRate(getBitAxeHashRate()); - } else if (getCurrentScreen() == SCREEN_BITAXE_BESTDIFF) { - taskEpdContent = - parseBitaxeBestDiff(getBitaxeBestDiff()); - } - setEpdContent(taskEpdContent); - break; - } - case TASK_MINING_POOL_STATS_UPDATE: { - if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) { - taskEpdContent = - parseMiningPoolStatsHashRate(getMiningPoolStatsHashRate(), *getMiningPool()); - } else if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS) { - taskEpdContent = - parseMiningPoolStatsDailyEarnings(getMiningPoolStatsDailyEarnings(), getMiningPool()->getDailyEarningsLabel(), *getMiningPool()); - } - setEpdContent(taskEpdContent); - break; - } - case TASK_PRICE_UPDATE: { - uint currency = getCurrentCurrency(); - uint price = getPrice(currency); - - if (getCurrentScreen() == SCREEN_BTC_TICKER) { - taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE), - preferences.getBool("mowMode", DEFAULT_MOW_MODE), - preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT) - ); - } else if (getCurrentScreen() == SCREEN_SATS_PER_CURRENCY) { - taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); - } else { - taskEpdContent = - parseMarketCap(getBlockHeight(), price, currency, - preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); - } - - setEpdContent(taskEpdContent); - break; - } - case TASK_FEE_UPDATE: { - if (getCurrentScreen() == SCREEN_BLOCK_FEE_RATE) { - taskEpdContent = parseBlockFees(static_cast(getBlockMedianFee())); - setEpdContent(taskEpdContent); - } - break; - } - case TASK_BLOCK_UPDATE: { - if (getCurrentScreen() != SCREEN_HALVING_COUNTDOWN) { - taskEpdContent = parseBlockHeight(getBlockHeight()); - } else { - taskEpdContent = parseHalvingCountdown(getBlockHeight(), preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN)); - } - - if (getCurrentScreen() == SCREEN_HALVING_COUNTDOWN || - getCurrentScreen() == SCREEN_BLOCK_HEIGHT) { - setEpdContent(taskEpdContent); - } - break; - } - case TASK_TIME_UPDATE: { - if (getCurrentScreen() == SCREEN_TIME) { - time_t currentTime; - struct tm timeinfo; - time(¤tTime); - localtime_r(¤tTime, &timeinfo); - std::string timeString; - - String minute = String(timeinfo.tm_min); - if (minute.length() < 2) { - minute = "0" + minute; - } - - timeString = - std::to_string(timeinfo.tm_hour) + ":" + minute.c_str(); - timeString.insert(timeString.begin(), - NUM_SCREENS - timeString.length(), ' '); - taskEpdContent[0] = std::to_string(timeinfo.tm_mday) + "/" + - std::to_string(timeinfo.tm_mon + 1); - - for (uint i = 1; i < NUM_SCREENS; i++) { - taskEpdContent[i] = timeString[i]; - } - setEpdContent(taskEpdContent); - } - - break; - } - // Add more cases for additional task types - } +// Convert existing functions to static member functions +void ScreenHandler::setCurrentScreen(uint newScreen) { + if (newScreen != SCREEN_CUSTOM) { + preferences.putUInt("currentScreen", newScreen); + } + currentScreen = newScreen; + + switch (currentScreen) { + case SCREEN_TIME: { + WorkItem timeUpdate = {TASK_TIME_UPDATE, 0}; + xQueueSend(workQueue, &timeUpdate, portMAX_DELAY); + break; + } + case SCREEN_HALVING_COUNTDOWN: + case SCREEN_BLOCK_HEIGHT: { + WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + break; + } + case SCREEN_MARKET_CAP: + case SCREEN_SATS_PER_CURRENCY: + case SCREEN_BTC_TICKER: { + WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; + xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); + break; + } + case SCREEN_BLOCK_FEE_RATE: { + WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + break; + } + case SCREEN_BITAXE_BESTDIFF: + case SCREEN_BITAXE_HASHRATE: { + if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) { + WorkItem bitaxeUpdate = {TASK_BITAXE_UPDATE, 0}; + xQueueSend(workQueue, &bitaxeUpdate, portMAX_DELAY); + } else { + setCurrentScreen(SCREEN_BLOCK_HEIGHT); + return; + } + break; + } + case SCREEN_MINING_POOL_STATS_HASHRATE: + case SCREEN_MINING_POOL_STATS_EARNINGS: { + if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) { + WorkItem miningPoolStatsUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; + xQueueSend(workQueue, &miningPoolStatsUpdate, portMAX_DELAY); + } else { + setCurrentScreen(SCREEN_BLOCK_HEIGHT); + return; + } + break; + } + } + + if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle); +} + +void ScreenHandler::setCurrentCurrency(char currency) { + currentCurrency = currency; + preferences.putUChar("lastCurrency", currency); +} + +bool ScreenHandler::isCurrencySpecific(uint screen) { + switch (screen) { + case SCREEN_BTC_TICKER: + case SCREEN_SATS_PER_CURRENCY: + case SCREEN_MARKET_CAP: + return true; + default: + return false; + } +} + +bool ScreenHandler::handleCurrencyRotation(bool forward) { + if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE) && isCurrencySpecific(getCurrentScreen())) { + std::vector ac = getActiveCurrencies(); + if (ac.empty()) return false; + + std::string curCode = getCurrencyCode(getCurrentCurrency()); + auto it = std::find(ac.begin(), ac.end(), curCode); + + if (it == ac.end()) { + // Current currency not found in active currencies - initialize based on direction + setCurrentCurrency(getCurrencyChar(forward ? ac.front() : ac.back())); + setCurrentScreen(getCurrentScreen()); + return true; + } else if (forward && curCode != ac.back()) { + // Moving forward and not at last currency + setCurrentCurrency(getCurrencyChar(ac.at(std::distance(ac.begin(), it) + 1))); + setCurrentScreen(getCurrentScreen()); + return true; + } else if (!forward && curCode != ac.front()) { + // Moving backward and not at first currency + setCurrentCurrency(getCurrencyChar(ac.at(std::distance(ac.begin(), it) - 1))); + setCurrentScreen(getCurrentScreen()); + return true; + } + return false; + } + return false; +} + +int ScreenHandler::findNextVisibleScreen(int currentScreen, bool forward) { + std::vector screenMappings = getScreenNameMap(); + int newScreen; + + if (forward) { + newScreen = (currentScreen < screenMappings.size() - 1) ? + screenMappings[currentScreen + 1].value : screenMappings.front().value; + } else { + newScreen = (currentScreen > 0) ? + screenMappings[currentScreen - 1].value : screenMappings.back().value; + } + + String key = "screen" + String(newScreen) + "Visible"; + while (!preferences.getBool(key.c_str(), true)) { + currentScreen = findScreenIndexByValue(newScreen); + if (forward) { + newScreen = (currentScreen < screenMappings.size() - 1) ? + screenMappings[currentScreen + 1].value : screenMappings.front().value; + } else { + newScreen = (currentScreen > 0) ? + screenMappings[currentScreen - 1].value : screenMappings.back().value; + } + key = "screen" + String(newScreen) + "Visible"; + } + + return newScreen; +} + +void ScreenHandler::nextScreen() { + if (handleCurrencyRotation(true)) return; + int currentIndex = findScreenIndexByValue(getCurrentScreen()); + setCurrentScreen(findNextVisibleScreen(currentIndex, true)); +} + +void ScreenHandler::previousScreen() { + if (handleCurrencyRotation(false)) return; + int currentIndex = findScreenIndexByValue(getCurrentScreen()); + setCurrentScreen(findNextVisibleScreen(currentIndex, false)); +} + +void ScreenHandler::showSystemStatusScreen() { + std::array sysStatusEpdContent; + std::fill(sysStatusEpdContent.begin(), sysStatusEpdContent.end(), ""); + + String ipAddr = WiFi.localIP().toString(); + String subNet = WiFi.subnetMask().toString(); + + sysStatusEpdContent[0] = "IP/Subnet"; + + int ipAddrPos = 0; + int subnetPos = 0; + for (int i = 0; i < 4; i++) { + sysStatusEpdContent[1 + i] = ipAddr.substring(0, ipAddr.indexOf('.')) + + "/" + subNet.substring(0, subNet.indexOf('.')); + ipAddrPos = ipAddr.indexOf('.') + 1; + subnetPos = subNet.indexOf('.') + 1; + ipAddr = ipAddr.substring(ipAddrPos); + subNet = subNet.substring(subnetPos); + } + sysStatusEpdContent[NUM_SCREENS - 2] = "RAM/Status"; + + sysStatusEpdContent[NUM_SCREENS - 1] = + String((int)round(ESP.getFreeHeap() / 1024)) + "/" + + (int)round(ESP.getHeapSize() / 1024); + setCurrentScreen(SCREEN_CUSTOM); + setEpdContent(sysStatusEpdContent); +} + +// Keep these as free functions +void workerTask(void *pvParameters) { + WorkItem receivedItem; + + while (1) { + if (xQueueReceive(workQueue, &receivedItem, portMAX_DELAY)) { + uint currentScreenValue = ScreenHandler::getCurrentScreen(); + + switch (receivedItem.type) { + case TASK_BITAXE_UPDATE: { + if (currentScreenValue != SCREEN_BITAXE_HASHRATE && + currentScreenValue != SCREEN_BITAXE_BESTDIFF) break; + + taskEpdContent = (currentScreenValue == SCREEN_BITAXE_HASHRATE) ? + parseBitaxeHashRate(getBitAxeHashRate()) : + parseBitaxeBestDiff(getBitaxeBestDiff()); + setEpdContent(taskEpdContent); + break; + } + + case TASK_MINING_POOL_STATS_UPDATE: { + if (currentScreenValue != SCREEN_MINING_POOL_STATS_HASHRATE && + currentScreenValue != SCREEN_MINING_POOL_STATS_EARNINGS) break; + + taskEpdContent = (currentScreenValue == SCREEN_MINING_POOL_STATS_HASHRATE) ? + parseMiningPoolStatsHashRate(getMiningPoolStatsHashRate(), *getMiningPool()) : + parseMiningPoolStatsDailyEarnings(getMiningPoolStatsDailyEarnings(), + getMiningPool()->getDailyEarningsLabel(), *getMiningPool()); + setEpdContent(taskEpdContent); + break; + } + + case TASK_PRICE_UPDATE: { + uint currency = ScreenHandler::getCurrentCurrency(); + uint price = getPrice(currency); + + if (currentScreenValue == SCREEN_BTC_TICKER) { + taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE), + preferences.getBool("mowMode", DEFAULT_MOW_MODE), + preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT) + ); + } else if (currentScreenValue == SCREEN_SATS_PER_CURRENCY) { + taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); + } else { + taskEpdContent = + parseMarketCap(getBlockHeight(), price, currency, + preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); + } + + setEpdContent(taskEpdContent); + break; + } + case TASK_FEE_UPDATE: { + if (currentScreenValue == SCREEN_BLOCK_FEE_RATE) { + taskEpdContent = parseBlockFees(static_cast(getBlockMedianFee())); + setEpdContent(taskEpdContent); + } + break; + } + case TASK_BLOCK_UPDATE: { + if (currentScreenValue != SCREEN_HALVING_COUNTDOWN) { + taskEpdContent = parseBlockHeight(getBlockHeight()); + } else { + taskEpdContent = parseHalvingCountdown(getBlockHeight(), preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN)); + } + + if (currentScreenValue == SCREEN_HALVING_COUNTDOWN || + currentScreenValue == SCREEN_BLOCK_HEIGHT) { + setEpdContent(taskEpdContent); + } + break; + } + case TASK_TIME_UPDATE: { + if (currentScreenValue == SCREEN_TIME) { + time_t currentTime; + struct tm timeinfo; + time(¤tTime); + localtime_r(¤tTime, &timeinfo); + std::string timeString; + + String minute = String(timeinfo.tm_min); + if (minute.length() < 2) { + minute = "0" + minute; + } + + timeString = + std::to_string(timeinfo.tm_hour) + ":" + minute.c_str(); + timeString.insert(timeString.begin(), + NUM_SCREENS - timeString.length(), ' '); + taskEpdContent[0] = std::to_string(timeinfo.tm_mday) + "/" + + std::to_string(timeinfo.tm_mon + 1); + + for (uint i = 1; i < NUM_SCREENS; i++) { + taskEpdContent[i] = timeString[i]; + } + setEpdContent(taskEpdContent); + } + + break; + } + // Add more cases for additional task types + } + } } - } } void taskScreenRotate(void *pvParameters) { - for (;;) { - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - - nextScreen(); - } + for (;;) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + ScreenHandler::nextScreen(); + } } void setupTasks() { - workQueue = xQueueCreate(WORK_QUEUE_SIZE, sizeof(WorkItem)); + workQueue = xQueueCreate(WORK_QUEUE_SIZE, sizeof(WorkItem)); - xTaskCreate(workerTask, "workerTask", 4096, NULL, tskIDLE_PRIORITY, - &workerTaskHandle); + xTaskCreate(workerTask, "workerTask", 4096, NULL, tskIDLE_PRIORITY, + &workerTaskHandle); - xTaskCreate(taskScreenRotate, "rotateScreen", 4096, NULL, tskIDLE_PRIORITY, - &taskScreenRotateTaskHandle); + xTaskCreate(taskScreenRotate, "rotateScreen", 4096, NULL, tskIDLE_PRIORITY, + &taskScreenRotateTaskHandle); - waitUntilNoneBusy(); + waitUntilNoneBusy(); - if (findScreenIndexByValue(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)) != -1) - setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)); + if (findScreenIndexByValue(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)) != -1) + ScreenHandler::setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)); } -uint getCurrentScreen() { return currentScreen; } - -void setCurrentScreen(uint newScreen) { - if (newScreen != SCREEN_CUSTOM) { - preferences.putUInt("currentScreen", newScreen); - } - - currentScreen = newScreen; - - switch (currentScreen) { - case SCREEN_TIME: { - WorkItem timeUpdate = {TASK_TIME_UPDATE, 0}; - xQueueSend(workQueue, &timeUpdate, portMAX_DELAY); - // xTaskNotifyGive(timeUpdateTaskHandle); - break; - } - case SCREEN_HALVING_COUNTDOWN: - case SCREEN_BLOCK_HEIGHT: { - WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); - // xTaskNotifyGive(blockUpdateTaskHandle); - break; - } - case SCREEN_MARKET_CAP: - case SCREEN_SATS_PER_CURRENCY: - case SCREEN_BTC_TICKER: { - WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; - xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); - // xTaskNotifyGive(priceUpdateTaskHandle); - break; - } - case SCREEN_BLOCK_FEE_RATE: { - WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); - break; - } - case SCREEN_BITAXE_BESTDIFF: - case SCREEN_BITAXE_HASHRATE: { - if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) { - WorkItem bitaxeUpdate = {TASK_BITAXE_UPDATE, 0}; - xQueueSend(workQueue, &bitaxeUpdate, portMAX_DELAY); - } else { - setCurrentScreen(SCREEN_BLOCK_HEIGHT); - return; - } - break; - } - case SCREEN_MINING_POOL_STATS_HASHRATE: - case SCREEN_MINING_POOL_STATS_EARNINGS: { - if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) { - WorkItem miningPoolStatsUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0}; - xQueueSend(workQueue, &miningPoolStatsUpdate, portMAX_DELAY); - } else { - setCurrentScreen(SCREEN_BLOCK_HEIGHT); - return; - } - break; - } - } - - if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle); -} - -bool isCurrencySpecific(uint screen) { - switch (screen) { - case SCREEN_BTC_TICKER: - case SCREEN_SATS_PER_CURRENCY: - case SCREEN_MARKET_CAP: - return true; - default: - return false; - } -} - -void nextScreen() { - int currentIndex = findScreenIndexByValue(getCurrentScreen()); - std::vector screenMappings = getScreenNameMap(); - - if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE) && isCurrencySpecific(getCurrentScreen())) { - std::vector ac = getActiveCurrencies(); - std::string curCode = getCurrencyCode(getCurrentCurrency()); - if (getCurrencyCode(getCurrentCurrency()) != ac.back()) { - auto it = std::find(ac.begin(), ac.end(), curCode); - if (it != ac.end()) { - size_t index = std::distance(ac.begin(), it); - setCurrentCurrency(getCurrencyChar(ac.at(index+1))); - setCurrentScreen(getCurrentScreen()); - return; - } - } - setCurrentCurrency(getCurrencyChar(ac.front())); - } - - int newCurrentScreen; - - if (currentIndex < screenMappings.size() - 1) { - newCurrentScreen = (screenMappings[currentIndex + 1].value); - } else { - newCurrentScreen = screenMappings.front().value; - } - - String key = "screen" + String(newCurrentScreen) + "Visible"; - - while (!preferences.getBool(key.c_str(), true)) { - currentIndex = findScreenIndexByValue(newCurrentScreen); - if (currentIndex < screenMappings.size() - 1) { - newCurrentScreen = (screenMappings[currentIndex + 1].value); - } else { - newCurrentScreen = screenMappings.front().value; - } - - key = "screen" + String(newCurrentScreen) + "Visible"; - } - - setCurrentScreen(newCurrentScreen); -} - -void previousScreen() { - int currentIndex = findScreenIndexByValue(getCurrentScreen()); - std::vector screenMappings = getScreenNameMap(); - - if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE) && isCurrencySpecific(getCurrentScreen())) { - std::vector ac = getActiveCurrencies(); - std::string curCode = getCurrencyCode(getCurrentCurrency()); - if (getCurrencyCode(getCurrentCurrency()) != ac.front()) { - auto it = std::find(ac.begin(), ac.end(), curCode); - if (it != ac.end()) { - size_t index = std::distance(ac.begin(), it); - setCurrentCurrency(getCurrencyChar(ac.at(index-1))); - setCurrentScreen(getCurrentScreen()); - return; - } - } - setCurrentCurrency(getCurrencyChar(ac.back())); - - } - - - int newCurrentScreen; - - if (currentIndex > 0) { - newCurrentScreen = screenMappings[currentIndex - 1].value; - } else { - newCurrentScreen = screenMappings.back().value; - } - - String key = "screen" + String(newCurrentScreen) + "Visible"; - - while (!preferences.getBool(key.c_str(), true)) { - int currentIndex = findScreenIndexByValue(newCurrentScreen); - if (currentIndex > 0) { - newCurrentScreen = screenMappings[currentIndex - 1].value; - } else { - newCurrentScreen = screenMappings.back().value; - } - - key = "screen" + String(newCurrentScreen) + "Visible"; - } - setCurrentScreen(newCurrentScreen); -} - -void showSystemStatusScreen() { - std::array sysStatusEpdContent; - std::fill(sysStatusEpdContent.begin(), sysStatusEpdContent.end(), ""); - - - String ipAddr = WiFi.localIP().toString(); - String subNet = WiFi.subnetMask().toString(); - - sysStatusEpdContent[0] = "IP/Subnet"; - - int ipAddrPos = 0; - int subnetPos = 0; - for (int i = 0; i < 4; i++) { - sysStatusEpdContent[1 + i] = ipAddr.substring(0, ipAddr.indexOf('.')) + - "/" + subNet.substring(0, subNet.indexOf('.')); - ipAddrPos = ipAddr.indexOf('.') + 1; - subnetPos = subNet.indexOf('.') + 1; - ipAddr = ipAddr.substring(ipAddrPos); - subNet = subNet.substring(subnetPos); - } - sysStatusEpdContent[NUM_SCREENS - 2] = "RAM/Status"; - - sysStatusEpdContent[NUM_SCREENS - 1] = - String((int)round(ESP.getFreeHeap() / 1024)) + "/" + - (int)round(ESP.getHeapSize() / 1024); - setCurrentScreen(SCREEN_CUSTOM); - setEpdContent(sysStatusEpdContent); -} - -void setCurrentCurrency(char currency) { - currentCurrency = currency; - preferences.putUChar("lastCurrency", currency); -} - -uint getCurrentCurrency() { - return currentCurrency; +void cleanup() { + vQueueDelete(workQueue); + // Add any other cleanup needed } \ No newline at end of file diff --git a/src/lib/screen_handler.hpp b/src/lib/screen_handler.hpp index fcdf2ba..914af09 100644 --- a/src/lib/screen_handler.hpp +++ b/src/lib/screen_handler.hpp @@ -11,12 +11,10 @@ #include "lib/epd.hpp" #include "lib/shared.hpp" -// extern TaskHandle_t priceUpdateTaskHandle; -// extern TaskHandle_t blockUpdateTaskHandle; -// extern TaskHandle_t timeUpdateTaskHandle; +#define WORK_QUEUE_SIZE 10 + extern TaskHandle_t workerTaskHandle; extern TaskHandle_t taskScreenRotateTaskHandle; - extern QueueHandle_t workQueue; typedef enum { @@ -33,24 +31,26 @@ typedef struct { char data; } WorkItem; +class ScreenHandler { +private: + static uint currentScreen; + static uint currentCurrency; + +public: + static uint getCurrentScreen() { return currentScreen; } + static uint getCurrentCurrency() { return currentCurrency; } + static void setCurrentScreen(uint newScreen); + static void setCurrentCurrency(char currency); + static void nextScreen(); + static void previousScreen(); + static void showSystemStatusScreen(); + static bool isCurrencySpecific(uint screen); + static bool handleCurrencyRotation(bool forward); + static int findNextVisibleScreen(int currentScreen, bool forward); +}; + +// Keep as free functions since they deal with FreeRTOS tasks void workerTask(void *pvParameters); -uint getCurrentScreen(); -void setCurrentScreen(uint newScreen); -void nextScreen(); -void previousScreen(); - -void showSystemStatusScreen(); - - - -// void taskPriceUpdate(void *pvParameters); -// void taskBlockUpdate(void *pvParameters); -// void taskTimeUpdate(void *pvParameters); void taskScreenRotate(void *pvParameters); - - - void setupTasks(); -void setCurrentCurrency(char currency); - -uint getCurrentCurrency(); \ No newline at end of file +void cleanup(); \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index f1810cc..4a8a8f5 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -228,7 +228,7 @@ JsonDocument getStatusObject() { JsonDocument root; - root["currentScreen"] = getCurrentScreen(); + root["currentScreen"] = ScreenHandler::getCurrentScreen(); root["numScreens"] = NUM_SCREENS; root["timerRunning"] = isTimerActive(); root["espUptime"] = esp_timer_get_time() / 1000000; @@ -248,7 +248,7 @@ JsonDocument getStatusObject() conStatus["nostr"] = nostrConnected(); root["rssi"] = WiFi.RSSI(); - root["currency"] = getCurrencyCode(getCurrentCurrency()); + root["currency"] = getCurrencyCode(ScreenHandler::getCurrentCurrency()); #ifdef HAS_FRONTLIGHT std::vector statuses = frontlightGetStatus(); uint16_t arr[NUM_SCREENS]; @@ -386,7 +386,7 @@ void onApiShowScreen(AsyncWebServerRequest *request) { const AsyncWebParameter *p = request->getParam("s"); uint currentScreen = p->value().toInt(); - setCurrentScreen(currentScreen); + ScreenHandler::setCurrentScreen(currentScreen); } request->send(HTTP_OK); } @@ -398,9 +398,9 @@ void onApiShowScreen(AsyncWebServerRequest *request) void onApiScreenControl(AsyncWebServerRequest *request) { const String& action = request->url(); if (action.endsWith("/next")) { - nextScreen(); + ScreenHandler::nextScreen(); } else if (action.endsWith("/previous")) { - previousScreen(); + ScreenHandler::previousScreen(); } request->send(HTTP_OK); } @@ -421,7 +421,7 @@ void onApiShowText(AsyncWebServerRequest *request) setEpdContent(textEpdContent); } - setCurrentScreen(SCREEN_CUSTOM); + ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); request->send(HTTP_OK); } @@ -439,7 +439,7 @@ void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json) setEpdContent(epdContent); - setCurrentScreen(SCREEN_CUSTOM); + ScreenHandler::setCurrentScreen(SCREEN_CUSTOM); request->send(HTTP_OK); } @@ -998,8 +998,8 @@ void onApiShowCurrency(AsyncWebServerRequest *request) char curChar = getCurrencyChar(currency); - setCurrentCurrency(curChar); - setCurrentScreen(getCurrentScreen()); + ScreenHandler::setCurrentCurrency(curChar); + ScreenHandler::setCurrentScreen(ScreenHandler::getCurrentScreen()); request->send(HTTP_OK); return;