diff --git a/platformio.ini b/platformio.ini index 2044170..5081d52 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,6 +39,7 @@ lib_deps = https://github.com/dsbaars/universal_pin https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.16-rc.2 + https://github.com/dsbaars/arduino-nostr#less_verbose [env:lolin_s3_mini] extends = btclock_base @@ -66,6 +67,9 @@ build_flags = extends = btclock_base board = btclock board_build.partitions = partition_16mb.csv +upload_protocol = cmsis-dap +debug_tool = cmsis-dap + test_framework = unity build_flags = ${btclock_base.build_flags} diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 09cf4fc..48e1731 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -203,14 +203,15 @@ void setupPreferences() { } void setupWebsocketClients(void *pvParameters) { - setupBlockNotify(); + // setupBlockNotify(); - if (preferences.getBool("fetchEurPrice", false)) { - setupPriceFetchTask(); - } else { - setupPriceNotify(); - } + // if (preferences.getBool("fetchEurPrice", false)) { + // setupPriceFetchTask(); + // } else { + // setupPriceNotify(); + // } + setupNostrSubscribeTask(); vTaskDelete(NULL); } diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 228beea..ab8cf61 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -17,6 +17,8 @@ #include "lib/ota.hpp" #include "lib/price_notify.hpp" #include "lib/screen_handler.hpp" +#include "lib/nostr_subscribe.hpp" + #include "lib/shared.hpp" #include "lib/webserver.hpp" @@ -27,6 +29,9 @@ #define MCP_DEV_ADDR 0x20 #define DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE 30 #define DEFAULT_MINUTES_FULL_REFRESH 60 +#define DEFAULT_NOSTR_RELAYS "nostr.dbtc.link,relay.damus.io,nostr.onsats.org" +#define DEFAULT_NOSTR_BLOCKS_AUTHOR "55cf67598c37737af6d0278cbdcb400bf1a90b7654e964bda08f451bd3dffe5a" +#define DEFAULT_NOSTR_PRICE_AUTHOR "df66a305cca14265c558a6fe61ed6aa5e1e1b80a7ef5d89d2bbe82f62698c1ca" #define DEFAULT_FG_COLOR GxEPD_WHITE #define DEFAULT_BG_COLOR GxEPD_BLACK diff --git a/src/lib/nostr_subscribe.cpp b/src/lib/nostr_subscribe.cpp new file mode 100644 index 0000000..ad33d35 --- /dev/null +++ b/src/lib/nostr_subscribe.cpp @@ -0,0 +1,163 @@ +#include "nostr_subscribe.hpp" + +TaskHandle_t nostrSubscribeTaskHandle; +NostrEvent nostr; +NostrRelayManager nostrRelayManager; +NostrQueueProcessor nostrQueue; + +bool hasSentEvent = false; +unsigned long int lastNostrPriceUpdate; +unsigned long int lastNostrBlockUpdate; + +void okEvent(const std::string& key, const char* payload) { + Serial.println("OK event"); + Serial.println("payload is: "); + Serial.println(payload); +} + +void nip01Event(const std::string& key, const char* payload) { + // Serial.println("NIP01 event"); + // Serial.println("payload is: "); + // Serial.println(payload); + + uint minSecPriceUpd = preferences.getUInt( + "minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE); + uint currentTime = esp_timer_get_time() / 1000000; + + SpiRamJsonDocument doc(1024); + DeserializationError error = deserializeJson(doc, payload); + + if (doc[2].containsKey("tags")) { + for (int i = 0; i < doc[2]["tags"].size(); i++) { + if (doc[2]["tags"][i][0].as().equals("type") && + doc[2]["tags"][i][1].as().equals("blockHeight")) { + uint currentBlockHeight = doc[2]["content"].as(); + + if (currentBlockHeight <= getBlockHeight()) + return; + + Serial.printf("Block Height: %d\n", currentBlockHeight); + setBlockHeight(currentBlockHeight); + if (workQueue != nullptr) { + WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; + xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); + } + + if (getCurrentScreen() != SCREEN_BLOCK_HEIGHT && + preferences.getBool("stealFocus", true)) { + uint64_t timerPeriod = 0; + if (isTimerActive()) { + // store timer periode before making inactive to prevent artifacts + timerPeriod = getTimerSeconds(); + esp_timer_stop(screenRotateTimer); + } + setCurrentScreen(SCREEN_BLOCK_HEIGHT); + if (timerPeriod > 0) { + esp_timer_start_periodic(screenRotateTimer, + timerPeriod * usPerSecond); + } + } + + if (getCurrentScreen() == SCREEN_BLOCK_HEIGHT && + preferences.getBool("ledFlashOnUpd", false)) { + vTaskDelay(pdMS_TO_TICKS(250)); // Wait until screens are updated + queueLedEffect(LED_FLASH_BLOCK_NOTIFY); + } + + preferences.putUInt("blockHeight", currentBlockHeight); + } else if (doc[2]["tags"][i][0].as().equals("type") && + doc[2]["tags"][i][1].as().equals("priceUsd")) { + uint usdPrice = doc[2]["content"].as(); + + if (lastNostrPriceUpdate == 0 || + (currentTime - lastNostrPriceUpdate) > minSecPriceUpd) { + setPrice(usdPrice); + + if (workQueue != nullptr && + (getCurrentScreen() == SCREEN_BTC_TICKER || + getCurrentScreen() == SCREEN_MSCW_TIME || + getCurrentScreen() == SCREEN_MARKET_CAP)) { + WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0}; + xQueueSend(workQueue, &priceUpdate, portMAX_DELAY); + } + lastNostrPriceUpdate = currentTime; + + preferences.putUInt("lastPrice", usdPrice); + } + } + } + } +} + +void taskNostrSubscribe(void* pvParameters) { + std::vector relays = retrieveStringVector("nostrRelays"); + + nostr.setLogging(false); + nostrRelayManager.setRelays(relays); + nostrRelayManager.setMinRelaysAndTimeout(2, 10000); + + nostrRelayManager.setEventCallback("ok", okEvent); + nostrRelayManager.setEventCallback(1, nip01Event); + nostrRelayManager.connect(); + + NostrRequestOptions* eventRequestOptions = new NostrRequestOptions(); + String authors[] = { + preferences.getString("nostrBlocksAuth", DEFAULT_NOSTR_BLOCKS_AUTHOR), + preferences.getString("nostrPriceAuth", DEFAULT_NOSTR_PRICE_AUTHOR)}; + eventRequestOptions->authors = authors; + eventRequestOptions->authors_count = sizeof(authors) / sizeof(authors[0]); + + int kinds[] = {1}; + eventRequestOptions->kinds = kinds; + eventRequestOptions->kinds_count = sizeof(kinds) / sizeof(kinds[0]); + + eventRequestOptions->limit = 5; + nostrRelayManager.requestEvents(eventRequestOptions); + while (1) { + nostrRelayManager.loop(); + nostrRelayManager.broadcastEvents(); + + vTaskDelay(100); + } +} + +void setupNostrSubscribeTask() { + xTaskCreate(taskNostrSubscribe, "nostrSubscribe", (10 * 1024), NULL, 12, + &nostrSubscribeTaskHandle); + + // taskNostrSubscribe(NULL); +} + +void storeStringVector(const char* key, const std::vector& vec) { + String serializedVector = ""; + + // Serialize the vector of strings + for (size_t i = 0; i < vec.size(); i++) { + serializedVector += vec[i]; + if (i < vec.size() - 1) { + serializedVector += ","; + } + } + + // Store the serialized vector as a string + preferences.putString(key, serializedVector); +} + +std::vector retrieveStringVector(const char* key) { + String serializedVector = preferences.getString(key, DEFAULT_NOSTR_RELAYS); + std::vector result; + + // Deserialize the string into a vector of strings + size_t startPos = 0; + size_t commaPos = serializedVector.indexOf(","); + while (commaPos != -1) { + result.push_back(serializedVector.substring(startPos, commaPos)); + startPos = commaPos + 1; + commaPos = serializedVector.indexOf(",", startPos); + } + + // Add the last element + result.push_back(serializedVector.substring(startPos)); + + return result; +} \ No newline at end of file diff --git a/src/lib/nostr_subscribe.hpp b/src/lib/nostr_subscribe.hpp new file mode 100644 index 0000000..d582035 --- /dev/null +++ b/src/lib/nostr_subscribe.hpp @@ -0,0 +1,16 @@ +#include +#include +#include + +#include "lib/config.hpp" +#include "lib/shared.hpp" +#include "lib/block_notify.hpp" +extern TaskHandle_t nostrSubscribeTaskHandle; + +void okEvent(const std::string& key, const char* payload); +void nip01Event(const std::string& key, const char* payload); +void setupNostrSubscribeTask(); +void taskNostrSubscribe(void *pvParameters); + +void storeStringVector(const char* key, const std::vector& vec); +std::vector retrieveStringVector(const char* key); \ No newline at end of file diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 8bb1a73..e2c2282 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -407,6 +407,10 @@ void onApiSettingsGet(AsyncWebServerRequest *request) { root["hostname"] = getMyHostname(); root["ip"] = WiFi.localIP(); root["txPower"] = WiFi.getTxPower(); + root["nostrRelays"] = preferences.getString("nostrRelays", DEFAULT_NOSTR_RELAYS); + root["nostrBlocksAuth"] = preferences.getString("nostrBlocksAuth", DEFAULT_NOSTR_BLOCKS_AUTHOR); + root["nostrPriceAuth"] = preferences.getString("nostrPriceAuth", DEFAULT_NOSTR_PRICE_AUTHOR); + #ifdef GIT_REV root["gitRev"] = String(GIT_REV);