From 8906bbee10ae42372449f7fc69ac1161b73d1e52 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 22:43:02 +0100 Subject: [PATCH 01/24] EPD single instance test --- src/lib/epd.cpp | 188 ++++++++++++++++++++++++------------------------ src/lib/epd.hpp | 6 +- 2 files changed, 96 insertions(+), 98 deletions(-) diff --git a/src/lib/epd.cpp b/src/lib/epd.cpp index fa389c6..a585afd 100644 --- a/src/lib/epd.cpp +++ b/src/lib/epd.cpp @@ -69,26 +69,7 @@ EPDManager::EPDManager() , 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 - } + , display{EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET[0], &EPD_BUSY[0])} { } @@ -119,11 +100,10 @@ void EPDManager::initialize() { String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME); loadFonts(fontName); - // Initialize displays + // Initialize first display + switchToDisplay(0); std::lock_guard lockMcp(mcpMutex); - for (auto& display : displays) { - display.init(0, true, 30); - } + display.init(0, true, 30); // Create update queue and task updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); @@ -228,46 +208,48 @@ void EPDManager::waitUntilNoneBusy() { } void EPDManager::setupDisplay(uint dispNum, const GFXfont* font) { - displays[dispNum].setRotation(2); - displays[dispNum].setFont(font); - displays[dispNum].setTextColor(fgColor); - displays[dispNum].fillScreen(bgColor); + switchToDisplay(dispNum); + display.setRotation(2); + display.setFont(font); + display.setTextColor(fgColor); + display.fillScreen(bgColor); } void EPDManager::splitText(uint dispNum, const String& top, const String& bottom, bool partial) { + switchToDisplay(dispNum); if (preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) { - displays[dispNum].setRotation(1); + display.setRotation(1); } else { - displays[dispNum].setRotation(2); + display.setRotation(2); } - displays[dispNum].setFont(fontSmall); - displays[dispNum].setTextColor(fgColor); + display.setFont(fontSmall); + display.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; + display.getTextBounds(top, 0, 0, &ttbx, &ttby, &ttbw, &ttbh); + uint16_t tx = ((display.width() - ttbw) / 2) - ttbx; + uint16_t ty = ((display.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; + display.getTextBounds(bottom, 0, 0, &tbbx, &tbby, &tbbw, &tbbh); + uint16_t bx = ((display.width() - tbbw) / 2) - tbbx; + uint16_t by = ((display.height() - tbbh) / 2) - tbby + tbbh / 2 + 12; // Make separator as wide as the shortest text uint16_t lineWidth = (tbbw < ttbh) ? tbbw : ttbw; - uint16_t lineX = round((displays[dispNum].width() - lineWidth) / 2); + uint16_t lineX = round((display.width() - lineWidth) / 2); - 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, fgColor); - displays[dispNum].setCursor(bx, by); - displays[dispNum].print(bottom); + display.fillScreen(bgColor); + display.setCursor(tx, ty); + display.print(top); + display.fillRoundRect(lineX, display.height() / 2 - 3, + lineWidth, 6, 3, fgColor); + display.setCursor(bx, by); + display.print(bottom); } void EPDManager::showDigit(uint dispNum, char chr, bool partial, const GFXfont* font) { @@ -280,17 +262,17 @@ void EPDManager::showDigit(uint dispNum, char chr, bool partial, const GFXfont* int16_t tbx, tby; uint16_t tbw, tbh; - displays[dispNum].getTextBounds(str, 0, 0, &tbx, &tby, &tbw, &tbh); + display.getTextBounds(str, 0, 0, &tbx, &tby, &tbw, &tbh); - uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx; - uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby; + uint16_t x = ((display.width() - tbw) / 2) - tbx; + uint16_t y = ((display.height() - tbh) / 2) - tby; - displays[dispNum].setCursor(x, y); - displays[dispNum].print(str); + display.setCursor(x, y); + display.print(str); if (chr == '.') { - displays[dispNum].fillRect(0, 0, displays[dispNum].width(), - round(displays[dispNum].height() * 0.67), bgColor); + display.fillRect(0, 0, display.width(), + round(display.height() * 0.67), bgColor); } } @@ -299,11 +281,11 @@ void EPDManager::showChars(uint dispNum, const String& chars, bool partial, cons int16_t tbx, tby; uint16_t tbw, tbh; - displays[dispNum].getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh); + display.getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh); // 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; + uint16_t x = ((display.width() - tbw) / 2) - tbx; + uint16_t y = ((display.height() - tbh) / 2) - tby; for (size_t i = 0; i < chars.length(); i++) { char c = chars[i]; @@ -313,12 +295,12 @@ void EPDManager::showChars(uint dispNum, const String& chars, bool partial, cons 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); + display.setCursor(x, y + dotDescent + dotGlyph->height + 8); + display.print(c); } else { // For other characters, use the original y-position - displays[dispNum].setCursor(x, y); - displays[dispNum].print(c); + display.setCursor(x, y); + display.print(c); } // Move x-position for the next character @@ -327,11 +309,12 @@ void EPDManager::showChars(uint dispNum, const String& chars, bool partial, cons } 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(bgColor); - displays[dispNum].setTextColor(fgColor); + switchToDisplay(dispNum); + display.setRotation(2); + display.setPartialWindow(0, 0, display.width(), + display.height()); + display.fillScreen(bgColor); + display.setTextColor(fgColor); uint iconIndex = 0; uint width = 122; @@ -352,27 +335,28 @@ bool EPDManager::renderIcon(uint dispNum, const String& text, bool partial) { return false; } - int x_offset = (displays[dispNum].width() - logo.width) / 2; - int y_offset = (displays[dispNum].height() - logo.height) / 2; - displays[dispNum].drawInvertedBitmap(x_offset, y_offset, logo.data, - logo.width, logo.height, fgColor); + int x_offset = (display.width() - logo.width) / 2; + int y_offset = (display.height() - logo.height) / 2; + display.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, fgColor); + int x_offset = (display.width() - width) / 2; + int y_offset = (display.height() - height) / 2; + display.drawInvertedBitmap(x_offset, y_offset, epd_icons_allArray[iconIndex], + width, height, fgColor); return true; } 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); + switchToDisplay(dispNum); + display.setRotation(2); + display.setPartialWindow(0, 0, display.width(), + display.height()); + display.fillScreen(GxEPD_WHITE); + display.setTextColor(GxEPD_BLACK); + display.setCursor(0, 50); std::stringstream ss; ss.str(text.c_str()); @@ -381,16 +365,17 @@ void EPDManager::renderText(uint dispNum, const String& text, bool partial) { 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); + display.setFont(&FreeSansBold9pt7b); } else { - displays[dispNum].setFont(&FreeSans9pt7b); + display.setFont(&FreeSans9pt7b); } - displays[dispNum].println(line.c_str()); + display.println(line.c_str()); } } void EPDManager::renderQr(uint dispNum, const String& text, bool partial) { #ifdef USE_QR + switchToDisplay(dispNum); // Dynamically allocate QR buffer uint8_t* qrcode = (uint8_t*)malloc(qrcodegen_BUFFER_LEN_MAX); if (!qrcode) { @@ -405,17 +390,17 @@ void EPDManager::renderQr(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 padding = floor(float(display.width() - (size * 4)) / 2); + const int paddingY = floor(float(display.height() - (size * 4)) / 2); - displays[dispNum].setRotation(2); - displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), - displays[dispNum].height()); - displays[dispNum].fillScreen(GxEPD_WHITE); + display.setRotation(2); + display.setPartialWindow(0, 0, display.width(), + display.height()); + display.fillScreen(GxEPD_WHITE); for (int y = 0; y < size * 4; y++) { for (int x = 0; x < size * 4; x++) { - displays[dispNum].drawPixel( + display.drawPixel( padding + x, paddingY + y, qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) ? GxEPD_BLACK @@ -449,10 +434,10 @@ void EPDManager::updateDisplayTask(void* pvParameters) noexcept { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); std::lock_guard lock(instance.displayMutexes[epdIndex]); - { - std::lock_guard lockMcp(mcpMutex); - instance.displays[epdIndex].init(0, false, 40); - } + + std::lock_guard lockMcp(mcpMutex); + instance.initializeDisplay(epdIndex); + uint32_t count = 0; while (instance.EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) { @@ -469,8 +454,8 @@ void EPDManager::updateDisplayTask(void* pvParameters) noexcept { char tries = 0; while (tries < 3) { - if (instance.displays[epdIndex].displayWithReturn(updatePartial)) { - instance.displays[epdIndex].powerOff(); + if (instance.display.displayWithReturn(updatePartial)) { + instance.display.powerOff(); instance.currentContent[epdIndex] = instance.content[epdIndex]; if (!updatePartial) { instance.lastFullRefresh[epdIndex] = millis(); @@ -534,4 +519,15 @@ void EPDManager::prepareDisplayUpdateTask(void* pvParameters) { xTaskNotifyGive(instance.tasks[epdIndex]); } } -} \ No newline at end of file +} + +void EPDManager::switchToDisplay(uint dispNum) { + display.setCSPin(&EPD_CS[dispNum]); + display.setRSTPin(&EPD_RESET[dispNum]); + display.setBusyPin(&EPD_BUSY[dispNum]); +} + +void EPDManager::initializeDisplay(uint dispNum) { + switchToDisplay(dispNum); + display.init(0, false, 40); +} diff --git a/src/lib/epd.hpp b/src/lib/epd.hpp index 80bed83..1022cd2 100644 --- a/src/lib/epd.hpp +++ b/src/lib/epd.hpp @@ -80,6 +80,8 @@ private: static void updateDisplayTask(void* pvParameters) noexcept; static void prepareDisplayUpdateTask(void* pvParameters); + void switchToDisplay(uint dispNum); + void initializeDisplay(uint dispNum); // Member variables std::array currentContent; @@ -119,8 +121,8 @@ private: static std::array EPD_RESET; #endif - // Display array - std::array, NUM_SCREENS> displays; + // Single display instance + GxEPD2_BW display; static constexpr size_t UPDATE_QUEUE_SIZE = 14; static constexpr uint32_t BUSY_TIMEOUT_COUNT = 200; From 178748b94d5019a6702497749d032e3ebac537ca Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 22:47:13 +0100 Subject: [PATCH 02/24] WebUI update --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 033fe09..732dd26 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 033fe098295ab6da6568d6298b4380e51bec0b98 +Subproject commit 732dd260ea708841f0e15ee1ee64a3d5115cd475 From 0999dd08ad30bc91efe9cacebcc79f4c2f0893fe Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 23:13:05 +0100 Subject: [PATCH 03/24] Remove deprecated ArduinoJson methods --- src/lib/block_notify.cpp | 6 +++--- src/lib/v2_notify.cpp | 6 +++--- src/lib/webserver.cpp | 29 ++++------------------------- 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 0dab659..60b3034 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -158,7 +158,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) // return; // } - if (doc.containsKey("block")) + if (doc["block"].is()) { JsonObject block = doc["block"]; @@ -168,7 +168,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) processNewBlock(block["height"].as()); } - else if (doc.containsKey("mempool-blocks")) + else if (doc["mempool-blocks"].is()) { JsonArray blockInfo = doc["mempool-blocks"].as(); @@ -181,7 +181,7 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) } void processNewBlock(uint32_t newBlockHeight) { - if (currentBlockHeight <= newBlockHeight) + if (newBlockHeight <= currentBlockHeight) { return; } diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index c0007dd..560b39d 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -127,7 +127,7 @@ namespace V2Notify void handleV2Message(JsonDocument doc) { - if (doc.containsKey("blockheight")) + if (doc["blockheight"].is()) { uint newBlockHeight = doc["blockheight"].as(); @@ -138,13 +138,13 @@ namespace V2Notify processNewBlock(newBlockHeight); } - else if (doc.containsKey("blockfee")) + else if (doc["blockfee"].is()) { uint medianFee = doc["blockfee"].as(); processNewBlockFee(medianFee); } - else if (doc.containsKey("price")) + else if (doc["price"].is()) { // Iterate through the key-value pairs of the "price" object diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 382faa9..1d6a878 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -612,15 +612,15 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) } // Handle DND settings - if (settings.containsKey("dnd")) { + if (settings["dnd"].is()) { JsonObject dndObj = settings["dnd"]; auto& ledHandler = getLedHandler(); - if (dndObj.containsKey("timeBasedEnabled")) { + if (dndObj["timeBasedEnabled"].is()) { ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as()); } - if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") && - dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) { + if (dndObj["startHour"].is() && dndObj["startMinute"].is() && + dndObj["endHour"].is() && dndObj["endMinute"].is()) { ledHandler.setDNDTimeRange( dndObj["startHour"].as(), dndObj["startMinute"].as(), @@ -1251,25 +1251,4 @@ void onApiLightsPost(AsyncWebServerRequest *request, uint8_t *data, size_t len, pixels.show(); request->send(200); -} - -void onApiSettings(AsyncWebServerRequest *request, JsonVariant &json) -{ - JsonObject settings = json.as(); - auto& ledHandler = getLedHandler(); - - if (settings.containsKey("dnd")) { - JsonObject dndObj = settings["dnd"]; - if (dndObj.containsKey("timeBasedEnabled")) { - ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as()); - } - if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") && - dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) { - ledHandler.setDNDTimeRange( - dndObj["startHour"].as(), - dndObj["startMinute"].as(), - dndObj["endHour"].as(), - dndObj["endMinute"].as()); - } - } } \ No newline at end of file From 7195b7d3432bf844798ffd58f7764b13afbe73dd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 23:13:25 +0100 Subject: [PATCH 04/24] Rewrite price notify to websocketsclient --- src/lib/price_notify.cpp | 165 +++++++++++++++------------------------ src/lib/price_notify.hpp | 14 ++-- 2 files changed, 71 insertions(+), 108 deletions(-) diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 2c6fe94..6ac2f3b 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -2,103 +2,64 @@ const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; -// WebsocketsClient client; -esp_websocket_client_handle_t clientPrice = NULL; -esp_websocket_client_config_t config; +WebSocketsClient webSocket; uint currentPrice = 90000; unsigned long int lastPriceUpdate; bool priceNotifyInit = false; std::map currencyMap; std::map lastUpdateMap; -WebSocketsClient priceNotifyWs; +TaskHandle_t priceNotifyTaskHandle; + +void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length); void setupPriceNotify() { - config = {.uri = wsServerPrice, - .user_agent = USER_AGENT}; - config.cert_pem = isrg_root_x1cert; + webSocket.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin"); + webSocket.onEvent([](WStype_t type, uint8_t * payload, size_t length) { + onWebsocketPriceEvent(type, payload, length); + }); + webSocket.setReconnectInterval(5000); + webSocket.enableHeartbeat(15000, 3000, 2); - config.task_stack = (6*1024); - - - clientPrice = esp_websocket_client_init(&config); - esp_websocket_register_events(clientPrice, WEBSOCKET_EVENT_ANY, - onWebsocketPriceEvent, clientPrice); - esp_websocket_client_start(clientPrice); - - // priceNotifyWs.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin"); - // priceNotifyWs.onEvent(onWebsocketPriceEvent); - // priceNotifyWs.setReconnectInterval(5000); - // priceNotifyWs.enableHeartbeat(15000, 3000, 2); + setupPriceNotifyTask(); } +void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { + switch(type) { + case WStype_DISCONNECTED: + Serial.println(F("Price WS Connection Closed")); + break; + case WStype_CONNECTED: + { + Serial.println("Connected to " + String(wsServerPrice)); + priceNotifyInit = true; + break; + } + case WStype_TEXT: + { + JsonDocument doc; + deserializeJson(doc, (char *)payload); -// void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { -// switch(type) { -// case WStype_DISCONNECTED: -// Serial.printf("[WSc] Disconnected!\n"); -// break; -// case WStype_CONNECTED: -// { -// Serial.printf("[WSc] Connected to url: %s\n", payload); - - -// break; -// } -// case WStype_TEXT: -// String message = String((char*)payload); -// onWebsocketPriceMessage(message); -// break; -// case WStype_BIN: -// break; -// case WStype_ERROR: -// case WStype_FRAGMENT_TEXT_START: -// case WStype_FRAGMENT_BIN_START: -// case WStype_FRAGMENT: -// case WStype_PING: -// case WStype_PONG: -// case WStype_FRAGMENT_FIN: -// break; -// } -// } - -void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data) -{ - esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; - - switch (event_id) - { - case WEBSOCKET_EVENT_CONNECTED: - Serial.println("Connected to " + String(config.uri) + " WebSocket"); - priceNotifyInit = true; - - break; - case WEBSOCKET_EVENT_DATA: - onWebsocketPriceMessage(data); - break; - case WEBSOCKET_EVENT_ERROR: - Serial.println(F("Price WS Connnection error")); - break; - case WEBSOCKET_EVENT_DISCONNECTED: - Serial.println(F("Price WS Connnection Closed")); - break; - } -} - -void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data) -{ - JsonDocument doc; - - deserializeJson(doc, (char *)event_data->data_ptr); - - if (doc.containsKey("bitcoin")) - { - if (currentPrice != doc["bitcoin"].as()) - { - processNewPrice(doc["bitcoin"].as(), CURRENCY_USD); + if (doc["bitcoin"].is()) + { + if (currentPrice != doc["bitcoin"].as()) + { + processNewPrice(doc["bitcoin"].as(), CURRENCY_USD); + } + } + break; + } + case WStype_BIN: + break; + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_PING: + case WStype_PONG: + case WStype_FRAGMENT_FIN: + break; } - } } void processNewPrice(uint newPrice, char currency) @@ -175,9 +136,7 @@ void setPrice(uint newPrice, char currency) bool isPriceNotifyConnected() { - if (clientPrice == NULL) - return false; - return esp_websocket_client_is_connected(clientPrice); + return webSocket.isConnected(); } bool getPriceNotifyInit() @@ -187,24 +146,30 @@ bool getPriceNotifyInit() void stopPriceNotify() { - if (clientPrice == NULL) - return; - esp_websocket_client_close(clientPrice, pdMS_TO_TICKS(5000)); - esp_websocket_client_stop(clientPrice); - esp_websocket_client_destroy(clientPrice); - - clientPrice = NULL; + webSocket.disconnect(); + if (priceNotifyTaskHandle != NULL) { + vTaskDelete(priceNotifyTaskHandle); + priceNotifyTaskHandle = NULL; + } } void restartPriceNotify() { stopPriceNotify(); - if (clientPrice == NULL) + setupPriceNotify(); +} + +void taskPriceNotify(void *pvParameters) +{ + for (;;) { - setupPriceNotify(); - return; + webSocket.loop(); + vTaskDelay(10 / portTICK_PERIOD_MS); } - // esp_websocket_client_close(clientPrice, pdMS_TO_TICKS(5000)); - // esp_websocket_client_stop(clientPrice); - // esp_websocket_client_start(clientPrice); +} + +void setupPriceNotifyTask() +{ + xTaskCreate(taskPriceNotify, "priceNotify", (6 * 1024), NULL, tskIDLE_PRIORITY, + &priceNotifyTaskHandle); } \ No newline at end of file diff --git a/src/lib/price_notify.hpp b/src/lib/price_notify.hpp index 3591d40..6c8c6df 100644 --- a/src/lib/price_notify.hpp +++ b/src/lib/price_notify.hpp @@ -2,24 +2,22 @@ #include #include -#include -#include "block_notify.hpp" +#include #include #include "lib/screen_handler.hpp" +extern TaskHandle_t priceNotifyTaskHandle; + void setupPriceNotify(); +void setupPriceNotifyTask(); +void taskPriceNotify(void *pvParameters); -void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data); -//void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length); - -void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data); +void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length); uint getPrice(char currency); void setPrice(uint newPrice, char currency); -//void processNewPrice(uint newPrice); void processNewPrice(uint newPrice, char currency); bool isPriceNotifyConnected(); From e19cad05bced9337c727e23a33d35fba65bc257e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 5 Jan 2025 23:15:34 +0100 Subject: [PATCH 05/24] Update ESPAsyncWebserver --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index b65defd..d46c153 100644 --- a/platformio.ini +++ b/platformio.ini @@ -38,7 +38,7 @@ build_unflags = lib_deps = https://github.com/joltwallet/esp_littlefs.git bblanchon/ArduinoJson@^7.2.1 - mathieucarbou/ESPAsyncWebServer @ 3.3.23 + mathieucarbou/ESPAsyncWebServer @ 3.4.5 robtillaart/MCP23017@^0.8.0 adafruit/Adafruit NeoPixel@^1.12.3 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt From ebbec75e6bc9e51a973d13ad78f277f7be2c2865 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 00:01:34 +0100 Subject: [PATCH 06/24] Fix V2 message parsing --- src/lib/v2_notify.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 560b39d..6d9fc61 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -127,7 +127,7 @@ namespace V2Notify void handleV2Message(JsonDocument doc) { - if (doc["blockheight"].is()) + if (doc["blockheight"].is()) { uint newBlockHeight = doc["blockheight"].as(); @@ -136,16 +136,29 @@ namespace V2Notify return; } + if (debugLogEnabled()) { + Serial.print(F("processNewBlock ")); + Serial.println(newBlockHeight); + } processNewBlock(newBlockHeight); } - else if (doc["blockfee"].is()) + else if (doc["blockfee"].is()) { uint medianFee = doc["blockfee"].as(); + if (debugLogEnabled()) { + Serial.print(F("processNewBlockFee ")); + Serial.println(medianFee); + } + processNewBlockFee(medianFee); } else if (doc["price"].is()) { + if (debugLogEnabled()) { + Serial.print(F("processNewPrice ")); + Serial.println(doc["price"].as().size()); + } // Iterate through the key-value pairs of the "price" object for (JsonPair kv : doc["price"].as()) From e330984ba23504f1a214c93d48101d4dc3630bf9 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 00:43:31 +0100 Subject: [PATCH 07/24] Refactor BlockNotify to a class, use websocketsClient --- src/lib/block_notify.cpp | 363 ++++++++++++++++--------------------- src/lib/block_notify.hpp | 65 +++++-- src/lib/config.cpp | 4 +- src/lib/nostr_notify.cpp | 11 +- src/lib/ota.cpp | 4 +- src/lib/screen_handler.cpp | 14 +- src/lib/v2_notify.cpp | 6 +- src/lib/webserver.cpp | 9 +- src/main.cpp | 20 +- 9 files changed, 239 insertions(+), 257 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 60b3034..4c32a2a 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -1,186 +1,146 @@ #include "block_notify.hpp" -#include "led_handler.hpp" -char *wsServer; -esp_websocket_client_handle_t blockNotifyClient = NULL; -uint32_t currentBlockHeight = 873400; -uint16_t blockMedianFee = 1; -bool blockNotifyInit = false; -unsigned long int lastBlockUpdate; +// Initialize static members +WebSocketsClient BlockNotify::webSocket; +uint32_t BlockNotify::currentBlockHeight = 878000; +uint16_t BlockNotify::blockMedianFee = 1; +bool BlockNotify::notifyInit = false; +unsigned long int BlockNotify::lastBlockUpdate = 0; +TaskHandle_t BlockNotify::taskHandle = nullptr; -const char *mempoolWsCert = R"EOF( ------BEGIN CERTIFICATE----- -MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB -iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl -cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV -BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw -MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV -BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU -aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy -dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B -3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY -tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ -Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 -VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT -79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 -c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT -Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l -c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee -UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE -Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd -BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G -A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF -Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO -VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 -ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs -8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR -iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze -Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ -XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ -qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB -VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB -L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG -jjxDah2nGN59PRbxYvnKkKj9 ------END CERTIFICATE----- -)EOF"; - -void setupBlockNotify() +void BlockNotify::taskNotify(void* pvParameters) { - IPAddress result; - - int dnsErr = -1; - String mempoolInstance = - preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); - - while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) - { - dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result); - - if (dnsErr != 1) + for (;;) { - Serial.print(mempoolInstance); - Serial.println(F("mempool DNS could not be resolved")); - WiFi.reconnect(); - vTaskDelay(pdMS_TO_TICKS(1000)); + webSocket.loop(); + vTaskDelay(10 / portTICK_PERIOD_MS); } - } - - // Get current block height through regular API - int blockFetch = getBlockFetch(); - - if (blockFetch > currentBlockHeight) - currentBlockHeight = blockFetch; - - if (currentBlockHeight != -1) - { - lastBlockUpdate = esp_timer_get_time() / 1000000; - } - - if (workQueue != nullptr) - { - WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); - } - - // std::strcpy(wsServer, String("wss://" + mempoolInstance + - // "/api/v1/ws").c_str()); - - const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "wss" : "ws"; - - String mempoolUri = protocol + "://" + preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE) + "/api/v1/ws"; - - esp_websocket_client_config_t config = { - // .uri = "wss://mempool.space/api/v1/ws", - .task_stack = (6*1024), - .user_agent = USER_AGENT - }; - - if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) { - config.cert_pem = mempoolWsCert; - } - - config.uri = mempoolUri.c_str(); - - Serial.printf("Connecting to %s\r\n", preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE)); - - blockNotifyClient = esp_websocket_client_init(&config); - esp_websocket_register_events(blockNotifyClient, WEBSOCKET_EVENT_ANY, - onWebsocketBlockEvent, blockNotifyClient); - esp_websocket_client_start(blockNotifyClient); } -void onWebsocketBlockEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data) +void BlockNotify::setupTask() { - esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; - const String sub = "{\"action\": \"want\", \"data\":[\"blocks\", \"mempool-blocks\"]}"; - switch (event_id) - { - case WEBSOCKET_EVENT_CONNECTED: - blockNotifyInit = true; + xTaskCreate(taskNotify, "blockNotify", (6 * 1024), NULL, tskIDLE_PRIORITY, + &taskHandle); +} - Serial.println(F("Connected to Mempool.space WebSocket")); +void BlockNotify::onWebsocketEvent(WStype_t type, uint8_t* payload, size_t length) { + switch(type) { + case WStype_DISCONNECTED: { + Serial.println(F("Mempool.space WS Connection Closed")); + break; + } + case WStype_CONNECTED: { + notifyInit = true; + Serial.print(F("Connected to ")); + Serial.println(preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE)); - Serial.println(sub); - if (esp_websocket_client_send_text(blockNotifyClient, sub.c_str(), - sub.length(), portMAX_DELAY) == -1) + JsonDocument doc; + doc["action"] = "want"; + JsonArray data = doc.createNestedArray("data"); + data.add("blocks"); + data.add("mempool-blocks"); + + String sub; + serializeJson(doc, sub); + Serial.println(sub); + webSocket.sendTXT(sub.c_str()); + break; + } + case WStype_TEXT: { + JsonDocument doc; + JsonDocument filter; + filter["block"]["height"] = true; + filter["mempool-blocks"][0]["medianFee"] = true; + + deserializeJson(doc, (char*)payload, DeserializationOption::Filter(filter)); + + if (debugLogEnabled()) { + Serial.println(doc.as()); + } + + if (doc["block"].is()) + { + JsonObject block = doc["block"]; + if (block["height"].as() != currentBlockHeight) { + BlockNotify::getInstance().processNewBlock(block["height"].as()); + } + } + else if (doc["mempool-blocks"].is()) + { + JsonArray blockInfo = doc["mempool-blocks"].as(); + uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); + BlockNotify::getInstance().processNewBlockFee(medianFee); + } + break; + } + case WStype_BIN: + case WStype_ERROR: + case WStype_FRAGMENT_TEXT_START: + case WStype_FRAGMENT_BIN_START: + case WStype_FRAGMENT: + case WStype_PING: + case WStype_PONG: + case WStype_FRAGMENT_FIN: { + break; + } + } +} + +void BlockNotify::setup() +{ + IPAddress result; + int dnsErr = -1; + String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); + + while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) { - Serial.println(F("Mempool.space WS Block Subscribe Error")); + dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result); + + if (dnsErr != 1) + { + Serial.print(mempoolInstance); + Serial.println(F("mempool DNS could not be resolved")); + WiFi.reconnect(); + vTaskDelay(pdMS_TO_TICKS(1000)); + } } - break; - case WEBSOCKET_EVENT_DATA: - onWebsocketBlockMessage(data); - break; - case WEBSOCKET_EVENT_ERROR: - Serial.println(F("Mempool.space WS Connnection error")); - break; - case WEBSOCKET_EVENT_DISCONNECTED: - Serial.println(F("Mempool.space WS Connnection Closed")); - break; - } -} + // Get current block height through regular API + int blockFetch = fetchLatestBlock(); -void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) -{ - JsonDocument doc; + if (blockFetch > currentBlockHeight) + currentBlockHeight = blockFetch; - JsonDocument filter; - filter["block"]["height"] = true; - filter["mempool-blocks"][0]["medianFee"] = true; - - deserializeJson(doc, (char *)event_data->data_ptr, DeserializationOption::Filter(filter)); - - // if (error) { - // Serial.print("deserializeJson() failed: "); - // Serial.println(error.c_str()); - // return; - // } - - if (doc["block"].is()) - { - JsonObject block = doc["block"]; - - if (block["height"].as() == currentBlockHeight) { - return; + if (currentBlockHeight != -1) + { + lastBlockUpdate = esp_timer_get_time() / 1000000; } - processNewBlock(block["height"].as()); - } - else if (doc["mempool-blocks"].is()) - { - JsonArray blockInfo = doc["mempool-blocks"].as(); + if (workQueue != nullptr) + { + WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + } - uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); + const bool useSSL = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); + const int port = useSSL ? 443 : 80; - processNewBlockFee(medianFee); - } + if (useSSL) { + webSocket.beginSSL(mempoolInstance.c_str(), port, "/api/v1/ws"); +// webSocket.beginSSL("ws.btclock.dev", port, "/api/v1/ws"); - doc.clear(); + } else { + webSocket.begin(mempoolInstance.c_str(), port, "/api/v1/ws"); + } + + webSocket.onEvent(onWebsocketEvent); + webSocket.setReconnectInterval(5000); + webSocket.enableHeartbeat(15000, 3000, 2); + + setupTask(); } -void processNewBlock(uint32_t newBlockHeight) { +void BlockNotify::processNewBlock(uint32_t newBlockHeight) { if (newBlockHeight <= currentBlockHeight) { return; @@ -220,76 +180,65 @@ void processNewBlock(uint32_t newBlockHeight) { } } -void processNewBlockFee(uint16_t newBlockFee) { - if (blockMedianFee == newBlockFee) +void BlockNotify::processNewBlockFee(uint16_t newBlockFee) { + if (blockMedianFee == newBlockFee) { - return; + return; } - // Serial.printf("New median fee: %d\r\n", medianFee); blockMedianFee = newBlockFee; if (workQueue != nullptr) { - WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; - xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); } } -uint32_t getBlockHeight() { return currentBlockHeight; } - -void setBlockHeight(uint32_t newBlockHeight) -{ - currentBlockHeight = newBlockHeight; +uint32_t BlockNotify::getBlockHeight() const { + return currentBlockHeight; } -uint16_t getBlockMedianFee() { return blockMedianFee; } - -void setBlockMedianFee(uint16_t newBlockMedianFee) +void BlockNotify::setBlockHeight(uint32_t newBlockHeight) { - blockMedianFee = newBlockMedianFee; + currentBlockHeight = newBlockHeight; } -bool isBlockNotifyConnected() -{ - if (blockNotifyClient == NULL) - return false; - return esp_websocket_client_is_connected(blockNotifyClient); +uint16_t BlockNotify::getBlockMedianFee() const { + return blockMedianFee; } -bool getBlockNotifyInit() +void BlockNotify::setBlockMedianFee(uint16_t newBlockMedianFee) { - return blockNotifyInit; + blockMedianFee = newBlockMedianFee; } -void stopBlockNotify() +bool BlockNotify::isConnected() const { - if (blockNotifyClient == NULL) - return; - - esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000)); - esp_websocket_client_stop(blockNotifyClient); - esp_websocket_client_destroy(blockNotifyClient); - - blockNotifyClient = NULL; + return webSocket.isConnected(); } -void restartBlockNotify() +bool BlockNotify::isInitialized() const { - stopBlockNotify(); - - if (blockNotifyClient == NULL) { - setupBlockNotify(); - return; - } - - // esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000)); - // esp_websocket_client_stop(blockNotifyClient); - // esp_websocket_client_start(blockNotifyClient); + return notifyInit; } +void BlockNotify::stop() +{ + webSocket.disconnect(); + if (taskHandle != NULL) { + vTaskDelete(taskHandle); + taskHandle = NULL; + } +} -int getBlockFetch() { +void BlockNotify::restart() +{ + stop(); + setup(); +} + +int BlockNotify::fetchLatestBlock() { try { String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "https" : "http"; @@ -312,12 +261,12 @@ int getBlockFetch() { return 2203; // B-T-C } -uint getLastBlockUpdate() +uint BlockNotify::getLastBlockUpdate() const { - return lastBlockUpdate; + return lastBlockUpdate; } -void setLastBlockUpdate(uint lastUpdate) +void BlockNotify::setLastBlockUpdate(uint lastUpdate) { - lastBlockUpdate = lastUpdate; + lastBlockUpdate = lastUpdate; } \ No newline at end of file diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index 9c41bf0..aa99f2b 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -4,8 +4,7 @@ #include #include #include -#include - +#include #include #include @@ -14,28 +13,54 @@ #include "lib/timers.hpp" #include "lib/shared.hpp" -// using namespace websockets; +class BlockNotify { +public: + static BlockNotify& getInstance() { + static BlockNotify instance; + return instance; + } -void setupBlockNotify(); + // Delete copy constructor and assignment operator + BlockNotify(const BlockNotify&) = delete; + void operator=(const BlockNotify&) = delete; -void onWebsocketBlockEvent(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data); -void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data); + // Block notification setup and control + void setup(); + void stop(); + void restart(); + bool isConnected() const; + bool isInitialized() const; -void setBlockHeight(uint32_t newBlockHeight); -uint32_t getBlockHeight(); + // Block height management + void setBlockHeight(uint32_t newBlockHeight); + uint32_t getBlockHeight() const; -void setBlockMedianFee(uint16_t blockMedianFee); -uint16_t getBlockMedianFee(); + // Block fee management + void setBlockMedianFee(uint16_t blockMedianFee); + uint16_t getBlockMedianFee() const; -bool isBlockNotifyConnected(); -void stopBlockNotify(); -void restartBlockNotify(); + // Block processing + void processNewBlock(uint32_t newBlockHeight); + void processNewBlockFee(uint16_t newBlockFee); -void processNewBlock(uint32_t newBlockHeight); -void processNewBlockFee(uint16_t newBlockFee); + // Block fetch and update tracking + int fetchLatestBlock(); + uint getLastBlockUpdate() const; + void setLastBlockUpdate(uint lastUpdate); -bool getBlockNotifyInit(); -uint32_t getLastBlockUpdate(); -int getBlockFetch(); -void setLastBlockUpdate(uint32_t lastUpdate); + // Task handling + static void taskNotify(void* pvParameters); + +private: + BlockNotify() = default; // Private constructor for singleton + + void setupTask(); + static void onWebsocketEvent(WStype_t type, uint8_t* payload, size_t length); + + static WebSocketsClient webSocket; + static uint32_t currentBlockHeight; + static uint16_t blockMedianFee; + static bool notifyInit; + static unsigned long int lastBlockUpdate; + static TaskHandle_t taskHandle; +}; \ No newline at end of file diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 6354a7a..def33ea 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -265,7 +265,7 @@ void setupPreferences() 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)); + BlockNotify::getInstance().setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT)); setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD); if (!preferences.isKey("enableDebugLog")) { @@ -373,7 +373,7 @@ void setupWebsocketClients(void *pvParameters) } else if (dataSource == THIRD_PARTY_SOURCE) { - setupBlockNotify(); + BlockNotify::getInstance().setup(); setupPriceNotify(); } diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 6d9cce9..7df5ea1 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -79,8 +79,9 @@ void nostrTask(void *pvParameters) { DataSourceType dataSource = getDataSource(); if(dataSource == NOSTR_SOURCE) { - int blockFetch = getBlockFetch(); - processNewBlock(blockFetch); + auto& blockNotify = BlockNotify::getInstance(); + int blockFetch = blockNotify.fetchLatestBlock(); + blockNotify.processNewBlock(blockFetch); } while (1) @@ -174,11 +175,13 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even processNewPrice(obj["content"].as(), CURRENCY_USD); } else if (typeValue == "blockHeight") { - processNewBlock(obj["content"].as()); + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.processNewBlock(obj["content"].as()); } if (medianFee != 0) { - processNewBlockFee(medianFee); + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.processNewBlockFee(medianFee); } } } diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index 59497c7..2d94d48 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -74,8 +74,8 @@ void onOTAStart() ButtonHandler::suspendTask(); // stopWebServer(); - stopBlockNotify(); - stopPriceNotify(); + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.stop(); } void handleOTATask(void *parameter) diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 6b3241e..75e59aa 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -251,9 +251,8 @@ void workerTask(void *pvParameters) { } 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)); + auto& blockNotify = BlockNotify::getInstance(); + taskEpdContent = parseMarketCap(blockNotify.getBlockHeight(), price, currency, preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR)); } EPDManager::getInstance().setContent(taskEpdContent); @@ -261,16 +260,19 @@ void workerTask(void *pvParameters) { } case TASK_FEE_UPDATE: { if (currentScreenValue == SCREEN_BLOCK_FEE_RATE) { - taskEpdContent = parseBlockFees(static_cast(getBlockMedianFee())); + auto& blockNotify = BlockNotify::getInstance(); + taskEpdContent = parseBlockFees(static_cast(blockNotify.getBlockMedianFee())); EPDManager::getInstance().setContent(taskEpdContent); } break; } case TASK_BLOCK_UPDATE: { if (currentScreenValue != SCREEN_HALVING_COUNTDOWN) { - taskEpdContent = parseBlockHeight(getBlockHeight()); + auto& blockNotify = BlockNotify::getInstance(); + taskEpdContent = parseBlockHeight(blockNotify.getBlockHeight()); } else { - taskEpdContent = parseHalvingCountdown(getBlockHeight(), preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN)); + auto& blockNotify = BlockNotify::getInstance(); + taskEpdContent = parseHalvingCountdown(blockNotify.getBlockHeight(), preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN)); } if (currentScreenValue == SCREEN_HALVING_COUNTDOWN || diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 6d9fc61..a8174a1 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -131,7 +131,7 @@ namespace V2Notify { uint newBlockHeight = doc["blockheight"].as(); - if (newBlockHeight == getBlockHeight()) + if (newBlockHeight == BlockNotify::getInstance().getBlockHeight()) { return; } @@ -140,7 +140,7 @@ namespace V2Notify Serial.print(F("processNewBlock ")); Serial.println(newBlockHeight); } - processNewBlock(newBlockHeight); + BlockNotify::getInstance().processNewBlock(newBlockHeight); } else if (doc["blockfee"].is()) { @@ -151,7 +151,7 @@ namespace V2Notify Serial.println(medianFee); } - processNewBlockFee(medianFee); + BlockNotify::getInstance().processNewBlockFee(medianFee); } else if (doc["price"].is()) { diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 1d6a878..430828d 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -247,7 +247,8 @@ JsonDocument getStatusObject() JsonObject conStatus = root["connectionStatus"].to(); conStatus["price"] = isPriceNotifyConnected(); - conStatus["blocks"] = isBlockNotifyConnected(); + auto& blockNotify = BlockNotify::getInstance(); + conStatus["blocks"] = blockNotify.isConnected(); conStatus["V2"] = V2Notify::isV2NotifyConnected(); conStatus["nostr"] = nostrConnected(); @@ -906,7 +907,7 @@ void onApiStopDataSources(AsyncWebServerRequest *request) request->beginResponseStream(JSON_CONTENT); stopPriceNotify(); - stopBlockNotify(); + BlockNotify::getInstance().stop(); request->send(response); } @@ -917,9 +918,7 @@ void onApiRestartDataSources(AsyncWebServerRequest *request) request->beginResponseStream(JSON_CONTENT); restartPriceNotify(); - restartBlockNotify(); - // setupPriceNotify(); - // setupBlockNotify(); + BlockNotify::getInstance().restart(); request->send(response); } diff --git a/src/main.cpp b/src/main.cpp index c892a03..08d38ad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include "ESPAsyncWebServer.h" #include "lib/config.hpp" #include "lib/led_handler.hpp" +#include "lib/block_notify.hpp" uint wifiLostConnection; uint priceNotifyLostConnection = 0; @@ -49,7 +50,8 @@ void handleBlockNotifyDisconnection() { if ((getUptime() - blockNotifyLostConnection) > 300) { // 5 minutes timeout Serial.println(F("Block notification connection lost for 5 minutes, restarting handler...")); - restartBlockNotify(); + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.restart(); blockNotifyLostConnection = 0; } } @@ -92,13 +94,14 @@ void checkWiFiConnection() { void checkMissedBlocks() { Serial.println(F("Long time (45 min) since last block, checking if I missed anything...")); - int currentBlock = getBlockFetch(); + auto& blockNotify = BlockNotify::getInstance(); + int currentBlock = blockNotify.fetchLatestBlock(); if (currentBlock != -1) { - if (currentBlock != getBlockHeight()) { + if (currentBlock != blockNotify.getBlockHeight()) { Serial.println(F("Detected stuck block height... restarting block handler.")); - restartBlockNotify(); + blockNotify.restart(); } - setLastBlockUpdate(getUptime()); + blockNotify.setLastBlockUpdate(getUptime()); } } @@ -111,9 +114,10 @@ void monitorDataConnections() { } // Block notification monitoring - if (getBlockNotifyInit() && !isBlockNotifyConnected()) { + auto& blockNotify = BlockNotify::getInstance(); + if (blockNotify.isInitialized() && !blockNotify.isConnected()) { handleBlockNotifyDisconnection(); - } else if (blockNotifyLostConnection > 0 && isBlockNotifyConnected()) { + } else if (blockNotifyLostConnection > 0 && blockNotify.isConnected()) { blockNotifyLostConnection = 0; } @@ -125,7 +129,7 @@ void monitorDataConnections() { } // Check for missed blocks - if ((getLastBlockUpdate() - getUptime()) > 45 * 60) { + if ((blockNotify.getLastBlockUpdate() - getUptime()) > 45 * 60) { checkMissedBlocks(); } } From 1d61453563f62430d441630e642737bb8aad5406 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 01:13:09 +0100 Subject: [PATCH 08/24] Revert to esp websocket client because websocketsClient does not work --- src/lib/block_notify.cpp | 195 ++++++++++++++++++++++----------------- src/lib/block_notify.hpp | 11 +-- src/lib/v2_notify.cpp | 2 +- 3 files changed, 114 insertions(+), 94 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 4c32a2a..8502e3d 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -1,103 +1,116 @@ #include "block_notify.hpp" // Initialize static members -WebSocketsClient BlockNotify::webSocket; +esp_websocket_client_handle_t BlockNotify::wsClient = nullptr; uint32_t BlockNotify::currentBlockHeight = 878000; uint16_t BlockNotify::blockMedianFee = 1; bool BlockNotify::notifyInit = false; unsigned long int BlockNotify::lastBlockUpdate = 0; TaskHandle_t BlockNotify::taskHandle = nullptr; -void BlockNotify::taskNotify(void* pvParameters) -{ - for (;;) - { - webSocket.loop(); - vTaskDelay(10 / portTICK_PERIOD_MS); - } -} +const char* BlockNotify::mempoolWsCert = R"EOF( +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- +)EOF"; -void BlockNotify::setupTask() -{ - xTaskCreate(taskNotify, "blockNotify", (6 * 1024), NULL, tskIDLE_PRIORITY, - &taskHandle); -} +void BlockNotify::onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { + esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; + BlockNotify& instance = BlockNotify::getInstance(); -void BlockNotify::onWebsocketEvent(WStype_t type, uint8_t* payload, size_t length) { - switch(type) { - case WStype_DISCONNECTED: { - Serial.println(F("Mempool.space WS Connection Closed")); - break; - } - case WStype_CONNECTED: { + switch (event_id) { + case WEBSOCKET_EVENT_CONNECTED: + { notifyInit = true; Serial.print(F("Connected to ")); Serial.println(preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE)); JsonDocument doc; doc["action"] = "want"; - JsonArray data = doc.createNestedArray("data"); - data.add("blocks"); - data.add("mempool-blocks"); + JsonArray dataArray = doc.createNestedArray("data"); + dataArray.add("blocks"); + dataArray.add("mempool-blocks"); String sub; serializeJson(doc, sub); - Serial.println(sub); - webSocket.sendTXT(sub.c_str()); + esp_websocket_client_send_text(wsClient, sub.c_str(), sub.length(), portMAX_DELAY); break; } - case WStype_TEXT: { - JsonDocument doc; - JsonDocument filter; - filter["block"]["height"] = true; - filter["mempool-blocks"][0]["medianFee"] = true; - - deserializeJson(doc, (char*)payload, DeserializationOption::Filter(filter)); - - if (debugLogEnabled()) { - Serial.println(doc.as()); - } - - if (doc["block"].is()) - { - JsonObject block = doc["block"]; - if (block["height"].as() != currentBlockHeight) { - BlockNotify::getInstance().processNewBlock(block["height"].as()); - } - } - else if (doc["mempool-blocks"].is()) - { - JsonArray blockInfo = doc["mempool-blocks"].as(); - uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); - BlockNotify::getInstance().processNewBlockFee(medianFee); - } + case WEBSOCKET_EVENT_DATA: + instance.onWebsocketMessage(data); break; - } - case WStype_BIN: - case WStype_ERROR: - case WStype_FRAGMENT_TEXT_START: - case WStype_FRAGMENT_BIN_START: - case WStype_FRAGMENT: - case WStype_PING: - case WStype_PONG: - case WStype_FRAGMENT_FIN: { + + case WEBSOCKET_EVENT_DISCONNECTED: + Serial.println(F("Mempool.space WS Connection Closed")); + break; + + case WEBSOCKET_EVENT_ERROR: + Serial.println(F("Mempool.space WS Connection Error")); break; - } } } -void BlockNotify::setup() -{ +void BlockNotify::onWebsocketMessage(esp_websocket_event_data_t *data) { + JsonDocument doc; + JsonDocument filter; + filter["block"]["height"] = true; + filter["mempool-blocks"][0]["medianFee"] = true; + + deserializeJson(doc, (char*)data->data_ptr, DeserializationOption::Filter(filter)); + + if (doc["block"].is()) { + JsonObject block = doc["block"]; + if (block["height"].as() != currentBlockHeight) { + processNewBlock(block["height"].as()); + } + } + else if (doc["mempool-blocks"].is()) { + JsonArray blockInfo = doc["mempool-blocks"].as(); + uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); + processNewBlockFee(medianFee); + } +} + +void BlockNotify::setup() { IPAddress result; int dnsErr = -1; String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); - while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) - { + while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) { dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result); - if (dnsErr != 1) - { + if (dnsErr != 1) { Serial.print(mempoolInstance); Serial.println(F("mempool DNS could not be resolved")); WiFi.reconnect(); @@ -111,35 +124,39 @@ void BlockNotify::setup() if (blockFetch > currentBlockHeight) currentBlockHeight = blockFetch; - if (currentBlockHeight != -1) - { + if (currentBlockHeight != -1) { lastBlockUpdate = esp_timer_get_time() / 1000000; } - if (workQueue != nullptr) - { + if (workQueue != nullptr) { WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); } const bool useSSL = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); - const int port = useSSL ? 443 : 80; + const String protocol = useSSL ? "wss" : "ws"; + String wsUri = protocol + "://" + mempoolInstance + "/api/v1/ws"; + + esp_websocket_client_config_t config = { + .task_stack = (6*1024), + .user_agent = USER_AGENT + }; if (useSSL) { - webSocket.beginSSL(mempoolInstance.c_str(), port, "/api/v1/ws"); -// webSocket.beginSSL("ws.btclock.dev", port, "/api/v1/ws"); - - } else { - webSocket.begin(mempoolInstance.c_str(), port, "/api/v1/ws"); + config.cert_pem = mempoolWsCert; } - webSocket.onEvent(onWebsocketEvent); - webSocket.setReconnectInterval(5000); - webSocket.enableHeartbeat(15000, 3000, 2); + config.uri = wsUri.c_str(); - setupTask(); + Serial.printf("Connecting to %s\r\n", mempoolInstance.c_str()); + + wsClient = esp_websocket_client_init(&config); + esp_websocket_register_events(wsClient, WEBSOCKET_EVENT_ANY, onWebsocketEvent, wsClient); + esp_websocket_client_start(wsClient); } + + void BlockNotify::processNewBlock(uint32_t newBlockHeight) { if (newBlockHeight <= currentBlockHeight) { @@ -215,7 +232,9 @@ void BlockNotify::setBlockMedianFee(uint16_t newBlockMedianFee) bool BlockNotify::isConnected() const { - return webSocket.isConnected(); + if (wsClient == NULL) + return false; + return esp_websocket_client_is_connected(wsClient); } bool BlockNotify::isInitialized() const @@ -225,11 +244,13 @@ bool BlockNotify::isInitialized() const void BlockNotify::stop() { - webSocket.disconnect(); - if (taskHandle != NULL) { - vTaskDelete(taskHandle); - taskHandle = NULL; - } + if (wsClient == NULL) + return; + + esp_websocket_client_close(wsClient, portMAX_DELAY); + esp_websocket_client_stop(wsClient); + esp_websocket_client_destroy(wsClient); + wsClient = NULL; } void BlockNotify::restart() diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index aa99f2b..15aabee 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -48,16 +48,15 @@ public: uint getLastBlockUpdate() const; void setLastBlockUpdate(uint lastUpdate); - // Task handling - static void taskNotify(void* pvParameters); - private: BlockNotify() = default; // Private constructor for singleton void setupTask(); - static void onWebsocketEvent(WStype_t type, uint8_t* payload, size_t length); + static void onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data); + void onWebsocketMessage(esp_websocket_event_data_t *data); - static WebSocketsClient webSocket; + static const char* mempoolWsCert; + static esp_websocket_client_handle_t wsClient; static uint32_t currentBlockHeight; static uint16_t blockMedianFee; static bool notifyInit; diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index a8174a1..4d23f3a 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -176,7 +176,7 @@ namespace V2Notify for (;;) { webSocket.loop(); - vTaskDelay(10 / portTICK_PERIOD_MS); + vTaskDelay(pdMS_TO_TICKS(10)); } } From bf64b2f64f26ca189ade22a40c0928d0b36b3b9e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 01:27:13 +0100 Subject: [PATCH 09/24] Merge root certificates --- src/lib/block_notify.cpp | 32 +++++++++++++++++++ src/lib/shared.cpp | 66 ++++++++++++++++++++-------------------- src/lib/shared.hpp | 2 +- 3 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 8502e3d..20e6267 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -43,6 +43,38 @@ VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG jjxDah2nGN59PRbxYvnKkKj9 -----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- )EOF"; void BlockNotify::onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { diff --git a/src/lib/shared.cpp b/src/lib/shared.cpp index aa67768..b8efc0c 100644 --- a/src/lib/shared.cpp +++ b/src/lib/shared.cpp @@ -40,39 +40,39 @@ // "MrY=\n" // "-----END CERTIFICATE-----\n"; -const char* isrg_root_x1cert = R"EOF( ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)EOF"; +// const char* isrg_root_x1cert = R"EOF( +// -----BEGIN CERTIFICATE----- +// MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +// TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +// cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +// WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +// ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +// MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +// h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +// 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +// A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +// T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +// B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +// B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +// KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +// OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +// jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +// qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +// rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +// HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +// hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +// ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +// 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +// NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +// ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +// TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +// jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +// oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +// 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +// mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +// emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +// -----END CERTIFICATE----- +// )EOF"; #ifdef TEST_SCREENS diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 479a52f..54d48aa 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -68,7 +68,7 @@ const int usPerSecond = 1000000; const int usPerMinute = 60 * usPerSecond; // extern const char *github_root_ca; -extern const char *isrg_root_x1cert; +// extern const char *isrg_root_x1cert; extern const uint8_t rootca_crt_bundle_start[] asm("_binary_x509_crt_bundle_start"); // extern const uint8_t ocean_logo_comp[] asm("_binary_ocean_gz_start"); From 963f3b10b7a42ebfb50aba1e71b20d6fe8557dcc Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 6 Jan 2025 01:30:46 +0100 Subject: [PATCH 10/24] Update WebUI --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 732dd26..91e60d2 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 732dd260ea708841f0e15ee1ee64a3d5115cd475 +Subproject commit 91e60d2f4cd7437075a6e10ea0f395b2650ed531 From 1083a3222bf71ab96d27196efa6666e4093bbb3b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 8 Jan 2025 02:14:33 +0100 Subject: [PATCH 11/24] Add local public pool support --- data | 2 +- dependencies.lock | 2 +- src/lib/defaults.hpp | 1 + src/lib/mining_pool/pool_factory.cpp | 2 ++ src/lib/mining_pool/pool_factory.hpp | 3 +++ src/lib/mining_pool/public_pool/local_public_pool.cpp | 11 +++++++++++ src/lib/mining_pool/public_pool/local_public_pool.hpp | 11 +++++++++++ src/lib/v2_notify.cpp | 4 ---- src/lib/webserver.cpp | 5 ++++- 9 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 src/lib/mining_pool/public_pool/local_public_pool.cpp create mode 100644 src/lib/mining_pool/public_pool/local_public_pool.hpp diff --git a/data b/data index 91e60d2..e0d539a 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 91e60d2f4cd7437075a6e10ea0f395b2650ed531 +Subproject commit e0d539a8a3d29669ccc116311870f317b0d9dfcc diff --git a/dependencies.lock b/dependencies.lock index c338e6c..f52d5f2 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.7 -manifest_hash: cd2f3ee15e776d949eb4ea4eddc8f39b30c2a7905050850eed01ab4928143cff +manifest_hash: 04e75badb795f8a851d8b088baff06d145ecf7a66457d960c2f4ede17b45ef05 target: esp32s3 version: 1.0.0 diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 29d7eee..a662344 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -60,6 +60,7 @@ #define DEFAULT_MINING_POOL_STATS_ENABLED false #define DEFAULT_MINING_POOL_NAME "ocean" #define DEFAULT_MINING_POOL_USER "38Qkkei3SuF1Eo45BaYmRHUneRD54yyTFy" // Random actual Ocean hasher +#define DEFAULT_LOCAL_POOL_ENDPOINT "umbrel.local:2019" #define DEFAULT_ZAP_NOTIFY_ENABLED false #define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422" diff --git a/src/lib/mining_pool/pool_factory.cpp b/src/lib/mining_pool/pool_factory.cpp index c353996..45bc6ce 100644 --- a/src/lib/mining_pool/pool_factory.cpp +++ b/src/lib/mining_pool/pool_factory.cpp @@ -5,6 +5,7 @@ const char* PoolFactory::MINING_POOL_NAME_NODERUNNERS = "noderunners"; const char* PoolFactory::MINING_POOL_NAME_BRAIINS = "braiins"; const char* PoolFactory::MINING_POOL_NAME_SATOSHI_RADIO = "satoshi_radio"; const char* PoolFactory::MINING_POOL_NAME_PUBLIC_POOL = "public_pool"; +const char* PoolFactory::MINING_POOL_NAME_LOCAL_PUBLIC_POOL = "local_public_pool"; const char* PoolFactory::MINING_POOL_NAME_GOBRRR_POOL = "gobrrr_pool"; const char* PoolFactory::MINING_POOL_NAME_CKPOOL = "ckpool"; const char* PoolFactory::MINING_POOL_NAME_EU_CKPOOL = "eu_ckpool"; @@ -17,6 +18,7 @@ std::unique_ptr PoolFactory::createPool(const std::string& {MINING_POOL_NAME_BRAIINS, []() { return std::make_unique(); }}, {MINING_POOL_NAME_SATOSHI_RADIO, []() { return std::make_unique(); }}, {MINING_POOL_NAME_PUBLIC_POOL, []() { return std::make_unique(); }}, + {MINING_POOL_NAME_LOCAL_PUBLIC_POOL, []() { return std::make_unique(); }}, {MINING_POOL_NAME_GOBRRR_POOL, []() { return std::make_unique(); }}, {MINING_POOL_NAME_CKPOOL, []() { return std::make_unique(); }}, {MINING_POOL_NAME_EU_CKPOOL, []() { return std::make_unique(); }} diff --git a/src/lib/mining_pool/pool_factory.hpp b/src/lib/mining_pool/pool_factory.hpp index 951dbe5..9885d74 100644 --- a/src/lib/mining_pool/pool_factory.hpp +++ b/src/lib/mining_pool/pool_factory.hpp @@ -10,6 +10,7 @@ #include "ocean/ocean_pool.hpp" #include "satoshi_radio/satoshi_radio_pool.hpp" #include "public_pool/public_pool.hpp" +#include "public_pool/local_public_pool.hpp" #include "gobrrr_pool/gobrrr_pool.hpp" #include "ckpool/ckpool.hpp" #include "ckpool/eu_ckpool.hpp" @@ -28,6 +29,7 @@ class PoolFactory { MINING_POOL_NAME_SATOSHI_RADIO, MINING_POOL_NAME_BRAIINS, MINING_POOL_NAME_PUBLIC_POOL, + MINING_POOL_NAME_LOCAL_PUBLIC_POOL, MINING_POOL_NAME_GOBRRR_POOL, MINING_POOL_NAME_CKPOOL, MINING_POOL_NAME_EU_CKPOOL @@ -55,6 +57,7 @@ class PoolFactory { static const char* MINING_POOL_NAME_BRAIINS; static const char* MINING_POOL_NAME_SATOSHI_RADIO; static const char* MINING_POOL_NAME_PUBLIC_POOL; + static const char* MINING_POOL_NAME_LOCAL_PUBLIC_POOL; static const char* MINING_POOL_NAME_GOBRRR_POOL; static const char* MINING_POOL_NAME_CKPOOL; static const char* MINING_POOL_NAME_EU_CKPOOL; diff --git a/src/lib/mining_pool/public_pool/local_public_pool.cpp b/src/lib/mining_pool/public_pool/local_public_pool.cpp new file mode 100644 index 0000000..048197c --- /dev/null +++ b/src/lib/mining_pool/public_pool/local_public_pool.cpp @@ -0,0 +1,11 @@ +#include "local_public_pool.hpp" +#include "lib/shared.hpp" +#include "lib/defaults.hpp" + +std::string LocalPublicPool::getEndpoint() const { + return preferences.getString("localPoolEndpoint", DEFAULT_LOCAL_POOL_ENDPOINT).c_str(); +} + +std::string LocalPublicPool::getApiUrl() const { + return "http://" + getEndpoint() + "/api/client/" + poolUser; +} \ No newline at end of file diff --git a/src/lib/mining_pool/public_pool/local_public_pool.hpp b/src/lib/mining_pool/public_pool/local_public_pool.hpp new file mode 100644 index 0000000..a9e37ad --- /dev/null +++ b/src/lib/mining_pool/public_pool/local_public_pool.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "public_pool.hpp" + +class LocalPublicPool : public PublicPool { +public: + std::string getApiUrl() const override; + std::string getDisplayLabel() const override { return "LOCAL/POOL"; } +private: + std::string getEndpoint() const; +}; \ No newline at end of file diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index 4d23f3a..b915518 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -155,10 +155,6 @@ namespace V2Notify } else if (doc["price"].is()) { - if (debugLogEnabled()) { - Serial.print(F("processNewPrice ")); - Serial.println(doc["price"].as().size()); - } // Iterate through the key-value pairs of the "price" object for (JsonPair kv : doc["price"].as()) diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 430828d..fef1634 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -5,7 +5,7 @@ static const char* JSON_CONTENT = "application/json"; static const char *const PROGMEM strSettings[] = { - "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName"}; + "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName", "localPoolEndpoint"}; static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"}; @@ -696,6 +696,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); root["mempoolSecure"] = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); + // Local pool settings + root["localPoolEndpoint"] = preferences.getString("localPoolEndpoint", DEFAULT_LOCAL_POOL_ENDPOINT); + // Nostr settings (used for NOSTR_SOURCE or when zapNotify is enabled) root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB); root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY); From b01003f07549cbd26e89b871bba4004a0356813b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 15 Jan 2025 22:09:05 +0100 Subject: [PATCH 12/24] fix: Never write to LED0 --- src/lib/led_handler.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 3cdb953..453b067 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -535,7 +535,7 @@ void LedHandler::frontlightSetBrightness(uint brightness) { } for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { - flArray.setPWM(ledPin, 0, brightness); + flArray.setPWM(ledPin + 1, 0, brightness); } } @@ -543,7 +543,7 @@ std::vector LedHandler::frontlightGetStatus() { std::vector statuses; for (int ledPin = 1; ledPin <= NUM_SCREENS; ledPin++) { uint16_t a = 0, b = 0; - flArray.getPWM(ledPin, &a, &b); + flArray.getPWM(ledPin + 1, &a, &b); statuses.push_back(round(b - a / 4096)); } return statuses; @@ -576,7 +576,7 @@ void LedHandler::frontlightFadeInAll(int flDelayTime, bool staggered) { } else { for (int dutyCycle = 0; dutyCycle <= maxBrightness; dutyCycle += FL_FADE_STEP) { for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { - flArray.setPWM(ledPin, 0, dutyCycle); + flArray.setPWM(ledPin + 1, 0, dutyCycle); } vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } @@ -611,7 +611,7 @@ void LedHandler::frontlightFadeOutAll(int flDelayTime, bool staggered) { } else { for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= FL_FADE_STEP) { for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { - flArray.setPWM(ledPin, 0, dutyCycle); + flArray.setPWM(ledPin + 1, 0, dutyCycle); } vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } @@ -628,7 +628,7 @@ void LedHandler::frontlightFadeIn(uint num, int flDelayTime) { } for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) { - flArray.setPWM(num, 0, dutyCycle); + flArray.setPWM(num + 1, 0, dutyCycle); vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } } @@ -639,7 +639,7 @@ void LedHandler::frontlightFadeOut(uint num, int flDelayTime) { } for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) { - flArray.setPWM(num, 0, dutyCycle); + flArray.setPWM(num + 1, 0, dutyCycle); vTaskDelay(pdMS_TO_TICKS(flDelayTime)); } } From 9ea021086490c53845508e2a6eac27b6f016b63e Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Thu, 16 Jan 2025 00:30:40 +0100 Subject: [PATCH 13/24] fix: set better defaults for frontlight enabled devices --- data | 2 +- src/lib/defaults.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data b/data index e0d539a..68207a7 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit e0d539a8a3d29669ccc116311870f317b0d9dfcc +Subproject commit 68207a7d95b6cb16df5c991b84ff40c25c23755b diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index a662344..55da6e3 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -46,8 +46,8 @@ #define DEFAULT_LUX_LIGHT_TOGGLE 128 #define DEFAULT_FL_OFF_WHEN_DARK true -#define DEFAULT_FL_ALWAYS_ON false -#define DEFAULT_FL_FLASH_ON_UPDATE false +#define DEFAULT_FL_ALWAYS_ON true +#define DEFAULT_FL_FLASH_ON_UPDATE true #define DEFAULT_LED_STATUS false #define DEFAULT_TIMER_ACTIVE true From 678a4ba0998121d78fa582c2353d44a92866bafc Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sun, 19 Jan 2025 22:32:04 +0100 Subject: [PATCH 14/24] fix: Set WiFi country to NL for scanning --- src/lib/config.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/lib/config.cpp b/src/lib/config.cpp index def33ea..16cb80a 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -132,9 +132,25 @@ void setup() void setupWifi() { WiFi.onEvent(WiFiEvent); + + // wifi_country_t country = { + // .cc = "NL", + // .schan = 1, + // .nchan = 13, + // .policy = WIFI_COUNTRY_POLICY_MANUAL + // }; + + // esp_err_t err = esp_wifi_set_country(&country); + // if (err != ESP_OK) { + // Serial.printf("Failed to set country: %d\n", err); + // } + WiFi.setAutoConnect(true); WiFi.setAutoReconnect(true); WiFi.begin(); + + + if (preferences.getInt("txPower", DEFAULT_TX_POWER)) { if (WiFi.setTxPower( @@ -172,6 +188,7 @@ void setupWifi() wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT)); wm.setWiFiAutoReconnect(false); wm.setDebugOutput(false); + wm.setCountry("NL"); wm.setConfigPortalBlocking(true); wm.setAPCallback([&](WiFiManager *wifiManager) From 3265eec30886a6ff3b6b4f54eb96b36773133cbc Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Mon, 20 Jan 2025 12:05:48 +0100 Subject: [PATCH 15/24] chore: update dependencies and make eventsource use static jsondocument --- platformio.ini | 13 ++++++++----- src/lib/webserver.cpp | 15 ++++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/platformio.ini b/platformio.ini index d46c153..1eb178a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,17 +30,17 @@ build_flags = -DLAST_BUILD_TIME=$UNIX_TIME -DARDUINO_USB_CDC_ON_BOOT -DCORE_DEBUG_LEVEL=0 - -D DEFAULT_BOOT_TEXT=\"BTCLOCK\" + -D CONFIG_ASYNC_TCP_STACK_SIZE=16384 -fexceptions build_unflags = -Werror=all -fno-exceptions lib_deps = https://github.com/joltwallet/esp_littlefs.git - bblanchon/ArduinoJson@^7.2.1 - mathieucarbou/ESPAsyncWebServer @ 3.4.5 + bblanchon/ArduinoJson@^7.3.0 + mathieucarbou/ESPAsyncWebServer @ 3.6.0 robtillaart/MCP23017@^0.8.0 - adafruit/Adafruit NeoPixel@^1.12.3 + adafruit/Adafruit NeoPixel@^1.12.4 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 @@ -79,9 +79,10 @@ build_flags = -D I2C_SDA_PIN=35 -D I2C_SCK_PIN=36 -D HAS_FRONTLIGHT - -D PCA_OE_PIN=45 + -D PCA_OE_PIN=48 -D PCA_I2C_ADDR=0x42 -D IS_HW_REV_B + lib_deps = ${btclock_base.lib_deps} robtillaart/PCA9685@^0.7.1 @@ -100,6 +101,7 @@ build_flags = -D USE_QR -D VERSION_EPD_2_13 -D HW_REV=\"REV_A_EPD_2_13\" + -D CONFIG_ARDUINO_MAIN_TASK_STACK_SIZE=16384 platform_packages = platformio/tool-mklittlefs@^1.203.210628 earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 @@ -112,6 +114,7 @@ build_flags = -D USE_QR -D VERSION_EPD_2_13 -D HW_REV=\"REV_B_EPD_2_13\" + -D CONFIG_ARDUINO_MAIN_TASK_STACK_SIZE=16384 platform_packages = platformio/tool-mklittlefs@^1.203.210628 earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index fef1634..cfb5d62 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -30,7 +30,8 @@ TaskHandle_t eventSourceTaskHandle; void setupWebserver() { events.onConnect([](AsyncEventSourceClient *client) - { client->send("welcome", NULL, millis(), 1000); }); + { client->send("welcome", NULL, millis(), 1000); + }); server.addHandler(&events); AsyncStaticWebHandler &staticHandler = server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); @@ -306,14 +307,18 @@ JsonDocument getLedStatusObject() void eventSourceUpdate() { if (!events.count()) return; - JsonDocument doc = getStatusObject(); - doc["leds"] = getLedStatusObject()["data"]; + static JsonDocument doc; + doc.clear(); + + JsonDocument root = getStatusObject(); + + root["leds"] = getLedStatusObject()["data"]; // Get current EPD content directly as array std::array epdContent = EPDManager::getInstance().getCurrentContent(); // Add EPD content arrays - JsonArray data = doc["data"].to(); + JsonArray data = root["data"].to(); // Copy array elements directly for(const auto& content : epdContent) { @@ -321,7 +326,7 @@ void eventSourceUpdate() { } String buffer; - serializeJson(doc, buffer); + serializeJson(root, buffer); events.send(buffer.c_str(), "status"); } From 0b1a362b53d44aa9c31b1bfd13a1781f92a585c4 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 19 Feb 2025 14:12:16 +0100 Subject: [PATCH 16/24] chore: dependency updates --- platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index 1eb178a..f9dc11e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,7 +15,7 @@ default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd, [env] [btclock_base] -platform = espressif32 @ ^6.9.0 +platform = espressif32 @ ^6.10.0 framework = arduino, espidf monitor_speed = 115200 monitor_filters = esp32_exception_decoder, colorize @@ -38,8 +38,8 @@ build_unflags = lib_deps = https://github.com/joltwallet/esp_littlefs.git bblanchon/ArduinoJson@^7.3.0 - mathieucarbou/ESPAsyncWebServer @ 3.6.0 - robtillaart/MCP23017@^0.8.0 + esp32async/ESPAsyncWebServer @ 3.7.0 + robtillaart/MCP23017@^0.9.0 adafruit/Adafruit NeoPixel@^1.12.4 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt https://github.com/dsbaars/GxEPD2#universal_pin From e4ac3c5c948372f6cc58048d24656b9ec0f0b187 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 19 Feb 2025 15:15:53 +0100 Subject: [PATCH 17/24] feat: switch to replaceable events for nostr source --- data | 2 +- dependencies.lock | 2 +- src/lib/nostr_notify.cpp | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/data b/data index 68207a7..0116cd6 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 68207a7d95b6cb16df5c991b84ff40c25c23755b +Subproject commit 0116cd68cdfdf383823f74e0f9665a1700cf0500 diff --git a/dependencies.lock b/dependencies.lock index f52d5f2..c338e6c 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.7 -manifest_hash: 04e75badb795f8a851d8b088baff06d145ecf7a66457d960c2f4ede17b45ef05 +manifest_hash: cd2f3ee15e776d949eb4ea4eddc8f39b30c2a7905050850eed01ab4928143cff target: esp32s3 version: 1.0.0 diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp index 7df5ea1..dda24e1 100644 --- a/src/lib/nostr_notify.cpp +++ b/src/lib/nostr_notify.cpp @@ -41,7 +41,7 @@ void setupNostrNotify(bool asDatasource, bool zapNotify) {relay}, {// First filter { - {"kinds", {"1"}}, + {"kinds", {"12203"}}, {"since", {String(getMinutesAgo(60))}}, {"authors", {pubKey}}, }}, @@ -146,6 +146,7 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even // Use direct value access instead of multiple comparisons String typeValue; uint medianFee = 0; + uint blockHeight = 0; for (JsonArray tag : tags) { if (tag.size() != 2) continue; @@ -166,6 +167,11 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even medianFee = tag[1].as(); } break; + case 'b': // blockHeight + if (strcmp(key, "block") == 0) { + blockHeight = tag[1].as(); + } + break; } } @@ -173,6 +179,10 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even if (!typeValue.isEmpty()) { if (typeValue == "priceUsd") { processNewPrice(obj["content"].as(), CURRENCY_USD); + if (blockHeight != 0) { + auto& blockNotify = BlockNotify::getInstance(); + blockNotify.processNewBlock(blockHeight); + } } else if (typeValue == "blockHeight") { auto& blockNotify = BlockNotify::getInstance(); From dc8e348aa349e9fcf3c0f7b12733b4f2306a995f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 19 Feb 2025 15:38:54 +0100 Subject: [PATCH 18/24] fix: Set explicit littlefs version tag --- dependencies.lock | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.lock b/dependencies.lock index c338e6c..2274b85 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -4,6 +4,6 @@ dependencies: source: type: idf version: 4.4.7 -manifest_hash: cd2f3ee15e776d949eb4ea4eddc8f39b30c2a7905050850eed01ab4928143cff +manifest_hash: 1d4ef353a86901733b106a1897b186dbf9fc091a4981f0560ea2f6899b7a3d44 target: esp32s3 version: 1.0.0 diff --git a/platformio.ini b/platformio.ini index f9dc11e..37e99da 100644 --- a/platformio.ini +++ b/platformio.ini @@ -36,7 +36,7 @@ build_unflags = -Werror=all -fno-exceptions lib_deps = - https://github.com/joltwallet/esp_littlefs.git + https://github.com/joltwallet/esp_littlefs.git#v1.16.4 bblanchon/ArduinoJson@^7.3.0 esp32async/ESPAsyncWebServer @ 3.7.0 robtillaart/MCP23017@^0.9.0 From 7266a51a92c776350bd7cb3b5ff409695478313f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 5 Apr 2025 22:35:40 +0200 Subject: [PATCH 19/24] chore: update dependencies --- platformio.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index 37e99da..140946d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,10 +37,10 @@ build_unflags = -fno-exceptions lib_deps = https://github.com/joltwallet/esp_littlefs.git#v1.16.4 - bblanchon/ArduinoJson@^7.3.0 - esp32async/ESPAsyncWebServer @ 3.7.0 - robtillaart/MCP23017@^0.9.0 - adafruit/Adafruit NeoPixel@^1.12.4 + bblanchon/ArduinoJson@^7.3.1 + esp32async/ESPAsyncWebServer @ 3.7.4 + robtillaart/MCP23017@^0.9.1 + adafruit/Adafruit NeoPixel@^1.12.5 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 From c62563c3c2a9886e09a617aa136c94901acebb50 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 5 Apr 2025 22:40:12 +0200 Subject: [PATCH 20/24] feat: Replace timezone offset with timezone selector --- include/timezone_data.hpp | 721 ++++++++++++++++++++++++++++++++++++++ src/lib/config.cpp | 13 +- src/lib/config.hpp | 3 +- src/lib/defaults.hpp | 1 + src/lib/webserver.cpp | 7 +- 5 files changed, 739 insertions(+), 6 deletions(-) create mode 100644 include/timezone_data.hpp diff --git a/include/timezone_data.hpp b/include/timezone_data.hpp new file mode 100644 index 0000000..1ebb5db --- /dev/null +++ b/include/timezone_data.hpp @@ -0,0 +1,721 @@ +#pragma once + +#include +#include +#include + +namespace timezone_data { + +// Enum for unique timezone strings +enum class TimezoneValue : uint8_t { + plus000plus02_2M350_1M1050_3, + plus01_1, + plus02_2, + plus0330_330, + plus03_3, + plus0430_430, + plus04_4, + plus0530_530, + plus0545_545, + plus05_5, + plus0630_630, + plus06_6, + plus07_7, + plus0845_845, + plus08_8, + plus09_9, + plus1030_1030plus11_11M1010M410, + plus10_10, + plus11_11, + plus11_11plus12M1010M410_3, + plus1245_1245plus1345M950_245M410_345, + plus12_12, + plus13_13, + plus14_14, + _011, + _011plus00M350_0M1050_1, + _022, + _022_01M350__1M1050_0, + _033, + _033_02M320M1110, + _044, + _044_03M1010_0M340_0, + _044_03M916_24M416_24, + _055, + _066, + _066_05M916_22M416_22, + _077, + _088, + _0930930, + _099, + _1010, + _1111, + _1212, + ACST_930, + ACST_930ACDTM1010M410_3, + AEST_10, + AEST_10AEDTM1010M410_3, + AKST9AKDTM320M1110, + AST4, + AST4ADTM320M1110, + AWST_8, + CAT_2, + CET_1, + CET_1CESTM350M1050_3, + CST_8, + CST5CDTM320_0M1110_1, + CST6, + CST6CDTM320M1110, + ChST_10, + EAT_3, + EET_2, + EET_2EESTM344_50M1044_50, + EET_2EESTM350M1050_3, + EET_2EESTM350_0M1050_0, + EET_2EESTM350_3M1050_4, + EET_2EESTM455_0M1054_24, + EST5, + EST5EDTM320M1110, + GMT0, + GMT0BSTM350_1M1050, + HKT_8, + HST10, + HST10HDTM320M1110, + IST_1GMT0M1050M350_1, + IST_2IDTM344_26M1050, + IST_530, + JST_9, + KST_9, + MSK_3, + MST7, + MST7MDTM320M1110, + NST330NDTM320M1110, + NZST_12NZDTM950M410_3, + PKT_5, + PST_8, + PST8PDTM320M1110, + SAST_2, + SST11, + UTC0, + WAT_1, + WET0WESTM350_1M1050, + WIB_7, + WIT_9, + WITA_8, +}; + +// Key-value pair type +using TimezoneEntry = std::pair; + +// Lookup table +constexpr std::array TIMEZONE_DATA = {{ + {"Africa/Abidjan", TimezoneValue::GMT0}, + {"Africa/Accra", TimezoneValue::GMT0}, + {"Africa/Addis_Ababa", TimezoneValue::EAT_3}, + {"Africa/Algiers", TimezoneValue::CET_1}, + {"Africa/Asmara", TimezoneValue::EAT_3}, + {"Africa/Bamako", TimezoneValue::GMT0}, + {"Africa/Bangui", TimezoneValue::WAT_1}, + {"Africa/Banjul", TimezoneValue::GMT0}, + {"Africa/Bissau", TimezoneValue::GMT0}, + {"Africa/Blantyre", TimezoneValue::CAT_2}, + {"Africa/Brazzaville", TimezoneValue::WAT_1}, + {"Africa/Bujumbura", TimezoneValue::CAT_2}, + {"Africa/Cairo", TimezoneValue::EET_2EESTM455_0M1054_24}, + {"Africa/Casablanca", TimezoneValue::plus01_1}, + {"Africa/Ceuta", TimezoneValue::CET_1CESTM350M1050_3}, + {"Africa/Conakry", TimezoneValue::GMT0}, + {"Africa/Dakar", TimezoneValue::GMT0}, + {"Africa/Dar_es_Salaam", TimezoneValue::EAT_3}, + {"Africa/Djibouti", TimezoneValue::EAT_3}, + {"Africa/Douala", TimezoneValue::WAT_1}, + {"Africa/El_Aaiun", TimezoneValue::plus01_1}, + {"Africa/Freetown", TimezoneValue::GMT0}, + {"Africa/Gaborone", TimezoneValue::CAT_2}, + {"Africa/Harare", TimezoneValue::CAT_2}, + {"Africa/Johannesburg", TimezoneValue::SAST_2}, + {"Africa/Juba", TimezoneValue::CAT_2}, + {"Africa/Kampala", TimezoneValue::EAT_3}, + {"Africa/Khartoum", TimezoneValue::CAT_2}, + {"Africa/Kigali", TimezoneValue::CAT_2}, + {"Africa/Kinshasa", TimezoneValue::WAT_1}, + {"Africa/Lagos", TimezoneValue::WAT_1}, + {"Africa/Libreville", TimezoneValue::WAT_1}, + {"Africa/Lome", TimezoneValue::GMT0}, + {"Africa/Luanda", TimezoneValue::WAT_1}, + {"Africa/Lubumbashi", TimezoneValue::CAT_2}, + {"Africa/Lusaka", TimezoneValue::CAT_2}, + {"Africa/Malabo", TimezoneValue::WAT_1}, + {"Africa/Maputo", TimezoneValue::CAT_2}, + {"Africa/Maseru", TimezoneValue::SAST_2}, + {"Africa/Mbabane", TimezoneValue::SAST_2}, + {"Africa/Mogadishu", TimezoneValue::EAT_3}, + {"Africa/Monrovia", TimezoneValue::GMT0}, + {"Africa/Nairobi", TimezoneValue::EAT_3}, + {"Africa/Ndjamena", TimezoneValue::WAT_1}, + {"Africa/Niamey", TimezoneValue::WAT_1}, + {"Africa/Nouakchott", TimezoneValue::GMT0}, + {"Africa/Ouagadougou", TimezoneValue::GMT0}, + {"Africa/Porto-Novo", TimezoneValue::WAT_1}, + {"Africa/Sao_Tome", TimezoneValue::GMT0}, + {"Africa/Tripoli", TimezoneValue::EET_2}, + {"Africa/Tunis", TimezoneValue::CET_1}, + {"Africa/Windhoek", TimezoneValue::CAT_2}, + {"America/Adak", TimezoneValue::HST10HDTM320M1110}, + {"America/Anchorage", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Anguilla", TimezoneValue::AST4}, + {"America/Antigua", TimezoneValue::AST4}, + {"America/Araguaina", TimezoneValue::_033}, + {"America/Argentina/Buenos_Aires", TimezoneValue::_033}, + {"America/Argentina/Catamarca", TimezoneValue::_033}, + {"America/Argentina/Cordoba", TimezoneValue::_033}, + {"America/Argentina/Jujuy", TimezoneValue::_033}, + {"America/Argentina/La_Rioja", TimezoneValue::_033}, + {"America/Argentina/Mendoza", TimezoneValue::_033}, + {"America/Argentina/Rio_Gallegos", TimezoneValue::_033}, + {"America/Argentina/Salta", TimezoneValue::_033}, + {"America/Argentina/San_Juan", TimezoneValue::_033}, + {"America/Argentina/San_Luis", TimezoneValue::_033}, + {"America/Argentina/Tucuman", TimezoneValue::_033}, + {"America/Argentina/Ushuaia", TimezoneValue::_033}, + {"America/Aruba", TimezoneValue::AST4}, + {"America/Asuncion", TimezoneValue::_044_03M1010_0M340_0}, + {"America/Atikokan", TimezoneValue::EST5}, + {"America/Bahia", TimezoneValue::_033}, + {"America/Bahia_Banderas", TimezoneValue::CST6}, + {"America/Barbados", TimezoneValue::AST4}, + {"America/Belem", TimezoneValue::_033}, + {"America/Belize", TimezoneValue::CST6}, + {"America/Blanc-Sablon", TimezoneValue::AST4}, + {"America/Boa_Vista", TimezoneValue::_044}, + {"America/Bogota", TimezoneValue::_055}, + {"America/Boise", TimezoneValue::MST7MDTM320M1110}, + {"America/Cambridge_Bay", TimezoneValue::MST7MDTM320M1110}, + {"America/Campo_Grande", TimezoneValue::_044}, + {"America/Cancun", TimezoneValue::EST5}, + {"America/Caracas", TimezoneValue::_044}, + {"America/Cayenne", TimezoneValue::_033}, + {"America/Cayman", TimezoneValue::EST5}, + {"America/Chicago", TimezoneValue::CST6CDTM320M1110}, + {"America/Chihuahua", TimezoneValue::CST6}, + {"America/Costa_Rica", TimezoneValue::CST6}, + {"America/Creston", TimezoneValue::MST7}, + {"America/Cuiaba", TimezoneValue::_044}, + {"America/Curacao", TimezoneValue::AST4}, + {"America/Danmarkshavn", TimezoneValue::GMT0}, + {"America/Dawson", TimezoneValue::MST7}, + {"America/Dawson_Creek", TimezoneValue::MST7}, + {"America/Denver", TimezoneValue::MST7MDTM320M1110}, + {"America/Detroit", TimezoneValue::EST5EDTM320M1110}, + {"America/Dominica", TimezoneValue::AST4}, + {"America/Edmonton", TimezoneValue::MST7MDTM320M1110}, + {"America/Eirunepe", TimezoneValue::_055}, + {"America/El_Salvador", TimezoneValue::CST6}, + {"America/Fort_Nelson", TimezoneValue::MST7}, + {"America/Fortaleza", TimezoneValue::_033}, + {"America/Glace_Bay", TimezoneValue::AST4ADTM320M1110}, + {"America/Godthab", TimezoneValue::_022_01M350__1M1050_0}, + {"America/Goose_Bay", TimezoneValue::AST4ADTM320M1110}, + {"America/Grand_Turk", TimezoneValue::EST5EDTM320M1110}, + {"America/Grenada", TimezoneValue::AST4}, + {"America/Guadeloupe", TimezoneValue::AST4}, + {"America/Guatemala", TimezoneValue::CST6}, + {"America/Guayaquil", TimezoneValue::_055}, + {"America/Guyana", TimezoneValue::_044}, + {"America/Halifax", TimezoneValue::AST4ADTM320M1110}, + {"America/Havana", TimezoneValue::CST5CDTM320_0M1110_1}, + {"America/Hermosillo", TimezoneValue::MST7}, + {"America/Indiana/Indianapolis", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Knox", TimezoneValue::CST6CDTM320M1110}, + {"America/Indiana/Marengo", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Petersburg", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Tell_City", TimezoneValue::CST6CDTM320M1110}, + {"America/Indiana/Vevay", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Vincennes", TimezoneValue::EST5EDTM320M1110}, + {"America/Indiana/Winamac", TimezoneValue::EST5EDTM320M1110}, + {"America/Inuvik", TimezoneValue::MST7MDTM320M1110}, + {"America/Iqaluit", TimezoneValue::EST5EDTM320M1110}, + {"America/Jamaica", TimezoneValue::EST5}, + {"America/Juneau", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Kentucky/Louisville", TimezoneValue::EST5EDTM320M1110}, + {"America/Kentucky/Monticello", TimezoneValue::EST5EDTM320M1110}, + {"America/Kralendijk", TimezoneValue::AST4}, + {"America/La_Paz", TimezoneValue::_044}, + {"America/Lima", TimezoneValue::_055}, + {"America/Los_Angeles", TimezoneValue::PST8PDTM320M1110}, + {"America/Lower_Princes", TimezoneValue::AST4}, + {"America/Maceio", TimezoneValue::_033}, + {"America/Managua", TimezoneValue::CST6}, + {"America/Manaus", TimezoneValue::_044}, + {"America/Marigot", TimezoneValue::AST4}, + {"America/Martinique", TimezoneValue::AST4}, + {"America/Matamoros", TimezoneValue::CST6CDTM320M1110}, + {"America/Mazatlan", TimezoneValue::MST7}, + {"America/Menominee", TimezoneValue::CST6CDTM320M1110}, + {"America/Merida", TimezoneValue::CST6}, + {"America/Metlakatla", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Mexico_City", TimezoneValue::CST6}, + {"America/Miquelon", TimezoneValue::_033_02M320M1110}, + {"America/Moncton", TimezoneValue::AST4ADTM320M1110}, + {"America/Monterrey", TimezoneValue::CST6}, + {"America/Montevideo", TimezoneValue::_033}, + {"America/Montreal", TimezoneValue::EST5EDTM320M1110}, + {"America/Montserrat", TimezoneValue::AST4}, + {"America/Nassau", TimezoneValue::EST5EDTM320M1110}, + {"America/New_York", TimezoneValue::EST5EDTM320M1110}, + {"America/Nipigon", TimezoneValue::EST5EDTM320M1110}, + {"America/Nome", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Noronha", TimezoneValue::_022}, + {"America/North_Dakota/Beulah", TimezoneValue::CST6CDTM320M1110}, + {"America/North_Dakota/Center", TimezoneValue::CST6CDTM320M1110}, + {"America/North_Dakota/New_Salem", TimezoneValue::CST6CDTM320M1110}, + {"America/Nuuk", TimezoneValue::_022_01M350__1M1050_0}, + {"America/Ojinaga", TimezoneValue::CST6CDTM320M1110}, + {"America/Panama", TimezoneValue::EST5}, + {"America/Pangnirtung", TimezoneValue::EST5EDTM320M1110}, + {"America/Paramaribo", TimezoneValue::_033}, + {"America/Phoenix", TimezoneValue::MST7}, + {"America/Port-au-Prince", TimezoneValue::EST5EDTM320M1110}, + {"America/Port_of_Spain", TimezoneValue::AST4}, + {"America/Porto_Velho", TimezoneValue::_044}, + {"America/Puerto_Rico", TimezoneValue::AST4}, + {"America/Punta_Arenas", TimezoneValue::_033}, + {"America/Rainy_River", TimezoneValue::CST6CDTM320M1110}, + {"America/Rankin_Inlet", TimezoneValue::CST6CDTM320M1110}, + {"America/Recife", TimezoneValue::_033}, + {"America/Regina", TimezoneValue::CST6}, + {"America/Resolute", TimezoneValue::CST6CDTM320M1110}, + {"America/Rio_Branco", TimezoneValue::_055}, + {"America/Santarem", TimezoneValue::_033}, + {"America/Santiago", TimezoneValue::_044_03M916_24M416_24}, + {"America/Santo_Domingo", TimezoneValue::AST4}, + {"America/Sao_Paulo", TimezoneValue::_033}, + {"America/Scoresbysund", TimezoneValue::_022_01M350__1M1050_0}, + {"America/Sitka", TimezoneValue::AKST9AKDTM320M1110}, + {"America/St_Barthelemy", TimezoneValue::AST4}, + {"America/St_Johns", TimezoneValue::NST330NDTM320M1110}, + {"America/St_Kitts", TimezoneValue::AST4}, + {"America/St_Lucia", TimezoneValue::AST4}, + {"America/St_Thomas", TimezoneValue::AST4}, + {"America/St_Vincent", TimezoneValue::AST4}, + {"America/Swift_Current", TimezoneValue::CST6}, + {"America/Tegucigalpa", TimezoneValue::CST6}, + {"America/Thule", TimezoneValue::AST4ADTM320M1110}, + {"America/Thunder_Bay", TimezoneValue::EST5EDTM320M1110}, + {"America/Tijuana", TimezoneValue::PST8PDTM320M1110}, + {"America/Toronto", TimezoneValue::EST5EDTM320M1110}, + {"America/Tortola", TimezoneValue::AST4}, + {"America/Vancouver", TimezoneValue::PST8PDTM320M1110}, + {"America/Whitehorse", TimezoneValue::MST7}, + {"America/Winnipeg", TimezoneValue::CST6CDTM320M1110}, + {"America/Yakutat", TimezoneValue::AKST9AKDTM320M1110}, + {"America/Yellowknife", TimezoneValue::MST7MDTM320M1110}, + {"Antarctica/Casey", TimezoneValue::plus08_8}, + {"Antarctica/Davis", TimezoneValue::plus07_7}, + {"Antarctica/DumontDUrville", TimezoneValue::plus10_10}, + {"Antarctica/Macquarie", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Antarctica/Mawson", TimezoneValue::plus05_5}, + {"Antarctica/McMurdo", TimezoneValue::NZST_12NZDTM950M410_3}, + {"Antarctica/Palmer", TimezoneValue::_033}, + {"Antarctica/Rothera", TimezoneValue::_033}, + {"Antarctica/Syowa", TimezoneValue::plus03_3}, + {"Antarctica/Troll", TimezoneValue::plus000plus02_2M350_1M1050_3}, + {"Antarctica/Vostok", TimezoneValue::plus05_5}, + {"Arctic/Longyearbyen", TimezoneValue::CET_1CESTM350M1050_3}, + {"Asia/Aden", TimezoneValue::plus03_3}, + {"Asia/Almaty", TimezoneValue::plus05_5}, + {"Asia/Amman", TimezoneValue::plus03_3}, + {"Asia/Anadyr", TimezoneValue::plus12_12}, + {"Asia/Aqtau", TimezoneValue::plus05_5}, + {"Asia/Aqtobe", TimezoneValue::plus05_5}, + {"Asia/Ashgabat", TimezoneValue::plus05_5}, + {"Asia/Atyrau", TimezoneValue::plus05_5}, + {"Asia/Baghdad", TimezoneValue::plus03_3}, + {"Asia/Bahrain", TimezoneValue::plus03_3}, + {"Asia/Baku", TimezoneValue::plus04_4}, + {"Asia/Bangkok", TimezoneValue::plus07_7}, + {"Asia/Barnaul", TimezoneValue::plus07_7}, + {"Asia/Beirut", TimezoneValue::EET_2EESTM350_0M1050_0}, + {"Asia/Bishkek", TimezoneValue::plus06_6}, + {"Asia/Brunei", TimezoneValue::plus08_8}, + {"Asia/Chita", TimezoneValue::plus09_9}, + {"Asia/Choibalsan", TimezoneValue::plus08_8}, + {"Asia/Colombo", TimezoneValue::plus0530_530}, + {"Asia/Damascus", TimezoneValue::plus03_3}, + {"Asia/Dhaka", TimezoneValue::plus06_6}, + {"Asia/Dili", TimezoneValue::plus09_9}, + {"Asia/Dubai", TimezoneValue::plus04_4}, + {"Asia/Dushanbe", TimezoneValue::plus05_5}, + {"Asia/Famagusta", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Asia/Gaza", TimezoneValue::EET_2EESTM344_50M1044_50}, + {"Asia/Hebron", TimezoneValue::EET_2EESTM344_50M1044_50}, + {"Asia/Ho_Chi_Minh", TimezoneValue::plus07_7}, + {"Asia/Hong_Kong", TimezoneValue::HKT_8}, + {"Asia/Hovd", TimezoneValue::plus07_7}, + {"Asia/Irkutsk", TimezoneValue::plus08_8}, + {"Asia/Jakarta", TimezoneValue::WIB_7}, + {"Asia/Jayapura", TimezoneValue::WIT_9}, + {"Asia/Jerusalem", TimezoneValue::IST_2IDTM344_26M1050}, + {"Asia/Kabul", TimezoneValue::plus0430_430}, + {"Asia/Kamchatka", TimezoneValue::plus12_12}, + {"Asia/Karachi", TimezoneValue::PKT_5}, + {"Asia/Kathmandu", TimezoneValue::plus0545_545}, + {"Asia/Khandyga", TimezoneValue::plus09_9}, + {"Asia/Kolkata", TimezoneValue::IST_530}, + {"Asia/Krasnoyarsk", TimezoneValue::plus07_7}, + {"Asia/Kuala_Lumpur", TimezoneValue::plus08_8}, + {"Asia/Kuching", TimezoneValue::plus08_8}, + {"Asia/Kuwait", TimezoneValue::plus03_3}, + {"Asia/Macau", TimezoneValue::CST_8}, + {"Asia/Magadan", TimezoneValue::plus11_11}, + {"Asia/Makassar", TimezoneValue::WITA_8}, + {"Asia/Manila", TimezoneValue::PST_8}, + {"Asia/Muscat", TimezoneValue::plus04_4}, + {"Asia/Nicosia", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Asia/Novokuznetsk", TimezoneValue::plus07_7}, + {"Asia/Novosibirsk", TimezoneValue::plus07_7}, + {"Asia/Omsk", TimezoneValue::plus06_6}, + {"Asia/Oral", TimezoneValue::plus05_5}, + {"Asia/Phnom_Penh", TimezoneValue::plus07_7}, + {"Asia/Pontianak", TimezoneValue::WIB_7}, + {"Asia/Pyongyang", TimezoneValue::KST_9}, + {"Asia/Qatar", TimezoneValue::plus03_3}, + {"Asia/Qyzylorda", TimezoneValue::plus05_5}, + {"Asia/Riyadh", TimezoneValue::plus03_3}, + {"Asia/Sakhalin", TimezoneValue::plus11_11}, + {"Asia/Samarkand", TimezoneValue::plus05_5}, + {"Asia/Seoul", TimezoneValue::KST_9}, + {"Asia/Shanghai", TimezoneValue::CST_8}, + {"Asia/Singapore", TimezoneValue::plus08_8}, + {"Asia/Srednekolymsk", TimezoneValue::plus11_11}, + {"Asia/Taipei", TimezoneValue::CST_8}, + {"Asia/Tashkent", TimezoneValue::plus05_5}, + {"Asia/Tbilisi", TimezoneValue::plus04_4}, + {"Asia/Tehran", TimezoneValue::plus0330_330}, + {"Asia/Thimphu", TimezoneValue::plus06_6}, + {"Asia/Tokyo", TimezoneValue::JST_9}, + {"Asia/Tomsk", TimezoneValue::plus07_7}, + {"Asia/Ulaanbaatar", TimezoneValue::plus08_8}, + {"Asia/Urumqi", TimezoneValue::plus06_6}, + {"Asia/Ust-Nera", TimezoneValue::plus10_10}, + {"Asia/Vientiane", TimezoneValue::plus07_7}, + {"Asia/Vladivostok", TimezoneValue::plus10_10}, + {"Asia/Yakutsk", TimezoneValue::plus09_9}, + {"Asia/Yangon", TimezoneValue::plus0630_630}, + {"Asia/Yekaterinburg", TimezoneValue::plus05_5}, + {"Asia/Yerevan", TimezoneValue::plus04_4}, + {"Atlantic/Azores", TimezoneValue::_011plus00M350_0M1050_1}, + {"Atlantic/Bermuda", TimezoneValue::AST4ADTM320M1110}, + {"Atlantic/Canary", TimezoneValue::WET0WESTM350_1M1050}, + {"Atlantic/Cape_Verde", TimezoneValue::_011}, + {"Atlantic/Faroe", TimezoneValue::WET0WESTM350_1M1050}, + {"Atlantic/Madeira", TimezoneValue::WET0WESTM350_1M1050}, + {"Atlantic/Reykjavik", TimezoneValue::GMT0}, + {"Atlantic/South_Georgia", TimezoneValue::_022}, + {"Atlantic/St_Helena", TimezoneValue::GMT0}, + {"Atlantic/Stanley", TimezoneValue::_033}, + {"Australia/Adelaide", TimezoneValue::ACST_930ACDTM1010M410_3}, + {"Australia/Brisbane", TimezoneValue::AEST_10}, + {"Australia/Broken_Hill", TimezoneValue::ACST_930ACDTM1010M410_3}, + {"Australia/Currie", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Australia/Darwin", TimezoneValue::ACST_930}, + {"Australia/Eucla", TimezoneValue::plus0845_845}, + {"Australia/Hobart", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Australia/Lindeman", TimezoneValue::AEST_10}, + {"Australia/Lord_Howe", TimezoneValue::plus1030_1030plus11_11M1010M410}, + {"Australia/Melbourne", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Australia/Perth", TimezoneValue::AWST_8}, + {"Australia/Sydney", TimezoneValue::AEST_10AEDTM1010M410_3}, + {"Etc/GMT", TimezoneValue::GMT0}, + {"Etc/GMT+0", TimezoneValue::GMT0}, + {"Etc/GMT+1", TimezoneValue::_011}, + {"Etc/GMT+10", TimezoneValue::_1010}, + {"Etc/GMT+11", TimezoneValue::_1111}, + {"Etc/GMT+12", TimezoneValue::_1212}, + {"Etc/GMT+2", TimezoneValue::_022}, + {"Etc/GMT+3", TimezoneValue::_033}, + {"Etc/GMT+4", TimezoneValue::_044}, + {"Etc/GMT+5", TimezoneValue::_055}, + {"Etc/GMT+6", TimezoneValue::_066}, + {"Etc/GMT+7", TimezoneValue::_077}, + {"Etc/GMT+8", TimezoneValue::_088}, + {"Etc/GMT+9", TimezoneValue::_099}, + {"Etc/GMT-0", TimezoneValue::GMT0}, + {"Etc/GMT-1", TimezoneValue::plus01_1}, + {"Etc/GMT-10", TimezoneValue::plus10_10}, + {"Etc/GMT-11", TimezoneValue::plus11_11}, + {"Etc/GMT-12", TimezoneValue::plus12_12}, + {"Etc/GMT-13", TimezoneValue::plus13_13}, + {"Etc/GMT-14", TimezoneValue::plus14_14}, + {"Etc/GMT-2", TimezoneValue::plus02_2}, + {"Etc/GMT-3", TimezoneValue::plus03_3}, + {"Etc/GMT-4", TimezoneValue::plus04_4}, + {"Etc/GMT-5", TimezoneValue::plus05_5}, + {"Etc/GMT-6", TimezoneValue::plus06_6}, + {"Etc/GMT-7", TimezoneValue::plus07_7}, + {"Etc/GMT-8", TimezoneValue::plus08_8}, + {"Etc/GMT-9", TimezoneValue::plus09_9}, + {"Etc/GMT0", TimezoneValue::GMT0}, + {"Etc/Greenwich", TimezoneValue::GMT0}, + {"Etc/UCT", TimezoneValue::UTC0}, + {"Etc/UTC", TimezoneValue::UTC0}, + {"Etc/Universal", TimezoneValue::UTC0}, + {"Etc/Zulu", TimezoneValue::UTC0}, + {"Europe/Amsterdam", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Andorra", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Astrakhan", TimezoneValue::plus04_4}, + {"Europe/Athens", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Belgrade", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Berlin", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Bratislava", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Brussels", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Bucharest", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Budapest", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Busingen", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Chisinau", TimezoneValue::EET_2EESTM350M1050_3}, + {"Europe/Copenhagen", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Dublin", TimezoneValue::IST_1GMT0M1050M350_1}, + {"Europe/Gibraltar", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Guernsey", TimezoneValue::GMT0BSTM350_1M1050}, + {"Europe/Helsinki", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Isle_of_Man", TimezoneValue::GMT0BSTM350_1M1050}, + {"Europe/Istanbul", TimezoneValue::plus03_3}, + {"Europe/Jersey", TimezoneValue::GMT0BSTM350_1M1050}, + {"Europe/Kaliningrad", TimezoneValue::EET_2}, + {"Europe/Kiev", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Kirov", TimezoneValue::MSK_3}, + {"Europe/Lisbon", TimezoneValue::WET0WESTM350_1M1050}, + {"Europe/Ljubljana", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/London", TimezoneValue::GMT0BSTM350_1M1050}, + {"Europe/Luxembourg", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Madrid", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Malta", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Mariehamn", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Minsk", TimezoneValue::plus03_3}, + {"Europe/Monaco", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Moscow", TimezoneValue::MSK_3}, + {"Europe/Oslo", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Paris", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Podgorica", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Prague", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Riga", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Rome", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Samara", TimezoneValue::plus04_4}, + {"Europe/San_Marino", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Sarajevo", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Saratov", TimezoneValue::plus04_4}, + {"Europe/Simferopol", TimezoneValue::MSK_3}, + {"Europe/Skopje", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Sofia", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Stockholm", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Tallinn", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Tirane", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Ulyanovsk", TimezoneValue::plus04_4}, + {"Europe/Uzhgorod", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Vaduz", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Vatican", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Vienna", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Vilnius", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Volgograd", TimezoneValue::MSK_3}, + {"Europe/Warsaw", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Zagreb", TimezoneValue::CET_1CESTM350M1050_3}, + {"Europe/Zaporozhye", TimezoneValue::EET_2EESTM350_3M1050_4}, + {"Europe/Zurich", TimezoneValue::CET_1CESTM350M1050_3}, + {"Indian/Antananarivo", TimezoneValue::EAT_3}, + {"Indian/Chagos", TimezoneValue::plus06_6}, + {"Indian/Christmas", TimezoneValue::plus07_7}, + {"Indian/Cocos", TimezoneValue::plus0630_630}, + {"Indian/Comoro", TimezoneValue::EAT_3}, + {"Indian/Kerguelen", TimezoneValue::plus05_5}, + {"Indian/Mahe", TimezoneValue::plus04_4}, + {"Indian/Maldives", TimezoneValue::plus05_5}, + {"Indian/Mauritius", TimezoneValue::plus04_4}, + {"Indian/Mayotte", TimezoneValue::EAT_3}, + {"Indian/Reunion", TimezoneValue::plus04_4}, + {"Pacific/Apia", TimezoneValue::plus13_13}, + {"Pacific/Auckland", TimezoneValue::NZST_12NZDTM950M410_3}, + {"Pacific/Bougainville", TimezoneValue::plus11_11}, + {"Pacific/Chatham", TimezoneValue::plus1245_1245plus1345M950_245M410_345}, + {"Pacific/Chuuk", TimezoneValue::plus10_10}, + {"Pacific/Easter", TimezoneValue::_066_05M916_22M416_22}, + {"Pacific/Efate", TimezoneValue::plus11_11}, + {"Pacific/Enderbury", TimezoneValue::plus13_13}, + {"Pacific/Fakaofo", TimezoneValue::plus13_13}, + {"Pacific/Fiji", TimezoneValue::plus12_12}, + {"Pacific/Funafuti", TimezoneValue::plus12_12}, + {"Pacific/Galapagos", TimezoneValue::_066}, + {"Pacific/Gambier", TimezoneValue::_099}, + {"Pacific/Guadalcanal", TimezoneValue::plus11_11}, + {"Pacific/Guam", TimezoneValue::ChST_10}, + {"Pacific/Honolulu", TimezoneValue::HST10}, + {"Pacific/Kiritimati", TimezoneValue::plus14_14}, + {"Pacific/Kosrae", TimezoneValue::plus11_11}, + {"Pacific/Kwajalein", TimezoneValue::plus12_12}, + {"Pacific/Majuro", TimezoneValue::plus12_12}, + {"Pacific/Marquesas", TimezoneValue::_0930930}, + {"Pacific/Midway", TimezoneValue::SST11}, + {"Pacific/Nauru", TimezoneValue::plus12_12}, + {"Pacific/Niue", TimezoneValue::_1111}, + {"Pacific/Norfolk", TimezoneValue::plus11_11plus12M1010M410_3}, + {"Pacific/Noumea", TimezoneValue::plus11_11}, + {"Pacific/Pago_Pago", TimezoneValue::SST11}, + {"Pacific/Palau", TimezoneValue::plus09_9}, + {"Pacific/Pitcairn", TimezoneValue::_088}, + {"Pacific/Pohnpei", TimezoneValue::plus11_11}, + {"Pacific/Port_Moresby", TimezoneValue::plus10_10}, + {"Pacific/Rarotonga", TimezoneValue::_1010}, + {"Pacific/Saipan", TimezoneValue::ChST_10}, + {"Pacific/Tahiti", TimezoneValue::_1010}, + {"Pacific/Tarawa", TimezoneValue::plus12_12}, + {"Pacific/Tongatapu", TimezoneValue::plus13_13}, + {"Pacific/Wake", TimezoneValue::plus12_12}, + {"Pacific/Wallis", TimezoneValue::plus12_12}, +}}; + +// Helper function to find timezone value +inline TimezoneValue find_timezone_value(std::string_view key) { + for (const auto& entry : TIMEZONE_DATA) { + if (entry.first == key) { + return entry.second; + } + } + return TimezoneValue::plus000plus02_2M350_1M1050_3; // Default fallback +} + +// Overload for String +inline TimezoneValue find_timezone_value(const String& key) { + return find_timezone_value(std::string_view(key.c_str())); +} + +// Overload for std::string +inline TimezoneValue find_timezone_value(const std::string& key) { + return find_timezone_value(std::string_view(key)); +} + +// Helper function to convert TimezoneValue to string representation +inline String get_timezone_string(TimezoneValue value) { + for (const auto& entry : TIMEZONE_DATA) { + if (entry.second == value) { + return String(entry.first.data(), entry.first.length()); + } + } + return String("GMT0"); // Default fallback +} + +// Overload for std::string +inline std::string get_timezone_string_std(TimezoneValue value) { + for (const auto& entry : TIMEZONE_DATA) { + if (entry.second == value) { + return std::string(entry.first.data(), entry.first.length()); + } + } + return "GMT0"; // Default fallback +} + +// Helper function to convert TimezoneValue enum to its string representation +inline String get_timezone_value_string(TimezoneValue value) { + switch (value) { + case TimezoneValue::plus000plus02_2M350_1M1050_3: return String("+00:00+02:00,M3.5.0/1,M10.5.0/3"); + case TimezoneValue::plus01_1: return String("+01:00"); + case TimezoneValue::plus02_2: return String("+02:00"); + case TimezoneValue::plus0330_330: return String("+03:30"); + case TimezoneValue::plus03_3: return String("+03:00"); + case TimezoneValue::plus0430_430: return String("+04:30"); + case TimezoneValue::plus04_4: return String("+04:00"); + case TimezoneValue::plus0530_530: return String("+05:30"); + case TimezoneValue::plus0545_545: return String("+05:45"); + case TimezoneValue::plus05_5: return String("+05:00"); + case TimezoneValue::plus0630_630: return String("+06:30"); + case TimezoneValue::plus06_6: return String("+06:00"); + case TimezoneValue::plus07_7: return String("+07:00"); + case TimezoneValue::plus0845_845: return String("+08:45"); + case TimezoneValue::plus08_8: return String("+08:00"); + case TimezoneValue::plus09_9: return String("+09:00"); + case TimezoneValue::plus1030_1030plus11_11M1010M410: return String("+10:30+11:00,M10.1.0,M4.1.0"); + case TimezoneValue::plus10_10: return String("+10:00"); + case TimezoneValue::plus11_11: return String("+11:00"); + case TimezoneValue::plus11_11plus12M1010M410_3: return String("+11:00+12:00,M10.1.0,M4.1.0/3"); + case TimezoneValue::plus1245_1245plus1345M950_245M410_345: return String("+12:45+13:45,M9.5.0/2:45,M4.1.0/3:45"); + case TimezoneValue::plus12_12: return String("+12:00"); + case TimezoneValue::plus13_13: return String("+13:00"); + case TimezoneValue::plus14_14: return String("+14:00"); + case TimezoneValue::_011: return String("-01:00"); + case TimezoneValue::_011plus00M350_0M1050_1: return String("-01:00+00:00,M3.5.0/0,M10.5.0/1"); + case TimezoneValue::_022: return String("-02:00"); + case TimezoneValue::_022_01M350__1M1050_0: return String("-02:00-01:00,M3.5.0/-1,M10.5.0/0"); + case TimezoneValue::_033: return String("-03:00"); + case TimezoneValue::_033_02M320M1110: return String("-03:00-02:00,M3.2.0,M11.1.0"); + case TimezoneValue::_044: return String("-04:00"); + case TimezoneValue::_044_03M1010_0M340_0: return String("-04:00-03:00,M10.1.0/0,M3.4.0/0"); + case TimezoneValue::_044_03M916_24M416_24: return String("-04:00-03:00,M9.1.6/24,M4.1.6/24"); + case TimezoneValue::_055: return String("-05:00"); + case TimezoneValue::_066: return String("-06:00"); + case TimezoneValue::_066_05M916_22M416_22: return String("-06:00-05:00,M9.1.6/22,M4.1.6/22"); + case TimezoneValue::_077: return String("-07:00"); + case TimezoneValue::_088: return String("-08:00"); + case TimezoneValue::_0930930: return String("-09:30"); + case TimezoneValue::_099: return String("-09:00"); + case TimezoneValue::_1010: return String("-10:00"); + case TimezoneValue::_1111: return String("-11:00"); + case TimezoneValue::_1212: return String("-12:00"); + case TimezoneValue::ACST_930: return String("ACST-9:30"); + case TimezoneValue::ACST_930ACDTM1010M410_3: return String("ACST-9:30ACDT,M10.1.0,M4.1.0/3"); + case TimezoneValue::AEST_10: return String("AEST-10"); + case TimezoneValue::AEST_10AEDTM1010M410_3: return String("AEST-10AEDT,M10.1.0,M4.1.0/3"); + case TimezoneValue::AKST9AKDTM320M1110: return String("AKST9AKDT,M3.2.0,M11.1.0"); + case TimezoneValue::AST4: return String("AST4"); + case TimezoneValue::AST4ADTM320M1110: return String("AST4ADT,M3.2.0,M11.1.0"); + case TimezoneValue::AWST_8: return String("AWST-8"); + case TimezoneValue::CAT_2: return String("CAT-2"); + case TimezoneValue::CET_1: return String("CET-1"); + case TimezoneValue::CET_1CESTM350M1050_3: return String("CET-1CEST,M3.5.0,M10.5.0/3"); + case TimezoneValue::CST_8: return String("CST-8"); + case TimezoneValue::CST5CDTM320_0M1110_1: return String("CST5CDT,M3.2.0/0,M11.1.0/1"); + case TimezoneValue::CST6: return String("CST6"); + case TimezoneValue::CST6CDTM320M1110: return String("CST6CDT,M3.2.0,M11.1.0"); + case TimezoneValue::ChST_10: return String("ChST-10"); + case TimezoneValue::EAT_3: return String("EAT-3"); + case TimezoneValue::EET_2: return String("EET-2"); + case TimezoneValue::EET_2EESTM344_50M1044_50: return String("EET-2EEST,M3.4.4/50,M10.4.4/50"); + case TimezoneValue::EET_2EESTM350M1050_3: return String("EET-2EEST,M3.5.0,M10.5.0/3"); + case TimezoneValue::EET_2EESTM350_0M1050_0: return String("EET-2EEST,M3.5.0/0,M10.5.0/0"); + case TimezoneValue::EET_2EESTM350_3M1050_4: return String("EET-2EEST,M3.5.0/3,M10.5.0/4"); + case TimezoneValue::EET_2EESTM455_0M1054_24: return String("EET-2EEST,M4.5.5/0,M10.5.4/24"); + case TimezoneValue::EST5: return String("EST5"); + case TimezoneValue::EST5EDTM320M1110: return String("EST5EDT,M3.2.0,M11.1.0"); + case TimezoneValue::GMT0: return String("GMT0"); + case TimezoneValue::GMT0BSTM350_1M1050: return String("GMT0BST,M3.5.0/1,M10.5.0"); + case TimezoneValue::HKT_8: return String("HKT-8"); + case TimezoneValue::HST10: return String("HST10"); + case TimezoneValue::HST10HDTM320M1110: return String("HST10HDT,M3.2.0,M11.1.0"); + case TimezoneValue::IST_1GMT0M1050M350_1: return String("IST-1GMT0,M10.5.0,M3.5.0/1"); + case TimezoneValue::IST_2IDTM344_26M1050: return String("IST-2IDT,M3.4.4/26,M10.5.0"); + case TimezoneValue::IST_530: return String("IST-5:30"); + case TimezoneValue::JST_9: return String("JST-9"); + case TimezoneValue::KST_9: return String("KST-9"); + case TimezoneValue::MSK_3: return String("MSK-3"); + case TimezoneValue::MST7: return String("MST7"); + case TimezoneValue::MST7MDTM320M1110: return String("MST7MDT,M3.2.0,M11.1.0"); + case TimezoneValue::NST330NDTM320M1110: return String("NST3:30NDT,M3.2.0,M11.1.0"); + case TimezoneValue::NZST_12NZDTM950M410_3: return String("NZST-12NZDT,M9.5.0,M4.1.0/3"); + case TimezoneValue::PKT_5: return String("PKT-5"); + case TimezoneValue::PST_8: return String("PST-8"); + case TimezoneValue::PST8PDTM320M1110: return String("PST8PDT,M3.2.0,M11.1.0"); + case TimezoneValue::SAST_2: return String("SAST-2"); + case TimezoneValue::SST11: return String("SST11"); + case TimezoneValue::UTC0: return String("UTC0"); + case TimezoneValue::WAT_1: return String("WAT-1"); + case TimezoneValue::WET0WESTM350_1M1050: return String("WET0WEST,M3.5.0/1,M10.5.0"); + case TimezoneValue::WIB_7: return String("WIB-7"); + case TimezoneValue::WIT_9: return String("WIT-9"); + case TimezoneValue::WITA_8: return String("WITA-8"); + default: return String("GMT0"); // Default fallback + } +} + +// Overload for std::string +inline std::string get_timezone_value_string_std(TimezoneValue value) { + return std::string(get_timezone_value_string(value).c_str()); +} + +} // namespace timezone_data diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 16cb80a..908a5a6 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -259,7 +259,7 @@ void setupWifi() void syncTime() { - configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0, + configTime(0, 0, NTP_SERVER); struct tm timeinfo; @@ -267,15 +267,24 @@ void syncTime() { auto& ledHandler = getLedHandler(); ledHandler.queueEffect(LED_EFFECT_CONFIGURING); - configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0, + configTime(0, 0, NTP_SERVER); delay(500); Serial.println(F("Retry set time")); } + setTimezone(get_timezone_value_string(timezone_data::find_timezone_value(preferences.getString("tzString", DEFAULT_TZ_STRING)))); + lastTimeSync = esp_timer_get_time() / 1000000; } +void setTimezone(String timezone) { + Serial.printf(" Setting Timezone to %s\n",timezone.c_str()); + setenv("TZ",timezone.c_str(),1); // Now adjust the TZ. Clock settings are adjusted to show the new local time + tzset(); +} + + void setupPreferences() { preferences.begin("btclock", false); diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 95c877d..c8d5336 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -33,7 +33,7 @@ #include "shared.hpp" #include "defaults.hpp" - +#include "timezone_data.hpp" #define NTP_SERVER "pool.ntp.org" #define DEFAULT_TIME_OFFSET_SECONDS 3600 #ifndef MCP_DEV_ADDR @@ -43,6 +43,7 @@ void setup(); void syncTime(); +void setTimezone(String timezone); uint getLastTimeSync(); void setupPreferences(); void setupWebsocketClients(void *pvParameters); diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 55da6e3..00d8bc0 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -24,6 +24,7 @@ #define DEFAULT_V2_SOURCE_CURRENCY CURRENCY_USD #define DEFAULT_TIME_OFFSET_SECONDS 3600 +#define DEFAULT_TZ_STRING "Europe/Amsterdam" #define DEFAULT_HOSTNAME_PREFIX "btclock" #define DEFAULT_MEMPOOL_INSTANCE "mempool.space" diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index cfb5d62..8313b76 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -5,7 +5,7 @@ static const char* JSON_CONTENT = "application/json"; static const char *const PROGMEM strSettings[] = { - "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName", "localPoolEndpoint"}; + "hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName", "localPoolEndpoint", "tzString"}; static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"}; @@ -692,8 +692,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["fullRefreshMin"] = preferences.getUInt("fullRefreshMin", DEFAULT_MINUTES_FULL_REFRESH); root["wpTimeout"] = preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT); - root["tzOffset"] = preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS) / 60; - + //root["tzOffset"] = preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS) / 60; + root["tzString"] = preferences.getString("tzString", DEFAULT_TZ_STRING); + // Add data source settings root["dataSource"] = preferences.getUChar("dataSource", DEFAULT_DATA_SOURCE); From 3e54343da8b995aae663ce972631561b6a950e7b Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 5 Apr 2025 22:40:52 +0200 Subject: [PATCH 21/24] chore: Update WebUI --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 0116cd6..0e278d1 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 0116cd68cdfdf383823f74e0f9665a1700cf0500 +Subproject commit 0e278d1be4160ab382213e80ec0716f8aad14a65 From d648551835d3db3ad80921ee652f4c518aae53dd Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Fri, 2 May 2025 22:28:46 +0200 Subject: [PATCH 22/24] fix: Fix LED status object --- src/lib/webserver.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 8313b76..6ee62f9 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -298,7 +298,13 @@ JsonDocument getLedStatusObject() uint blue = pixColor & 0xFF; char hexColor[8]; snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X", red, green, blue); - colors.add(hexColor); + // colors.add(hexColor); + + JsonObject object = colors.add(); + object["red"] = red; + object["green"] = green; + object["blue"] = blue; + object["hex"] = hexColor; } return root; From 064fe8fe6ce83b04bfdc28529bf6685ffda095ca Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 3 May 2025 00:03:01 +0200 Subject: [PATCH 23/24] chore: Update WebUI and dependencies --- data | 2 +- platformio.ini | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data b/data index 0e278d1..8389ed8 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 0e278d1be4160ab382213e80ec0716f8aad14a65 +Subproject commit 8389ed8e36a9a1a7a39ca31f53324a0949aedb1d diff --git a/platformio.ini b/platformio.ini index 140946d..98192fe 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,8 +37,8 @@ build_unflags = -fno-exceptions lib_deps = https://github.com/joltwallet/esp_littlefs.git#v1.16.4 - bblanchon/ArduinoJson@^7.3.1 - esp32async/ESPAsyncWebServer @ 3.7.4 + bblanchon/ArduinoJson@^7.4.1 + esp32async/ESPAsyncWebServer @ 3.7.7 robtillaart/MCP23017@^0.9.1 adafruit/Adafruit NeoPixel@^1.12.5 https://github.com/dsbaars/universal_pin#feature/mcp23017_rt From 2ac2a62c617ca0469cba1027c70c0ddd9ac3ae76 Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Sat, 3 May 2025 00:03:19 +0200 Subject: [PATCH 24/24] feat: change 3rd party price source to kraken --- src/lib/price_notify.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 6ac2f3b..c851e85 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -1,6 +1,6 @@ #include "price_notify.hpp" -const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; +const char *wsServerPrice = "wss://ws.kraken.com/v2"; WebSocketsClient webSocket; uint currentPrice = 90000; @@ -14,7 +14,7 @@ void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length); void setupPriceNotify() { - webSocket.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin"); + webSocket.beginSSL("ws.kraken.com", 443, "/v2"); webSocket.onEvent([](WStype_t type, uint8_t * payload, size_t length) { onWebsocketPriceEvent(type, payload, length); }); @@ -32,7 +32,14 @@ void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { case WStype_CONNECTED: { Serial.println("Connected to " + String(wsServerPrice)); - priceNotifyInit = true; + + JsonDocument doc; + doc["method"] = "subscribe"; + JsonObject params = doc["params"].to(); + params["channel"] = "ticker"; + params["symbol"][0] = "BTC/USD"; + + webSocket.sendTXT(doc.as().c_str()); break; } case WStype_TEXT: @@ -40,13 +47,15 @@ void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { JsonDocument doc; deserializeJson(doc, (char *)payload); - if (doc["bitcoin"].is()) + if (doc["data"][0].is()) { - if (currentPrice != doc["bitcoin"].as()) + float price = doc["data"][0]["last"].as(); + uint roundedPrice = round(price); + if (currentPrice != roundedPrice) { - processNewPrice(doc["bitcoin"].as(), CURRENCY_USD); + processNewPrice(roundedPrice, CURRENCY_USD); } - } + } break; } case WStype_BIN: