diff --git a/data b/data index 2363d98..ee4d6d8 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 2363d98965bb1fdbfdf5d130b41732f5b864e2d0 +Subproject commit ee4d6d88c76fa279e643faabf4216c88145e0b2c diff --git a/platformio.ini b/platformio.ini index 6f9b109..7392655 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,7 +41,8 @@ lib_deps = https://github.com/dsbaars/universal_pin https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 - + rblb/Nostrduino@^1.2.5 + [env:lolin_s3_mini] extends = btclock_base board = lolin_s3_mini diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index c775b7d..dad6ad1 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -172,7 +172,25 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) return; } - currentBlockHeight = block["height"].as(); + processNewBlock(block["height"].as()); + } + else if (doc.containsKey("mempool-blocks")) + { + JsonArray blockInfo = doc["mempool-blocks"].as(); + + uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); + + processNewBlockFee(medianFee); + } + + doc.clear(); +} + +void processNewBlock(uint newBlockHeight) { + if (newBlockHeight < currentBlockHeight) + return; + + currentBlockHeight = newBlockHeight; // Serial.printf("New block found: %d\r\n", block["height"].as()); preferences.putUInt("blockHeight", currentBlockHeight); @@ -209,30 +227,22 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data) queueLedEffect(LED_FLASH_BLOCK_NOTIFY); } } - } - else if (doc.containsKey("mempool-blocks")) - { - JsonArray blockInfo = doc["mempool-blocks"].as(); +} - uint medianFee = (uint)round(blockInfo[0]["medianFee"].as()); - - if (blockMedianFee == medianFee) +void processNewBlockFee(uint newBlockFee) { + if (blockMedianFee == newBlockFee) { - doc.clear(); return; } // Serial.printf("New median fee: %d\r\n", medianFee); - blockMedianFee = medianFee; + blockMedianFee = newBlockFee; if (workQueue != nullptr) { WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); } - } - - doc.clear(); } uint getBlockHeight() { return currentBlockHeight; } diff --git a/src/lib/block_notify.hpp b/src/lib/block_notify.hpp index c233b2e..0af7196 100644 --- a/src/lib/block_notify.hpp +++ b/src/lib/block_notify.hpp @@ -31,6 +31,9 @@ bool isBlockNotifyConnected(); void stopBlockNotify(); void restartBlockNotify(); +void processNewBlock(uint newBlockHeight); +void processNewBlockFee(uint newBlockFee); + bool getBlockNotifyInit(); uint getLastBlockUpdate(); int getBlockFetch(); diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 0b96689..61840c9 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -48,7 +48,9 @@ void setup() { delay(1000); } - } else if (mcp1.digitalRead(1) == LOW) { + } + else if (mcp1.digitalRead(1) == LOW) + { preferences.clear(); queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); ESP.restart(); @@ -66,8 +68,16 @@ void setup() setupTasks(); setupTimers(); - xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL, - tskIDLE_PRIORITY, NULL); + if (preferences.getBool("useNostr", DEFAULT_USE_NOSTR)) + { + setupNostrNotify(); + setupNostrTask(); + } + else + { + xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL, + tskIDLE_PRIORITY, NULL); + } setupButtonTask(); setupOTA(); @@ -401,14 +411,15 @@ void setupHardware() #ifdef HAS_FRONTLIGHT setupFrontlight(); - Wire.beginTransmission(0x5C); - byte error = Wire.endTransmission(); + Wire.beginTransmission(0x5C); + byte error = Wire.endTransmission(); - if (error == 0) { + if (error == 0) + { Serial.println(F("Found BH1750")); hasLuxSensor = true; bh1750.begin(BH1750::CONTINUOUS_LOW_RES_MODE, 0x5C); - } + } #endif } @@ -742,14 +753,15 @@ void setupFrontlight() { preferences.putBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE); } - } -float getLightLevel() { +float getLightLevel() +{ return bh1750.readLightLevel(); } -bool hasLightLevel() { +bool hasLightLevel() +{ return hasLuxSensor; } #endif @@ -784,5 +796,3 @@ String getFsRev() fsHash.close(); return ret; } - - diff --git a/src/lib/config.hpp b/src/lib/config.hpp index c9c3199..c0d0ac5 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -16,6 +16,8 @@ #include "lib/improv.hpp" #include "lib/led_handler.hpp" #include "lib/ota.hpp" +#include "lib/nostr_notify.hpp" + #include "lib/price_notify.hpp" #include "lib/screen_handler.hpp" #include "lib/shared.hpp" diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 510ab61..0de2415 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -22,6 +22,10 @@ #define DEFAULT_HOSTNAME_PREFIX "btclock" #define DEFAULT_MEMPOOL_INSTANCE "mempool.space" +#define DEFAULT_USE_NOSTR false +#define DEFAULT_NOSTR_NPUB "642317135fd4c4205323b9dea8af3270657e62d51dc31a657c0ec8aab31c6288" +#define DEFAULT_NOSTR_RELAY "wss://nostr.dbtc.link" + #define DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE 30 #define DEFAULT_MINUTES_FULL_REFRESH 60 diff --git a/src/lib/nostr_notify.cpp b/src/lib/nostr_notify.cpp new file mode 100644 index 0000000..4f7650f --- /dev/null +++ b/src/lib/nostr_notify.cpp @@ -0,0 +1,123 @@ +#include "nostr_notify.hpp" + +std::vector pools; +nostr::Transport *transport; +TaskHandle_t nostrTaskHandle = NULL; + +void setupNostrNotify() +{ + nostr::esp32::ESP32Platform::initNostr(false); + + + + try + { + transport = nostr::esp32::ESP32Platform::getTransport(); + nostr::NostrPool *pool = new nostr::NostrPool(transport); + String relay = preferences.getString("nostrRelay"); + String pubKey = preferences.getString("nostrPubKey"); + pools.push_back(pool); + // Lets subscribe to the relay + String subId = pool->subscribeMany( + {relay}, + {{// we set the filters here (see + // https://github.com/nostr-protocol/nips/blob/master/01.md#from-client-to-relay-sending-events-and-creating-subscriptions) + {"kinds", {"1"}}, + {"authors", {pubKey}}}}, + [&](const String &subId, nostr::SignedNostrEvent *event) + { + // Received events callback, we can access the event content with + // event->getContent() Here you should handle the event, for this + // test we will just serialize it and print to console + JsonDocument doc; + JsonArray arr = doc["data"].to(); + event->toSendableEvent(arr); + // Access the second element which is the object + JsonObject obj = arr[1].as(); + + // Access the "tags" array + JsonArray tags = obj["tags"].as(); + + // Flag to check if the tag was found + bool tagFound = false; + uint medianFee = 0; + String typeValue; + + // Iterate over the tags array + for (JsonArray tag : tags) + { + // Check if the tag is an array with two elements + if (tag.size() == 2) + { + const char *key = tag[0]; + const char *value = tag[1]; + + // Check if the key is "type" and the value is "priceUsd" + if (strcmp(key, "type") == 0 && (strcmp(value, "priceUsd") == 0 || strcmp(value, "blockHeight") == 0)) + { + typeValue = value; + tagFound = true; + } + else if (strcmp(key, "medianFee") == 0) + { + medianFee = tag[1].as(); + } + } + } + if (tagFound) + { + if (typeValue.equals("priceUsd")) + { + processNewPrice(obj["content"].as()); + } + else if (typeValue.equals("blockHeight")) + { + processNewBlock(obj["content"].as()); + } + + if (medianFee != 0) + { + processNewBlockFee(medianFee); + } + } + }, + [&](const String &subId, const String &reason) + { + // This is the callback that will be called when the subscription is + // closed + Serial.println("Subscription closed: " + reason); + }, + [&](const String &subId) + { + // This is the callback that will be called when the subscription is + // EOSE + Serial.println("Subscription EOSE: " + subId); + }); + } + catch (const std::exception &e) + { + Serial.println("Error: " + String(e.what())); + } +} + +void nostrTask(void *pvParameters) +{ + int blockFetch = getBlockFetch(); + processNewBlock(blockFetch); + + while (1) + { + for (nostr::NostrPool *pool : pools) + { + // Run internal loop: refresh relays, complete pending connections, send + // pending messages + pool->loop(); + } + vTaskDelay(pdMS_TO_TICKS(100)); + } +} + +void setupNostrTask() +{ + xTaskCreate(nostrTask, "nostrTask", 16384, NULL, 10, &nostrTaskHandle); +} \ No newline at end of file diff --git a/src/lib/nostr_notify.hpp b/src/lib/nostr_notify.hpp new file mode 100644 index 0000000..e76c87b --- /dev/null +++ b/src/lib/nostr_notify.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "shared.hpp" + +#include +#include +#include +#include +#include +#include +#include "price_notify.hpp" +#include "block_notify.hpp" + +void setupNostrNotify(); + +void nostrTask(void *pvParameters); +void setupNostrTask(); \ No newline at end of file diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index 18f36ad..66381fb 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -98,31 +98,40 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data) { if (currentPrice != doc["bitcoin"].as()) { - uint minSecPriceUpd = preferences.getUInt( - "minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE); - uint currentTime = esp_timer_get_time() / 1000000; - - if (lastPriceUpdate == 0 || - (currentTime - lastPriceUpdate) > minSecPriceUpd) - { - // const unsigned long oldPrice = currentPrice; - currentPrice = doc["bitcoin"].as(); - preferences.putUInt("lastPrice", currentPrice); - lastPriceUpdate = currentTime; - // if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) { - 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); - } - //} - } + processNewPrice(doc["bitcoin"].as()); } } } +void processNewPrice(uint newPrice) +{ + uint minSecPriceUpd = preferences.getUInt( + "minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE); + uint currentTime = esp_timer_get_time() / 1000000; + + if (lastPriceUpdate == 0 || + (currentTime - lastPriceUpdate) > minSecPriceUpd) + { + // const unsigned long oldPrice = currentPrice; + currentPrice = newPrice; + if (lastPriceUpdate == 0 || + (currentTime - lastPriceUpdate) > 120) + { + preferences.putUInt("lastPrice", currentPrice); + } + lastPriceUpdate = currentTime; + // if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) { + 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); + } + //} + } +} + uint getLastPriceUpdate() { return lastPriceUpdate; diff --git a/src/lib/price_notify.hpp b/src/lib/price_notify.hpp index 44c343d..485b096 100644 --- a/src/lib/price_notify.hpp +++ b/src/lib/price_notify.hpp @@ -17,6 +17,8 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data); uint getPrice(); void setPrice(uint newPrice); +void processNewPrice(uint newPrice); + bool isPriceNotifyConnected(); void stopPriceNotify(); void restartPriceNotify(); diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 432de65..175d8f0 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -442,7 +442,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) settings["timePerScreen"].as() * 60); } - String strSettings[] = {"hostnamePrefix", "mempoolInstance"}; + String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay"}; for (String setting : strSettings) { @@ -477,7 +477,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", - "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure"}; + "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure", "useNostr"}; for (String setting : boolSettings) { @@ -577,6 +577,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); root["mempoolSecure"] = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); + root["useNostr"] = preferences.getBool("useNostr", DEFAULT_USE_NOSTR); root["ledTestOnPower"] = preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER); root["ledFlashOnUpd"] = preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD); root["ledBrightness"] = preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS); @@ -596,6 +597,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["txPower"] = WiFi.getTxPower(); root["ownDataSource"] = preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE); + root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB); + root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY); + #ifdef HAS_FRONTLIGHT root["hasFrontlight"] = true; root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS); diff --git a/src/main.cpp b/src/main.cpp index 0106eab..a5d980e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -44,7 +44,7 @@ extern "C" void app_main() int64_t currentUptime = esp_timer_get_time() / 1000000; ; - + if (!getIsOTAUpdating()) { #ifdef HAS_FRONTLIGHT