Nostr data source implementation

This commit is contained in:
Djuri Baars 2024-07-11 22:08:42 +02:00
parent 8e71f29d10
commit 87b22e5851
13 changed files with 236 additions and 51 deletions

2
data

@ -1 +1 @@
Subproject commit 2363d98965bb1fdbfdf5d130b41732f5b864e2d0 Subproject commit ee4d6d88c76fa279e643faabf4216c88145e0b2c

View file

@ -41,7 +41,8 @@ lib_deps =
https://github.com/dsbaars/universal_pin https://github.com/dsbaars/universal_pin
https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/dsbaars/GxEPD2#universal_pin
https://github.com/tzapu/WiFiManager.git#v2.0.17 https://github.com/tzapu/WiFiManager.git#v2.0.17
rblb/Nostrduino@^1.2.5
[env:lolin_s3_mini] [env:lolin_s3_mini]
extends = btclock_base extends = btclock_base
board = lolin_s3_mini board = lolin_s3_mini

View file

@ -172,7 +172,25 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data)
return; return;
} }
currentBlockHeight = block["height"].as<uint>(); processNewBlock(block["height"].as<uint>());
}
else if (doc.containsKey("mempool-blocks"))
{
JsonArray blockInfo = doc["mempool-blocks"].as<JsonArray>();
uint medianFee = (uint)round(blockInfo[0]["medianFee"].as<double>());
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<uint>()); // Serial.printf("New block found: %d\r\n", block["height"].as<uint>());
preferences.putUInt("blockHeight", currentBlockHeight); preferences.putUInt("blockHeight", currentBlockHeight);
@ -209,30 +227,22 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data)
queueLedEffect(LED_FLASH_BLOCK_NOTIFY); queueLedEffect(LED_FLASH_BLOCK_NOTIFY);
} }
} }
} }
else if (doc.containsKey("mempool-blocks"))
{
JsonArray blockInfo = doc["mempool-blocks"].as<JsonArray>();
uint medianFee = (uint)round(blockInfo[0]["medianFee"].as<double>()); void processNewBlockFee(uint newBlockFee) {
if (blockMedianFee == newBlockFee)
if (blockMedianFee == medianFee)
{ {
doc.clear();
return; return;
} }
// Serial.printf("New median fee: %d\r\n", medianFee); // Serial.printf("New median fee: %d\r\n", medianFee);
blockMedianFee = medianFee; blockMedianFee = newBlockFee;
if (workQueue != nullptr) if (workQueue != nullptr)
{ {
WorkItem blockUpdate = {TASK_FEE_UPDATE, 0}; WorkItem blockUpdate = {TASK_FEE_UPDATE, 0};
xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); xQueueSend(workQueue, &blockUpdate, portMAX_DELAY);
} }
}
doc.clear();
} }
uint getBlockHeight() { return currentBlockHeight; } uint getBlockHeight() { return currentBlockHeight; }

View file

@ -31,6 +31,9 @@ bool isBlockNotifyConnected();
void stopBlockNotify(); void stopBlockNotify();
void restartBlockNotify(); void restartBlockNotify();
void processNewBlock(uint newBlockHeight);
void processNewBlockFee(uint newBlockFee);
bool getBlockNotifyInit(); bool getBlockNotifyInit();
uint getLastBlockUpdate(); uint getLastBlockUpdate();
int getBlockFetch(); int getBlockFetch();

View file

@ -48,7 +48,9 @@ void setup()
{ {
delay(1000); delay(1000);
} }
} else if (mcp1.digitalRead(1) == LOW) { }
else if (mcp1.digitalRead(1) == LOW)
{
preferences.clear(); preferences.clear();
queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS);
ESP.restart(); ESP.restart();
@ -66,8 +68,16 @@ void setup()
setupTasks(); setupTasks();
setupTimers(); setupTimers();
xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL, if (preferences.getBool("useNostr", DEFAULT_USE_NOSTR))
tskIDLE_PRIORITY, NULL); {
setupNostrNotify();
setupNostrTask();
}
else
{
xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL,
tskIDLE_PRIORITY, NULL);
}
setupButtonTask(); setupButtonTask();
setupOTA(); setupOTA();
@ -401,14 +411,15 @@ void setupHardware()
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT
setupFrontlight(); setupFrontlight();
Wire.beginTransmission(0x5C); Wire.beginTransmission(0x5C);
byte error = Wire.endTransmission(); byte error = Wire.endTransmission();
if (error == 0) { if (error == 0)
{
Serial.println(F("Found BH1750")); Serial.println(F("Found BH1750"));
hasLuxSensor = true; hasLuxSensor = true;
bh1750.begin(BH1750::CONTINUOUS_LOW_RES_MODE, 0x5C); bh1750.begin(BH1750::CONTINUOUS_LOW_RES_MODE, 0x5C);
} }
#endif #endif
} }
@ -742,14 +753,15 @@ void setupFrontlight()
{ {
preferences.putBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE); preferences.putBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE);
} }
} }
float getLightLevel() { float getLightLevel()
{
return bh1750.readLightLevel(); return bh1750.readLightLevel();
} }
bool hasLightLevel() { bool hasLightLevel()
{
return hasLuxSensor; return hasLuxSensor;
} }
#endif #endif
@ -784,5 +796,3 @@ String getFsRev()
fsHash.close(); fsHash.close();
return ret; return ret;
} }

View file

@ -16,6 +16,8 @@
#include "lib/improv.hpp" #include "lib/improv.hpp"
#include "lib/led_handler.hpp" #include "lib/led_handler.hpp"
#include "lib/ota.hpp" #include "lib/ota.hpp"
#include "lib/nostr_notify.hpp"
#include "lib/price_notify.hpp" #include "lib/price_notify.hpp"
#include "lib/screen_handler.hpp" #include "lib/screen_handler.hpp"
#include "lib/shared.hpp" #include "lib/shared.hpp"

View file

@ -22,6 +22,10 @@
#define DEFAULT_HOSTNAME_PREFIX "btclock" #define DEFAULT_HOSTNAME_PREFIX "btclock"
#define DEFAULT_MEMPOOL_INSTANCE "mempool.space" #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_SECONDS_BETWEEN_PRICE_UPDATE 30
#define DEFAULT_MINUTES_FULL_REFRESH 60 #define DEFAULT_MINUTES_FULL_REFRESH 60

123
src/lib/nostr_notify.cpp Normal file
View file

@ -0,0 +1,123 @@
#include "nostr_notify.hpp"
std::vector<nostr::NostrPool *> 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<JsonArray>();
event->toSendableEvent(arr);
// Access the second element which is the object
JsonObject obj = arr[1].as<JsonObject>();
// Access the "tags" array
JsonArray tags = obj["tags"].as<JsonArray>();
// 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<uint>();
}
}
}
if (tagFound)
{
if (typeValue.equals("priceUsd"))
{
processNewPrice(obj["content"].as<uint>());
}
else if (typeValue.equals("blockHeight"))
{
processNewBlock(obj["content"].as<uint>());
}
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);
}

17
src/lib/nostr_notify.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include "shared.hpp"
#include <ArduinoJson.h>
#include <esp32/ESP32Platform.h>
#include <NostrEvent.h>
#include <NostrPool.h>
#include <Transport.h>
#include <Utils.h>
#include "price_notify.hpp"
#include "block_notify.hpp"
void setupNostrNotify();
void nostrTask(void *pvParameters);
void setupNostrTask();

View file

@ -98,31 +98,40 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data)
{ {
if (currentPrice != doc["bitcoin"].as<long>()) if (currentPrice != doc["bitcoin"].as<long>())
{ {
uint minSecPriceUpd = preferences.getUInt( processNewPrice(doc["bitcoin"].as<long>());
"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<uint>();
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);
}
//}
}
} }
} }
} }
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() uint getLastPriceUpdate()
{ {
return lastPriceUpdate; return lastPriceUpdate;

View file

@ -17,6 +17,8 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data);
uint getPrice(); uint getPrice();
void setPrice(uint newPrice); void setPrice(uint newPrice);
void processNewPrice(uint newPrice);
bool isPriceNotifyConnected(); bool isPriceNotifyConnected();
void stopPriceNotify(); void stopPriceNotify();
void restartPriceNotify(); void restartPriceNotify();

View file

@ -442,7 +442,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json)
settings["timePerScreen"].as<uint>() * 60); settings["timePerScreen"].as<uint>() * 60);
} }
String strSettings[] = {"hostnamePrefix", "mempoolInstance"}; String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay"};
for (String setting : strSettings) for (String setting : strSettings)
{ {
@ -477,7 +477,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json)
String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd", String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd",
"mdnsEnabled", "otaEnabled", "stealFocus", "mdnsEnabled", "otaEnabled", "stealFocus",
"mcapBigChar", "useSatsSymbol", "useBlkCountdown", "mcapBigChar", "useSatsSymbol", "useBlkCountdown",
"suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure"}; "suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure", "useNostr"};
for (String setting : boolSettings) for (String setting : boolSettings)
{ {
@ -577,6 +577,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["mempoolInstance"] = root["mempoolInstance"] =
preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
root["mempoolSecure"] = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); 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["ledTestOnPower"] = preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER);
root["ledFlashOnUpd"] = preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD); root["ledFlashOnUpd"] = preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD);
root["ledBrightness"] = preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS); root["ledBrightness"] = preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS);
@ -596,6 +597,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["txPower"] = WiFi.getTxPower(); root["txPower"] = WiFi.getTxPower();
root["ownDataSource"] = preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE); 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 #ifdef HAS_FRONTLIGHT
root["hasFrontlight"] = true; root["hasFrontlight"] = true;
root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS); root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS);

View file

@ -44,7 +44,7 @@ extern "C" void app_main()
int64_t currentUptime = esp_timer_get_time() / 1000000; int64_t currentUptime = esp_timer_get_time() / 1000000;
; ;
if (!getIsOTAUpdating()) if (!getIsOTAUpdating())
{ {
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT