Compare commits

..

17 commits

Author SHA1 Message Date
dc8e348aa3
fix: Set explicit littlefs version tag
All checks were successful
BTClock CI / build (push) Successful in 23m39s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 23s
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 1m1s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Successful in 20s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Successful in 1m2s
BTClock CI / release (push) Successful in 14s
2025-02-19 15:38:54 +01:00
e4ac3c5c94
feat: switch to replaceable events for nostr source
Some checks failed
BTClock CI / build (push) Failing after 5m31s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Has been skipped
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Has been skipped
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Has been skipped
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Has been skipped
BTClock CI / release (push) Has been skipped
2025-02-19 15:15:53 +01:00
0b1a362b53
chore: dependency updates 2025-02-19 14:12:16 +01:00
3265eec308
chore: update dependencies and make eventsource use static jsondocument 2025-01-20 12:05:48 +01:00
678a4ba099
fix: Set WiFi country to NL for scanning 2025-01-19 22:32:04 +01:00
9ea0210864
fix: set better defaults for frontlight enabled devices 2025-01-16 00:30:40 +01:00
b01003f075
fix: Never write to LED0
All checks were successful
BTClock CI / build (push) Successful in 24m32s
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 37s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 22s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Successful in 36s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Successful in 21s
BTClock CI / release (push) Successful in 11s
2025-01-15 22:09:05 +01:00
1083a3222b
Add local public pool support
All checks were successful
BTClock CI / build (push) Successful in 22m59s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 34s
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 22s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Successful in 32s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Successful in 19s
BTClock CI / release (push) Successful in 11s
2025-01-08 02:14:33 +01:00
963f3b10b7
Update WebUI
All checks were successful
BTClock CI / build (push) Successful in 21m25s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 33s
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 21s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Successful in 31s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Successful in 19s
BTClock CI / release (push) Successful in 10s
2025-01-06 01:30:46 +01:00
bf64b2f64f
Merge root certificates 2025-01-06 01:27:13 +01:00
1d61453563
Revert to esp websocket client because websocketsClient does not work 2025-01-06 01:13:09 +01:00
e330984ba2
Refactor BlockNotify to a class, use websocketsClient 2025-01-06 00:43:31 +01:00
ebbec75e6b
Fix V2 message parsing 2025-01-06 00:01:34 +01:00
e19cad05bc
Update ESPAsyncWebserver 2025-01-05 23:15:34 +01:00
7195b7d343
Rewrite price notify to websocketsclient 2025-01-05 23:13:25 +01:00
0999dd08ad
Remove deprecated ArduinoJson methods 2025-01-05 23:13:05 +01:00
178748b94d
WebUI update 2025-01-05 22:47:13 +01:00
24 changed files with 566 additions and 513 deletions

2
data

@ -1 +1 @@
Subproject commit 033fe098295ab6da6568d6298b4380e51bec0b98 Subproject commit 0116cd68cdfdf383823f74e0f9665a1700cf0500

View file

@ -4,6 +4,6 @@ dependencies:
source: source:
type: idf type: idf
version: 4.4.7 version: 4.4.7
manifest_hash: cd2f3ee15e776d949eb4ea4eddc8f39b30c2a7905050850eed01ab4928143cff manifest_hash: 1d4ef353a86901733b106a1897b186dbf9fc091a4981f0560ea2f6899b7a3d44
target: esp32s3 target: esp32s3
version: 1.0.0 version: 1.0.0

View file

@ -15,7 +15,7 @@ default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd,
[env] [env]
[btclock_base] [btclock_base]
platform = espressif32 @ ^6.9.0 platform = espressif32 @ ^6.10.0
framework = arduino, espidf framework = arduino, espidf
monitor_speed = 115200 monitor_speed = 115200
monitor_filters = esp32_exception_decoder, colorize monitor_filters = esp32_exception_decoder, colorize
@ -30,17 +30,17 @@ build_flags =
-DLAST_BUILD_TIME=$UNIX_TIME -DLAST_BUILD_TIME=$UNIX_TIME
-DARDUINO_USB_CDC_ON_BOOT -DARDUINO_USB_CDC_ON_BOOT
-DCORE_DEBUG_LEVEL=0 -DCORE_DEBUG_LEVEL=0
-D DEFAULT_BOOT_TEXT=\"BTCLOCK\" -D CONFIG_ASYNC_TCP_STACK_SIZE=16384
-fexceptions -fexceptions
build_unflags = build_unflags =
-Werror=all -Werror=all
-fno-exceptions -fno-exceptions
lib_deps = lib_deps =
https://github.com/joltwallet/esp_littlefs.git https://github.com/joltwallet/esp_littlefs.git#v1.16.4
bblanchon/ArduinoJson@^7.2.1 bblanchon/ArduinoJson@^7.3.0
mathieucarbou/ESPAsyncWebServer @ 3.3.23 esp32async/ESPAsyncWebServer @ 3.7.0
robtillaart/MCP23017@^0.8.0 robtillaart/MCP23017@^0.9.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/universal_pin#feature/mcp23017_rt
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
@ -79,9 +79,10 @@ build_flags =
-D I2C_SDA_PIN=35 -D I2C_SDA_PIN=35
-D I2C_SCK_PIN=36 -D I2C_SCK_PIN=36
-D HAS_FRONTLIGHT -D HAS_FRONTLIGHT
-D PCA_OE_PIN=45 -D PCA_OE_PIN=48
-D PCA_I2C_ADDR=0x42 -D PCA_I2C_ADDR=0x42
-D IS_HW_REV_B -D IS_HW_REV_B
lib_deps = lib_deps =
${btclock_base.lib_deps} ${btclock_base.lib_deps}
robtillaart/PCA9685@^0.7.1 robtillaart/PCA9685@^0.7.1
@ -100,6 +101,7 @@ build_flags =
-D USE_QR -D USE_QR
-D VERSION_EPD_2_13 -D VERSION_EPD_2_13
-D HW_REV=\"REV_A_EPD_2_13\" -D HW_REV=\"REV_A_EPD_2_13\"
-D CONFIG_ARDUINO_MAIN_TASK_STACK_SIZE=16384
platform_packages = platform_packages =
platformio/tool-mklittlefs@^1.203.210628 platformio/tool-mklittlefs@^1.203.210628
earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216
@ -112,6 +114,7 @@ build_flags =
-D USE_QR -D USE_QR
-D VERSION_EPD_2_13 -D VERSION_EPD_2_13
-D HW_REV=\"REV_B_EPD_2_13\" -D HW_REV=\"REV_B_EPD_2_13\"
-D CONFIG_ARDUINO_MAIN_TASK_STACK_SIZE=16384
platform_packages = platform_packages =
platformio/tool-mklittlefs@^1.203.210628 platformio/tool-mklittlefs@^1.203.210628
earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216 earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216

View file

@ -1,14 +1,14 @@
#include "block_notify.hpp" #include "block_notify.hpp"
#include "led_handler.hpp"
char *wsServer; // Initialize static members
esp_websocket_client_handle_t blockNotifyClient = NULL; esp_websocket_client_handle_t BlockNotify::wsClient = nullptr;
uint32_t currentBlockHeight = 873400; uint32_t BlockNotify::currentBlockHeight = 878000;
uint16_t blockMedianFee = 1; uint16_t BlockNotify::blockMedianFee = 1;
bool blockNotifyInit = false; bool BlockNotify::notifyInit = false;
unsigned long int lastBlockUpdate; unsigned long int BlockNotify::lastBlockUpdate = 0;
TaskHandle_t BlockNotify::taskHandle = nullptr;
const char *mempoolWsCert = R"EOF( const char* BlockNotify::mempoolWsCert = R"EOF(
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
@ -43,22 +43,106 @@ VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
jjxDah2nGN59PRbxYvnKkKj9 jjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE----- -----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"; )EOF";
void setupBlockNotify() 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();
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 dataArray = doc.createNestedArray("data");
dataArray.add("blocks");
dataArray.add("mempool-blocks");
String sub;
serializeJson(doc, sub);
esp_websocket_client_send_text(wsClient, sub.c_str(), sub.length(), portMAX_DELAY);
break;
}
case WEBSOCKET_EVENT_DATA:
instance.onWebsocketMessage(data);
break;
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::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>()) {
JsonObject block = doc["block"];
if (block["height"].as<uint>() != currentBlockHeight) {
processNewBlock(block["height"].as<uint>());
}
}
else if (doc["mempool-blocks"].is<JsonArray>()) {
JsonArray blockInfo = doc["mempool-blocks"].as<JsonArray>();
uint medianFee = (uint)round(blockInfo[0]["medianFee"].as<double>());
processNewBlockFee(medianFee);
}
}
void BlockNotify::setup() {
IPAddress result; IPAddress result;
int dnsErr = -1; int dnsErr = -1;
String mempoolInstance = String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
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); dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result);
if (dnsErr != 1) if (dnsErr != 1) {
{
Serial.print(mempoolInstance); Serial.print(mempoolInstance);
Serial.println(F("mempool DNS could not be resolved")); Serial.println(F("mempool DNS could not be resolved"));
WiFi.reconnect(); WiFi.reconnect();
@ -67,121 +151,46 @@ void setupBlockNotify()
} }
// Get current block height through regular API // Get current block height through regular API
int blockFetch = getBlockFetch(); int blockFetch = fetchLatestBlock();
if (blockFetch > currentBlockHeight) if (blockFetch > currentBlockHeight)
currentBlockHeight = blockFetch; currentBlockHeight = blockFetch;
if (currentBlockHeight != -1) if (currentBlockHeight != -1) {
{
lastBlockUpdate = esp_timer_get_time() / 1000000; lastBlockUpdate = esp_timer_get_time() / 1000000;
} }
if (workQueue != nullptr) if (workQueue != nullptr) {
{
WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0}; WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0};
xQueueSend(workQueue, &blockUpdate, portMAX_DELAY); xQueueSend(workQueue, &blockUpdate, portMAX_DELAY);
} }
// std::strcpy(wsServer, String("wss://" + mempoolInstance + const bool useSSL = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE);
// "/api/v1/ws").c_str()); const String protocol = useSSL ? "wss" : "ws";
String wsUri = protocol + "://" + mempoolInstance + "/api/v1/ws";
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 = { esp_websocket_client_config_t config = {
// .uri = "wss://mempool.space/api/v1/ws",
.task_stack = (6*1024), .task_stack = (6*1024),
.user_agent = USER_AGENT .user_agent = USER_AGENT
}; };
if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) { if (useSSL) {
config.cert_pem = mempoolWsCert; config.cert_pem = mempoolWsCert;
} }
config.uri = mempoolUri.c_str(); config.uri = wsUri.c_str();
Serial.printf("Connecting to %s\r\n", preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE)); Serial.printf("Connecting to %s\r\n", mempoolInstance.c_str());
blockNotifyClient = esp_websocket_client_init(&config); wsClient = esp_websocket_client_init(&config);
esp_websocket_register_events(blockNotifyClient, WEBSOCKET_EVENT_ANY, esp_websocket_register_events(wsClient, WEBSOCKET_EVENT_ANY, onWebsocketEvent, wsClient);
onWebsocketBlockEvent, blockNotifyClient); esp_websocket_client_start(wsClient);
esp_websocket_client_start(blockNotifyClient);
} }
void onWebsocketBlockEvent(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;
const String sub = "{\"action\": \"want\", \"data\":[\"blocks\", \"mempool-blocks\"]}";
switch (event_id)
{
case WEBSOCKET_EVENT_CONNECTED:
blockNotifyInit = true;
Serial.println(F("Connected to Mempool.space WebSocket"));
Serial.println(sub); void BlockNotify::processNewBlock(uint32_t newBlockHeight) {
if (esp_websocket_client_send_text(blockNotifyClient, sub.c_str(), if (newBlockHeight <= currentBlockHeight)
sub.length(), portMAX_DELAY) == -1)
{
Serial.println(F("Mempool.space WS Block Subscribe Error"));
}
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;
}
}
void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data)
{
JsonDocument doc;
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.containsKey("block"))
{
JsonObject block = doc["block"];
if (block["height"].as<uint>() == currentBlockHeight) {
return;
}
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(uint32_t newBlockHeight) {
if (currentBlockHeight <= newBlockHeight)
{ {
return; return;
} }
@ -220,13 +229,12 @@ void processNewBlock(uint32_t newBlockHeight) {
} }
} }
void processNewBlockFee(uint16_t newBlockFee) { void BlockNotify::processNewBlockFee(uint16_t newBlockFee) {
if (blockMedianFee == newBlockFee) if (blockMedianFee == newBlockFee)
{ {
return; return;
} }
// Serial.printf("New median fee: %d\r\n", medianFee);
blockMedianFee = newBlockFee; blockMedianFee = newBlockFee;
if (workQueue != nullptr) if (workQueue != nullptr)
@ -236,60 +244,54 @@ void processNewBlockFee(uint16_t newBlockFee) {
} }
} }
uint32_t getBlockHeight() { return currentBlockHeight; } uint32_t BlockNotify::getBlockHeight() const {
return currentBlockHeight;
}
void setBlockHeight(uint32_t newBlockHeight) void BlockNotify::setBlockHeight(uint32_t newBlockHeight)
{ {
currentBlockHeight = newBlockHeight; currentBlockHeight = newBlockHeight;
} }
uint16_t getBlockMedianFee() { return blockMedianFee; } uint16_t BlockNotify::getBlockMedianFee() const {
return blockMedianFee;
}
void setBlockMedianFee(uint16_t newBlockMedianFee) void BlockNotify::setBlockMedianFee(uint16_t newBlockMedianFee)
{ {
blockMedianFee = newBlockMedianFee; blockMedianFee = newBlockMedianFee;
} }
bool isBlockNotifyConnected() bool BlockNotify::isConnected() const
{ {
if (blockNotifyClient == NULL) if (wsClient == NULL)
return false; return false;
return esp_websocket_client_is_connected(blockNotifyClient); return esp_websocket_client_is_connected(wsClient);
} }
bool getBlockNotifyInit() bool BlockNotify::isInitialized() const
{ {
return blockNotifyInit; return notifyInit;
} }
void stopBlockNotify() void BlockNotify::stop()
{ {
if (blockNotifyClient == NULL) if (wsClient == NULL)
return; return;
esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000)); esp_websocket_client_close(wsClient, portMAX_DELAY);
esp_websocket_client_stop(blockNotifyClient); esp_websocket_client_stop(wsClient);
esp_websocket_client_destroy(blockNotifyClient); esp_websocket_client_destroy(wsClient);
wsClient = NULL;
blockNotifyClient = NULL;
} }
void restartBlockNotify() void BlockNotify::restart()
{ {
stopBlockNotify(); stop();
setup();
if (blockNotifyClient == NULL) {
setupBlockNotify();
return;
} }
// esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000)); int BlockNotify::fetchLatestBlock() {
// esp_websocket_client_stop(blockNotifyClient);
// esp_websocket_client_start(blockNotifyClient);
}
int getBlockFetch() {
try { try {
String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "https" : "http"; const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "https" : "http";
@ -312,12 +314,12 @@ int getBlockFetch() {
return 2203; // B-T-C 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;
} }

View file

@ -5,7 +5,6 @@
#include <HTTPClient.h> #include <HTTPClient.h>
#include <esp_timer.h> #include <esp_timer.h>
#include <esp_websocket_client.h> #include <esp_websocket_client.h>
#include <cstring> #include <cstring>
#include <string> #include <string>
@ -14,28 +13,53 @@
#include "lib/timers.hpp" #include "lib/timers.hpp"
#include "lib/shared.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, // Block notification setup and control
int32_t event_id, void *event_data); void setup();
void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data); void stop();
void restart();
bool isConnected() const;
bool isInitialized() const;
// Block height management
void setBlockHeight(uint32_t newBlockHeight); void setBlockHeight(uint32_t newBlockHeight);
uint32_t getBlockHeight(); uint32_t getBlockHeight() const;
// Block fee management
void setBlockMedianFee(uint16_t blockMedianFee); void setBlockMedianFee(uint16_t blockMedianFee);
uint16_t getBlockMedianFee(); uint16_t getBlockMedianFee() const;
bool isBlockNotifyConnected();
void stopBlockNotify();
void restartBlockNotify();
// Block processing
void processNewBlock(uint32_t newBlockHeight); void processNewBlock(uint32_t newBlockHeight);
void processNewBlockFee(uint16_t newBlockFee); void processNewBlockFee(uint16_t newBlockFee);
bool getBlockNotifyInit(); // Block fetch and update tracking
uint32_t getLastBlockUpdate(); int fetchLatestBlock();
int getBlockFetch(); uint getLastBlockUpdate() const;
void setLastBlockUpdate(uint32_t lastUpdate); void setLastBlockUpdate(uint lastUpdate);
private:
BlockNotify() = default; // Private constructor for singleton
void setupTask();
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 const char* mempoolWsCert;
static esp_websocket_client_handle_t wsClient;
static uint32_t currentBlockHeight;
static uint16_t blockMedianFee;
static bool notifyInit;
static unsigned long int lastBlockUpdate;
static TaskHandle_t taskHandle;
};

View file

@ -132,9 +132,25 @@ void setup()
void setupWifi() void setupWifi()
{ {
WiFi.onEvent(WiFiEvent); 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.setAutoConnect(true);
WiFi.setAutoReconnect(true); WiFi.setAutoReconnect(true);
WiFi.begin(); WiFi.begin();
if (preferences.getInt("txPower", DEFAULT_TX_POWER)) if (preferences.getInt("txPower", DEFAULT_TX_POWER))
{ {
if (WiFi.setTxPower( if (WiFi.setTxPower(
@ -172,6 +188,7 @@ void setupWifi()
wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT)); wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT));
wm.setWiFiAutoReconnect(false); wm.setWiFiAutoReconnect(false);
wm.setDebugOutput(false); wm.setDebugOutput(false);
wm.setCountry("NL");
wm.setConfigPortalBlocking(true); wm.setConfigPortalBlocking(true);
wm.setAPCallback([&](WiFiManager *wifiManager) wm.setAPCallback([&](WiFiManager *wifiManager)
@ -265,7 +282,7 @@ void setupPreferences()
EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR));
EPDManager::getInstance().setBackgroundColor(preferences.getUInt("bgColor", DEFAULT_BG_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); setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD);
if (!preferences.isKey("enableDebugLog")) { if (!preferences.isKey("enableDebugLog")) {
@ -373,7 +390,7 @@ void setupWebsocketClients(void *pvParameters)
} }
else if (dataSource == THIRD_PARTY_SOURCE) else if (dataSource == THIRD_PARTY_SOURCE)
{ {
setupBlockNotify(); BlockNotify::getInstance().setup();
setupPriceNotify(); setupPriceNotify();
} }

View file

@ -46,8 +46,8 @@
#define DEFAULT_LUX_LIGHT_TOGGLE 128 #define DEFAULT_LUX_LIGHT_TOGGLE 128
#define DEFAULT_FL_OFF_WHEN_DARK true #define DEFAULT_FL_OFF_WHEN_DARK true
#define DEFAULT_FL_ALWAYS_ON false #define DEFAULT_FL_ALWAYS_ON true
#define DEFAULT_FL_FLASH_ON_UPDATE false #define DEFAULT_FL_FLASH_ON_UPDATE true
#define DEFAULT_LED_STATUS false #define DEFAULT_LED_STATUS false
#define DEFAULT_TIMER_ACTIVE true #define DEFAULT_TIMER_ACTIVE true
@ -60,6 +60,7 @@
#define DEFAULT_MINING_POOL_STATS_ENABLED false #define DEFAULT_MINING_POOL_STATS_ENABLED false
#define DEFAULT_MINING_POOL_NAME "ocean" #define DEFAULT_MINING_POOL_NAME "ocean"
#define DEFAULT_MINING_POOL_USER "38Qkkei3SuF1Eo45BaYmRHUneRD54yyTFy" // Random actual Ocean hasher #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_ENABLED false
#define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422" #define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422"

View file

@ -69,7 +69,26 @@ EPDManager::EPDManager()
, fontSatsymbol{nullptr} , fontSatsymbol{nullptr}
, bgColor{GxEPD_BLACK} , bgColor{GxEPD_BLACK}
, fgColor{GxEPD_WHITE} , fgColor{GxEPD_WHITE}
, display{EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET[0], &EPD_BUSY[0])} , 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
}
{ {
} }
@ -100,10 +119,11 @@ void EPDManager::initialize() {
String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME); String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME);
loadFonts(fontName); loadFonts(fontName);
// Initialize first display // Initialize displays
switchToDisplay(0);
std::lock_guard<std::mutex> lockMcp(mcpMutex); std::lock_guard<std::mutex> lockMcp(mcpMutex);
for (auto& display : displays) {
display.init(0, true, 30); display.init(0, true, 30);
}
// Create update queue and task // Create update queue and task
updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem)); updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem));
@ -208,48 +228,46 @@ void EPDManager::waitUntilNoneBusy() {
} }
void EPDManager::setupDisplay(uint dispNum, const GFXfont* font) { void EPDManager::setupDisplay(uint dispNum, const GFXfont* font) {
switchToDisplay(dispNum); displays[dispNum].setRotation(2);
display.setRotation(2); displays[dispNum].setFont(font);
display.setFont(font); displays[dispNum].setTextColor(fgColor);
display.setTextColor(fgColor); displays[dispNum].fillScreen(bgColor);
display.fillScreen(bgColor);
} }
void EPDManager::splitText(uint dispNum, const String& top, const String& bottom, bool partial) { void EPDManager::splitText(uint dispNum, const String& top, const String& bottom, bool partial) {
switchToDisplay(dispNum);
if (preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) { if (preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) {
display.setRotation(1); displays[dispNum].setRotation(1);
} else { } else {
display.setRotation(2); displays[dispNum].setRotation(2);
} }
display.setFont(fontSmall); displays[dispNum].setFont(fontSmall);
display.setTextColor(fgColor); displays[dispNum].setTextColor(fgColor);
// Top text // Top text
int16_t ttbx, ttby; int16_t ttbx, ttby;
uint16_t ttbw, ttbh; uint16_t ttbw, ttbh;
display.getTextBounds(top, 0, 0, &ttbx, &ttby, &ttbw, &ttbh); displays[dispNum].getTextBounds(top, 0, 0, &ttbx, &ttby, &ttbw, &ttbh);
uint16_t tx = ((display.width() - ttbw) / 2) - ttbx; uint16_t tx = ((displays[dispNum].width() - ttbw) / 2) - ttbx;
uint16_t ty = ((display.height() - ttbh) / 2) - ttby - ttbh / 2 - 12; uint16_t ty = ((displays[dispNum].height() - ttbh) / 2) - ttby - ttbh / 2 - 12;
// Bottom text // Bottom text
int16_t tbbx, tbby; int16_t tbbx, tbby;
uint16_t tbbw, tbbh; uint16_t tbbw, tbbh;
display.getTextBounds(bottom, 0, 0, &tbbx, &tbby, &tbbw, &tbbh); displays[dispNum].getTextBounds(bottom, 0, 0, &tbbx, &tbby, &tbbw, &tbbh);
uint16_t bx = ((display.width() - tbbw) / 2) - tbbx; uint16_t bx = ((displays[dispNum].width() - tbbw) / 2) - tbbx;
uint16_t by = ((display.height() - tbbh) / 2) - tbby + tbbh / 2 + 12; uint16_t by = ((displays[dispNum].height() - tbbh) / 2) - tbby + tbbh / 2 + 12;
// Make separator as wide as the shortest text // Make separator as wide as the shortest text
uint16_t lineWidth = (tbbw < ttbh) ? tbbw : ttbw; uint16_t lineWidth = (tbbw < ttbh) ? tbbw : ttbw;
uint16_t lineX = round((display.width() - lineWidth) / 2); uint16_t lineX = round((displays[dispNum].width() - lineWidth) / 2);
display.fillScreen(bgColor); displays[dispNum].fillScreen(bgColor);
display.setCursor(tx, ty); displays[dispNum].setCursor(tx, ty);
display.print(top); displays[dispNum].print(top);
display.fillRoundRect(lineX, display.height() / 2 - 3, displays[dispNum].fillRoundRect(lineX, displays[dispNum].height() / 2 - 3,
lineWidth, 6, 3, fgColor); lineWidth, 6, 3, fgColor);
display.setCursor(bx, by); displays[dispNum].setCursor(bx, by);
display.print(bottom); displays[dispNum].print(bottom);
} }
void EPDManager::showDigit(uint dispNum, char chr, bool partial, const GFXfont* font) { void EPDManager::showDigit(uint dispNum, char chr, bool partial, const GFXfont* font) {
@ -262,17 +280,17 @@ void EPDManager::showDigit(uint dispNum, char chr, bool partial, const GFXfont*
int16_t tbx, tby; int16_t tbx, tby;
uint16_t tbw, tbh; uint16_t tbw, tbh;
display.getTextBounds(str, 0, 0, &tbx, &tby, &tbw, &tbh); displays[dispNum].getTextBounds(str, 0, 0, &tbx, &tby, &tbw, &tbh);
uint16_t x = ((display.width() - tbw) / 2) - tbx; uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx;
uint16_t y = ((display.height() - tbh) / 2) - tby; uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby;
display.setCursor(x, y); displays[dispNum].setCursor(x, y);
display.print(str); displays[dispNum].print(str);
if (chr == '.') { if (chr == '.') {
display.fillRect(0, 0, display.width(), displays[dispNum].fillRect(0, 0, displays[dispNum].width(),
round(display.height() * 0.67), bgColor); round(displays[dispNum].height() * 0.67), bgColor);
} }
} }
@ -281,11 +299,11 @@ void EPDManager::showChars(uint dispNum, const String& chars, bool partial, cons
int16_t tbx, tby; int16_t tbx, tby;
uint16_t tbw, tbh; uint16_t tbw, tbh;
display.getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh); displays[dispNum].getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh);
// Center the bounding box by transposition of the origin // Center the bounding box by transposition of the origin
uint16_t x = ((display.width() - tbw) / 2) - tbx; uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx;
uint16_t y = ((display.height() - tbh) / 2) - tby; uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby;
for (size_t i = 0; i < chars.length(); i++) { for (size_t i = 0; i < chars.length(); i++) {
char c = chars[i]; char c = chars[i];
@ -295,12 +313,12 @@ void EPDManager::showChars(uint dispNum, const String& chars, bool partial, cons
int16_t dotDescent = dotGlyph->yOffset; int16_t dotDescent = dotGlyph->yOffset;
// Draw the dot with adjusted y-position // Draw the dot with adjusted y-position
display.setCursor(x, y + dotDescent + dotGlyph->height + 8); displays[dispNum].setCursor(x, y + dotDescent + dotGlyph->height + 8);
display.print(c); displays[dispNum].print(c);
} else { } else {
// For other characters, use the original y-position // For other characters, use the original y-position
display.setCursor(x, y); displays[dispNum].setCursor(x, y);
display.print(c); displays[dispNum].print(c);
} }
// Move x-position for the next character // Move x-position for the next character
@ -309,12 +327,11 @@ void EPDManager::showChars(uint dispNum, const String& chars, bool partial, cons
} }
bool EPDManager::renderIcon(uint dispNum, const String& text, bool partial) { bool EPDManager::renderIcon(uint dispNum, const String& text, bool partial) {
switchToDisplay(dispNum); displays[dispNum].setRotation(2);
display.setRotation(2); displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(),
display.setPartialWindow(0, 0, display.width(), displays[dispNum].height());
display.height()); displays[dispNum].fillScreen(bgColor);
display.fillScreen(bgColor); displays[dispNum].setTextColor(fgColor);
display.setTextColor(fgColor);
uint iconIndex = 0; uint iconIndex = 0;
uint width = 122; uint width = 122;
@ -335,28 +352,27 @@ bool EPDManager::renderIcon(uint dispNum, const String& text, bool partial) {
return false; return false;
} }
int x_offset = (display.width() - logo.width) / 2; int x_offset = (displays[dispNum].width() - logo.width) / 2;
int y_offset = (display.height() - logo.height) / 2; int y_offset = (displays[dispNum].height() - logo.height) / 2;
display.drawInvertedBitmap(x_offset, y_offset, logo.data, displays[dispNum].drawInvertedBitmap(x_offset, y_offset, logo.data,
logo.width, logo.height, fgColor); logo.width, logo.height, fgColor);
return true; return true;
} }
int x_offset = (display.width() - width) / 2; int x_offset = (displays[dispNum].width() - width) / 2;
int y_offset = (display.height() - height) / 2; int y_offset = (displays[dispNum].height() - height) / 2;
display.drawInvertedBitmap(x_offset, y_offset, epd_icons_allArray[iconIndex], displays[dispNum].drawInvertedBitmap(x_offset, y_offset, epd_icons_allArray[iconIndex],
width, height, fgColor); width, height, fgColor);
return true; return true;
} }
void EPDManager::renderText(uint dispNum, const String& text, bool partial) { void EPDManager::renderText(uint dispNum, const String& text, bool partial) {
switchToDisplay(dispNum); displays[dispNum].setRotation(2);
display.setRotation(2); displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(),
display.setPartialWindow(0, 0, display.width(), displays[dispNum].height());
display.height()); displays[dispNum].fillScreen(GxEPD_WHITE);
display.fillScreen(GxEPD_WHITE); displays[dispNum].setTextColor(GxEPD_BLACK);
display.setTextColor(GxEPD_BLACK); displays[dispNum].setCursor(0, 50);
display.setCursor(0, 50);
std::stringstream ss; std::stringstream ss;
ss.str(text.c_str()); ss.str(text.c_str());
@ -365,17 +381,16 @@ void EPDManager::renderText(uint dispNum, const String& text, bool partial) {
while (std::getline(ss, line, '\n')) { while (std::getline(ss, line, '\n')) {
if (line.rfind("*", 0) == 0) { if (line.rfind("*", 0) == 0) {
line.erase(std::remove(line.begin(), line.end(), '*'), line.end()); line.erase(std::remove(line.begin(), line.end(), '*'), line.end());
display.setFont(&FreeSansBold9pt7b); displays[dispNum].setFont(&FreeSansBold9pt7b);
} else { } else {
display.setFont(&FreeSans9pt7b); displays[dispNum].setFont(&FreeSans9pt7b);
} }
display.println(line.c_str()); displays[dispNum].println(line.c_str());
} }
} }
void EPDManager::renderQr(uint dispNum, const String& text, bool partial) { void EPDManager::renderQr(uint dispNum, const String& text, bool partial) {
#ifdef USE_QR #ifdef USE_QR
switchToDisplay(dispNum);
// Dynamically allocate QR buffer // Dynamically allocate QR buffer
uint8_t* qrcode = (uint8_t*)malloc(qrcodegen_BUFFER_LEN_MAX); uint8_t* qrcode = (uint8_t*)malloc(qrcodegen_BUFFER_LEN_MAX);
if (!qrcode) { if (!qrcode) {
@ -390,17 +405,17 @@ void EPDManager::renderQr(uint dispNum, const String& text, bool partial) {
if (ok) { if (ok) {
const int size = qrcodegen_getSize(qrcode); const int size = qrcodegen_getSize(qrcode);
const int padding = floor(float(display.width() - (size * 4)) / 2); const int padding = floor(float(displays[dispNum].width() - (size * 4)) / 2);
const int paddingY = floor(float(display.height() - (size * 4)) / 2); const int paddingY = floor(float(displays[dispNum].height() - (size * 4)) / 2);
display.setRotation(2); displays[dispNum].setRotation(2);
display.setPartialWindow(0, 0, display.width(), displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(),
display.height()); displays[dispNum].height());
display.fillScreen(GxEPD_WHITE); displays[dispNum].fillScreen(GxEPD_WHITE);
for (int y = 0; y < size * 4; y++) { for (int y = 0; y < size * 4; y++) {
for (int x = 0; x < size * 4; x++) { for (int x = 0; x < size * 4; x++) {
display.drawPixel( displays[dispNum].drawPixel(
padding + x, paddingY + y, padding + x, paddingY + y,
qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4))
? GxEPD_BLACK ? GxEPD_BLACK
@ -434,10 +449,10 @@ void EPDManager::updateDisplayTask(void* pvParameters) noexcept {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
std::lock_guard<std::mutex> lock(instance.displayMutexes[epdIndex]); std::lock_guard<std::mutex> lock(instance.displayMutexes[epdIndex]);
{
std::lock_guard<std::mutex> lockMcp(mcpMutex); std::lock_guard<std::mutex> lockMcp(mcpMutex);
instance.initializeDisplay(epdIndex); instance.displays[epdIndex].init(0, false, 40);
}
uint32_t count = 0; uint32_t count = 0;
while (instance.EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) { while (instance.EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) {
@ -454,8 +469,8 @@ void EPDManager::updateDisplayTask(void* pvParameters) noexcept {
char tries = 0; char tries = 0;
while (tries < 3) { while (tries < 3) {
if (instance.display.displayWithReturn(updatePartial)) { if (instance.displays[epdIndex].displayWithReturn(updatePartial)) {
instance.display.powerOff(); instance.displays[epdIndex].powerOff();
instance.currentContent[epdIndex] = instance.content[epdIndex]; instance.currentContent[epdIndex] = instance.content[epdIndex];
if (!updatePartial) { if (!updatePartial) {
instance.lastFullRefresh[epdIndex] = millis(); instance.lastFullRefresh[epdIndex] = millis();
@ -520,14 +535,3 @@ void EPDManager::prepareDisplayUpdateTask(void* pvParameters) {
} }
} }
} }
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);
}

View file

@ -80,8 +80,6 @@ private:
static void updateDisplayTask(void* pvParameters) noexcept; static void updateDisplayTask(void* pvParameters) noexcept;
static void prepareDisplayUpdateTask(void* pvParameters); static void prepareDisplayUpdateTask(void* pvParameters);
void switchToDisplay(uint dispNum);
void initializeDisplay(uint dispNum);
// Member variables // Member variables
std::array<String, NUM_SCREENS> currentContent; std::array<String, NUM_SCREENS> currentContent;
@ -121,8 +119,8 @@ private:
static std::array<MCP23X17_Pin, NUM_SCREENS> EPD_RESET; static std::array<MCP23X17_Pin, NUM_SCREENS> EPD_RESET;
#endif #endif
// Single display instance // Display array
GxEPD2_BW<EPD_CLASS, EPD_CLASS::HEIGHT> display; std::array<GxEPD2_BW<EPD_CLASS, EPD_CLASS::HEIGHT>, NUM_SCREENS> displays;
static constexpr size_t UPDATE_QUEUE_SIZE = 14; static constexpr size_t UPDATE_QUEUE_SIZE = 14;
static constexpr uint32_t BUSY_TIMEOUT_COUNT = 200; static constexpr uint32_t BUSY_TIMEOUT_COUNT = 200;

View file

@ -535,7 +535,7 @@ void LedHandler::frontlightSetBrightness(uint brightness) {
} }
for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { 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<uint16_t> LedHandler::frontlightGetStatus() {
std::vector<uint16_t> statuses; std::vector<uint16_t> statuses;
for (int ledPin = 1; ledPin <= NUM_SCREENS; ledPin++) { for (int ledPin = 1; ledPin <= NUM_SCREENS; ledPin++) {
uint16_t a = 0, b = 0; uint16_t a = 0, b = 0;
flArray.getPWM(ledPin, &a, &b); flArray.getPWM(ledPin + 1, &a, &b);
statuses.push_back(round(b - a / 4096)); statuses.push_back(round(b - a / 4096));
} }
return statuses; return statuses;
@ -576,7 +576,7 @@ void LedHandler::frontlightFadeInAll(int flDelayTime, bool staggered) {
} else { } else {
for (int dutyCycle = 0; dutyCycle <= maxBrightness; dutyCycle += FL_FADE_STEP) { for (int dutyCycle = 0; dutyCycle <= maxBrightness; dutyCycle += FL_FADE_STEP) {
for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) {
flArray.setPWM(ledPin, 0, dutyCycle); flArray.setPWM(ledPin + 1, 0, dutyCycle);
} }
vTaskDelay(pdMS_TO_TICKS(flDelayTime)); vTaskDelay(pdMS_TO_TICKS(flDelayTime));
} }
@ -611,7 +611,7 @@ void LedHandler::frontlightFadeOutAll(int flDelayTime, bool staggered) {
} else { } else {
for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= FL_FADE_STEP) { for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= FL_FADE_STEP) {
for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) { for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) {
flArray.setPWM(ledPin, 0, dutyCycle); flArray.setPWM(ledPin + 1, 0, dutyCycle);
} }
vTaskDelay(pdMS_TO_TICKS(flDelayTime)); 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) { 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)); 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) { 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)); vTaskDelay(pdMS_TO_TICKS(flDelayTime));
} }
} }

View file

@ -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_BRAIINS = "braiins";
const char* PoolFactory::MINING_POOL_NAME_SATOSHI_RADIO = "satoshi_radio"; 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_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_GOBRRR_POOL = "gobrrr_pool";
const char* PoolFactory::MINING_POOL_NAME_CKPOOL = "ckpool"; const char* PoolFactory::MINING_POOL_NAME_CKPOOL = "ckpool";
const char* PoolFactory::MINING_POOL_NAME_EU_CKPOOL = "eu_ckpool"; const char* PoolFactory::MINING_POOL_NAME_EU_CKPOOL = "eu_ckpool";
@ -17,6 +18,7 @@ std::unique_ptr<MiningPoolInterface> PoolFactory::createPool(const std::string&
{MINING_POOL_NAME_BRAIINS, []() { return std::make_unique<BraiinsPool>(); }}, {MINING_POOL_NAME_BRAIINS, []() { return std::make_unique<BraiinsPool>(); }},
{MINING_POOL_NAME_SATOSHI_RADIO, []() { return std::make_unique<SatoshiRadioPool>(); }}, {MINING_POOL_NAME_SATOSHI_RADIO, []() { return std::make_unique<SatoshiRadioPool>(); }},
{MINING_POOL_NAME_PUBLIC_POOL, []() { return std::make_unique<PublicPool>(); }}, {MINING_POOL_NAME_PUBLIC_POOL, []() { return std::make_unique<PublicPool>(); }},
{MINING_POOL_NAME_LOCAL_PUBLIC_POOL, []() { return std::make_unique<LocalPublicPool>(); }},
{MINING_POOL_NAME_GOBRRR_POOL, []() { return std::make_unique<GoBrrrPool>(); }}, {MINING_POOL_NAME_GOBRRR_POOL, []() { return std::make_unique<GoBrrrPool>(); }},
{MINING_POOL_NAME_CKPOOL, []() { return std::make_unique<CKPool>(); }}, {MINING_POOL_NAME_CKPOOL, []() { return std::make_unique<CKPool>(); }},
{MINING_POOL_NAME_EU_CKPOOL, []() { return std::make_unique<EUCKPool>(); }} {MINING_POOL_NAME_EU_CKPOOL, []() { return std::make_unique<EUCKPool>(); }}

View file

@ -10,6 +10,7 @@
#include "ocean/ocean_pool.hpp" #include "ocean/ocean_pool.hpp"
#include "satoshi_radio/satoshi_radio_pool.hpp" #include "satoshi_radio/satoshi_radio_pool.hpp"
#include "public_pool/public_pool.hpp" #include "public_pool/public_pool.hpp"
#include "public_pool/local_public_pool.hpp"
#include "gobrrr_pool/gobrrr_pool.hpp" #include "gobrrr_pool/gobrrr_pool.hpp"
#include "ckpool/ckpool.hpp" #include "ckpool/ckpool.hpp"
#include "ckpool/eu_ckpool.hpp" #include "ckpool/eu_ckpool.hpp"
@ -28,6 +29,7 @@ class PoolFactory {
MINING_POOL_NAME_SATOSHI_RADIO, MINING_POOL_NAME_SATOSHI_RADIO,
MINING_POOL_NAME_BRAIINS, MINING_POOL_NAME_BRAIINS,
MINING_POOL_NAME_PUBLIC_POOL, MINING_POOL_NAME_PUBLIC_POOL,
MINING_POOL_NAME_LOCAL_PUBLIC_POOL,
MINING_POOL_NAME_GOBRRR_POOL, MINING_POOL_NAME_GOBRRR_POOL,
MINING_POOL_NAME_CKPOOL, MINING_POOL_NAME_CKPOOL,
MINING_POOL_NAME_EU_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_BRAIINS;
static const char* MINING_POOL_NAME_SATOSHI_RADIO; static const char* MINING_POOL_NAME_SATOSHI_RADIO;
static const char* MINING_POOL_NAME_PUBLIC_POOL; 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_GOBRRR_POOL;
static const char* MINING_POOL_NAME_CKPOOL; static const char* MINING_POOL_NAME_CKPOOL;
static const char* MINING_POOL_NAME_EU_CKPOOL; static const char* MINING_POOL_NAME_EU_CKPOOL;

View file

@ -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;
}

View file

@ -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;
};

View file

@ -41,7 +41,7 @@ void setupNostrNotify(bool asDatasource, bool zapNotify)
{relay}, {relay},
{// First filter {// First filter
{ {
{"kinds", {"1"}}, {"kinds", {"12203"}},
{"since", {String(getMinutesAgo(60))}}, {"since", {String(getMinutesAgo(60))}},
{"authors", {pubKey}}, {"authors", {pubKey}},
}}, }},
@ -79,8 +79,9 @@ void nostrTask(void *pvParameters)
{ {
DataSourceType dataSource = getDataSource(); DataSourceType dataSource = getDataSource();
if(dataSource == NOSTR_SOURCE) { if(dataSource == NOSTR_SOURCE) {
int blockFetch = getBlockFetch(); auto& blockNotify = BlockNotify::getInstance();
processNewBlock(blockFetch); int blockFetch = blockNotify.fetchLatestBlock();
blockNotify.processNewBlock(blockFetch);
} }
while (1) while (1)
@ -145,6 +146,7 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even
// Use direct value access instead of multiple comparisons // Use direct value access instead of multiple comparisons
String typeValue; String typeValue;
uint medianFee = 0; uint medianFee = 0;
uint blockHeight = 0;
for (JsonArray tag : tags) { for (JsonArray tag : tags) {
if (tag.size() != 2) continue; if (tag.size() != 2) continue;
@ -165,6 +167,11 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even
medianFee = tag[1].as<uint>(); medianFee = tag[1].as<uint>();
} }
break; break;
case 'b': // blockHeight
if (strcmp(key, "block") == 0) {
blockHeight = tag[1].as<uint>();
}
break;
} }
} }
@ -172,13 +179,19 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even
if (!typeValue.isEmpty()) { if (!typeValue.isEmpty()) {
if (typeValue == "priceUsd") { if (typeValue == "priceUsd") {
processNewPrice(obj["content"].as<uint>(), CURRENCY_USD); processNewPrice(obj["content"].as<uint>(), CURRENCY_USD);
if (blockHeight != 0) {
auto& blockNotify = BlockNotify::getInstance();
blockNotify.processNewBlock(blockHeight);
}
} }
else if (typeValue == "blockHeight") { else if (typeValue == "blockHeight") {
processNewBlock(obj["content"].as<uint>()); auto& blockNotify = BlockNotify::getInstance();
blockNotify.processNewBlock(obj["content"].as<uint>());
} }
if (medianFee != 0) { if (medianFee != 0) {
processNewBlockFee(medianFee); auto& blockNotify = BlockNotify::getInstance();
blockNotify.processNewBlockFee(medianFee);
} }
} }
} }

View file

@ -74,8 +74,8 @@ void onOTAStart()
ButtonHandler::suspendTask(); ButtonHandler::suspendTask();
// stopWebServer(); // stopWebServer();
stopBlockNotify(); auto& blockNotify = BlockNotify::getInstance();
stopPriceNotify(); blockNotify.stop();
} }
void handleOTATask(void *parameter) void handleOTATask(void *parameter)

View file

@ -2,103 +2,64 @@
const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin"; const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin";
// WebsocketsClient client; WebSocketsClient webSocket;
esp_websocket_client_handle_t clientPrice = NULL;
esp_websocket_client_config_t config;
uint currentPrice = 90000; uint currentPrice = 90000;
unsigned long int lastPriceUpdate; unsigned long int lastPriceUpdate;
bool priceNotifyInit = false; bool priceNotifyInit = false;
std::map<char, std::uint64_t> currencyMap; std::map<char, std::uint64_t> currencyMap;
std::map<char, unsigned long int> lastUpdateMap; std::map<char, unsigned long int> lastUpdateMap;
WebSocketsClient priceNotifyWs; TaskHandle_t priceNotifyTaskHandle;
void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length);
void setupPriceNotify() void setupPriceNotify()
{ {
config = {.uri = wsServerPrice, webSocket.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin");
.user_agent = USER_AGENT}; webSocket.onEvent([](WStype_t type, uint8_t * payload, size_t length) {
config.cert_pem = isrg_root_x1cert; onWebsocketPriceEvent(type, payload, length);
});
webSocket.setReconnectInterval(5000);
webSocket.enableHeartbeat(15000, 3000, 2);
config.task_stack = (6*1024); setupPriceNotifyTask();
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);
} }
void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) {
// void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) { switch(type) {
// switch(type) { case WStype_DISCONNECTED:
// case WStype_DISCONNECTED: Serial.println(F("Price WS Connection Closed"));
// Serial.printf("[WSc] Disconnected!\n"); break;
// break; case WStype_CONNECTED:
// 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; Serial.println("Connected to " + String(wsServerPrice));
switch (event_id)
{
case WEBSOCKET_EVENT_CONNECTED:
Serial.println("Connected to " + String(config.uri) + " WebSocket");
priceNotifyInit = true; 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; break;
} }
} case WStype_TEXT:
void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data)
{ {
JsonDocument doc; JsonDocument doc;
deserializeJson(doc, (char *)payload);
deserializeJson(doc, (char *)event_data->data_ptr); if (doc["bitcoin"].is<JsonObject>())
if (doc.containsKey("bitcoin"))
{ {
if (currentPrice != doc["bitcoin"].as<long>()) if (currentPrice != doc["bitcoin"].as<long>())
{ {
processNewPrice(doc["bitcoin"].as<long>(), CURRENCY_USD); processNewPrice(doc["bitcoin"].as<long>(), 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) void processNewPrice(uint newPrice, char currency)
@ -175,9 +136,7 @@ void setPrice(uint newPrice, char currency)
bool isPriceNotifyConnected() bool isPriceNotifyConnected()
{ {
if (clientPrice == NULL) return webSocket.isConnected();
return false;
return esp_websocket_client_is_connected(clientPrice);
} }
bool getPriceNotifyInit() bool getPriceNotifyInit()
@ -187,24 +146,30 @@ bool getPriceNotifyInit()
void stopPriceNotify() void stopPriceNotify()
{ {
if (clientPrice == NULL) webSocket.disconnect();
return; if (priceNotifyTaskHandle != NULL) {
esp_websocket_client_close(clientPrice, pdMS_TO_TICKS(5000)); vTaskDelete(priceNotifyTaskHandle);
esp_websocket_client_stop(clientPrice); priceNotifyTaskHandle = NULL;
esp_websocket_client_destroy(clientPrice); }
clientPrice = NULL;
} }
void restartPriceNotify() void restartPriceNotify()
{ {
stopPriceNotify(); stopPriceNotify();
if (clientPrice == NULL)
{
setupPriceNotify(); setupPriceNotify();
return;
} }
// esp_websocket_client_close(clientPrice, pdMS_TO_TICKS(5000));
// esp_websocket_client_stop(clientPrice); void taskPriceNotify(void *pvParameters)
// esp_websocket_client_start(clientPrice); {
for (;;)
{
webSocket.loop();
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void setupPriceNotifyTask()
{
xTaskCreate(taskPriceNotify, "priceNotify", (6 * 1024), NULL, tskIDLE_PRIORITY,
&priceNotifyTaskHandle);
} }

View file

@ -2,24 +2,22 @@
#include <Arduino.h> #include <Arduino.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <esp_websocket_client.h> #include <WebSocketsClient.h>
#include "block_notify.hpp"
#include <string> #include <string>
#include "lib/screen_handler.hpp" #include "lib/screen_handler.hpp"
extern TaskHandle_t priceNotifyTaskHandle;
void setupPriceNotify(); void setupPriceNotify();
void setupPriceNotifyTask();
void taskPriceNotify(void *pvParameters);
void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base, void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length);
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);
uint getPrice(char currency); uint getPrice(char currency);
void setPrice(uint newPrice, char currency); void setPrice(uint newPrice, char currency);
//void processNewPrice(uint newPrice);
void processNewPrice(uint newPrice, char currency); void processNewPrice(uint newPrice, char currency);
bool isPriceNotifyConnected(); bool isPriceNotifyConnected();

View file

@ -251,9 +251,8 @@ void workerTask(void *pvParameters) {
} else if (currentScreenValue == SCREEN_SATS_PER_CURRENCY) { } else if (currentScreenValue == SCREEN_SATS_PER_CURRENCY) {
taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL)); taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL));
} else { } else {
taskEpdContent = auto& blockNotify = BlockNotify::getInstance();
parseMarketCap(getBlockHeight(), price, currency, taskEpdContent = parseMarketCap(blockNotify.getBlockHeight(), price, currency, preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR));
preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR));
} }
EPDManager::getInstance().setContent(taskEpdContent); EPDManager::getInstance().setContent(taskEpdContent);
@ -261,16 +260,19 @@ void workerTask(void *pvParameters) {
} }
case TASK_FEE_UPDATE: { case TASK_FEE_UPDATE: {
if (currentScreenValue == SCREEN_BLOCK_FEE_RATE) { if (currentScreenValue == SCREEN_BLOCK_FEE_RATE) {
taskEpdContent = parseBlockFees(static_cast<std::uint16_t>(getBlockMedianFee())); auto& blockNotify = BlockNotify::getInstance();
taskEpdContent = parseBlockFees(static_cast<std::uint16_t>(blockNotify.getBlockMedianFee()));
EPDManager::getInstance().setContent(taskEpdContent); EPDManager::getInstance().setContent(taskEpdContent);
} }
break; break;
} }
case TASK_BLOCK_UPDATE: { case TASK_BLOCK_UPDATE: {
if (currentScreenValue != SCREEN_HALVING_COUNTDOWN) { if (currentScreenValue != SCREEN_HALVING_COUNTDOWN) {
taskEpdContent = parseBlockHeight(getBlockHeight()); auto& blockNotify = BlockNotify::getInstance();
taskEpdContent = parseBlockHeight(blockNotify.getBlockHeight());
} else { } 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 || if (currentScreenValue == SCREEN_HALVING_COUNTDOWN ||

View file

@ -40,39 +40,39 @@
// "MrY=\n" // "MrY=\n"
// "-----END CERTIFICATE-----\n"; // "-----END CERTIFICATE-----\n";
const char* isrg_root_x1cert = R"EOF( // const char* isrg_root_x1cert = R"EOF(
-----BEGIN CERTIFICATE----- // -----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw // MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh // TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 // cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu // WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY // ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc // MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ // h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U // 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW // A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH // T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC // B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv // B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn // KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn // OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw // jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI // qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV // rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq // HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL // hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ // ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK // 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 // NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur // ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC // TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc // jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq // oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA // 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d // mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= // emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE----- // -----END CERTIFICATE-----
)EOF"; // )EOF";
#ifdef TEST_SCREENS #ifdef TEST_SCREENS

View file

@ -68,7 +68,7 @@ const int usPerSecond = 1000000;
const int usPerMinute = 60 * usPerSecond; const int usPerMinute = 60 * usPerSecond;
// extern const char *github_root_ca; // 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 rootca_crt_bundle_start[] asm("_binary_x509_crt_bundle_start");
// extern const uint8_t ocean_logo_comp[] asm("_binary_ocean_gz_start"); // extern const uint8_t ocean_logo_comp[] asm("_binary_ocean_gz_start");

View file

@ -127,24 +127,33 @@ namespace V2Notify
void handleV2Message(JsonDocument doc) void handleV2Message(JsonDocument doc)
{ {
if (doc.containsKey("blockheight")) if (doc["blockheight"].is<uint>())
{ {
uint newBlockHeight = doc["blockheight"].as<uint>(); uint newBlockHeight = doc["blockheight"].as<uint>();
if (newBlockHeight == getBlockHeight()) if (newBlockHeight == BlockNotify::getInstance().getBlockHeight())
{ {
return; return;
} }
processNewBlock(newBlockHeight); if (debugLogEnabled()) {
Serial.print(F("processNewBlock "));
Serial.println(newBlockHeight);
} }
else if (doc.containsKey("blockfee")) BlockNotify::getInstance().processNewBlock(newBlockHeight);
}
else if (doc["blockfee"].is<uint>())
{ {
uint medianFee = doc["blockfee"].as<uint>(); uint medianFee = doc["blockfee"].as<uint>();
processNewBlockFee(medianFee); if (debugLogEnabled()) {
Serial.print(F("processNewBlockFee "));
Serial.println(medianFee);
} }
else if (doc.containsKey("price"))
BlockNotify::getInstance().processNewBlockFee(medianFee);
}
else if (doc["price"].is<JsonObject>())
{ {
// Iterate through the key-value pairs of the "price" object // Iterate through the key-value pairs of the "price" object
@ -163,7 +172,7 @@ namespace V2Notify
for (;;) for (;;)
{ {
webSocket.loop(); webSocket.loop();
vTaskDelay(10 / portTICK_PERIOD_MS); vTaskDelay(pdMS_TO_TICKS(10));
} }
} }

View file

@ -5,7 +5,7 @@
static const char* JSON_CONTENT = "application/json"; static const char* JSON_CONTENT = "application/json";
static const char *const PROGMEM strSettings[] = { 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"}; static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"};
@ -30,7 +30,8 @@ TaskHandle_t eventSourceTaskHandle;
void setupWebserver() void setupWebserver()
{ {
events.onConnect([](AsyncEventSourceClient *client) events.onConnect([](AsyncEventSourceClient *client)
{ client->send("welcome", NULL, millis(), 1000); }); { client->send("welcome", NULL, millis(), 1000);
});
server.addHandler(&events); server.addHandler(&events);
AsyncStaticWebHandler &staticHandler = server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); AsyncStaticWebHandler &staticHandler = server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html");
@ -247,7 +248,8 @@ JsonDocument getStatusObject()
JsonObject conStatus = root["connectionStatus"].to<JsonObject>(); JsonObject conStatus = root["connectionStatus"].to<JsonObject>();
conStatus["price"] = isPriceNotifyConnected(); conStatus["price"] = isPriceNotifyConnected();
conStatus["blocks"] = isBlockNotifyConnected(); auto& blockNotify = BlockNotify::getInstance();
conStatus["blocks"] = blockNotify.isConnected();
conStatus["V2"] = V2Notify::isV2NotifyConnected(); conStatus["V2"] = V2Notify::isV2NotifyConnected();
conStatus["nostr"] = nostrConnected(); conStatus["nostr"] = nostrConnected();
@ -305,14 +307,18 @@ JsonDocument getLedStatusObject()
void eventSourceUpdate() { void eventSourceUpdate() {
if (!events.count()) return; if (!events.count()) return;
JsonDocument doc = getStatusObject(); static JsonDocument doc;
doc["leds"] = getLedStatusObject()["data"]; doc.clear();
JsonDocument root = getStatusObject();
root["leds"] = getLedStatusObject()["data"];
// Get current EPD content directly as array // Get current EPD content directly as array
std::array<String, NUM_SCREENS> epdContent = EPDManager::getInstance().getCurrentContent(); std::array<String, NUM_SCREENS> epdContent = EPDManager::getInstance().getCurrentContent();
// Add EPD content arrays // Add EPD content arrays
JsonArray data = doc["data"].to<JsonArray>(); JsonArray data = root["data"].to<JsonArray>();
// Copy array elements directly // Copy array elements directly
for(const auto& content : epdContent) { for(const auto& content : epdContent) {
@ -320,7 +326,7 @@ void eventSourceUpdate() {
} }
String buffer; String buffer;
serializeJson(doc, buffer); serializeJson(root, buffer);
events.send(buffer.c_str(), "status"); events.send(buffer.c_str(), "status");
} }
@ -612,15 +618,15 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json)
} }
// Handle DND settings // Handle DND settings
if (settings.containsKey("dnd")) { if (settings["dnd"].is<JsonObject>()) {
JsonObject dndObj = settings["dnd"]; JsonObject dndObj = settings["dnd"];
auto& ledHandler = getLedHandler(); auto& ledHandler = getLedHandler();
if (dndObj.containsKey("timeBasedEnabled")) { if (dndObj["timeBasedEnabled"].is<bool>()) {
ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as<bool>()); ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as<bool>());
} }
if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") && if (dndObj["startHour"].is<uint8_t>() && dndObj["startMinute"].is<uint8_t>() &&
dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) { dndObj["endHour"].is<uint8_t>() && dndObj["endMinute"].is<uint8_t>()) {
ledHandler.setDNDTimeRange( ledHandler.setDNDTimeRange(
dndObj["startHour"].as<uint8_t>(), dndObj["startHour"].as<uint8_t>(),
dndObj["startMinute"].as<uint8_t>(), dndObj["startMinute"].as<uint8_t>(),
@ -695,6 +701,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
root["mempoolSecure"] = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE); 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) // Nostr settings (used for NOSTR_SOURCE or when zapNotify is enabled)
root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB); root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB);
root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY); root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY);
@ -906,7 +915,7 @@ void onApiStopDataSources(AsyncWebServerRequest *request)
request->beginResponseStream(JSON_CONTENT); request->beginResponseStream(JSON_CONTENT);
stopPriceNotify(); stopPriceNotify();
stopBlockNotify(); BlockNotify::getInstance().stop();
request->send(response); request->send(response);
} }
@ -917,9 +926,7 @@ void onApiRestartDataSources(AsyncWebServerRequest *request)
request->beginResponseStream(JSON_CONTENT); request->beginResponseStream(JSON_CONTENT);
restartPriceNotify(); restartPriceNotify();
restartBlockNotify(); BlockNotify::getInstance().restart();
// setupPriceNotify();
// setupBlockNotify();
request->send(response); request->send(response);
} }
@ -1252,24 +1259,3 @@ void onApiLightsPost(AsyncWebServerRequest *request, uint8_t *data, size_t len,
request->send(200); request->send(200);
} }
void onApiSettings(AsyncWebServerRequest *request, JsonVariant &json)
{
JsonObject settings = json.as<JsonObject>();
auto& ledHandler = getLedHandler();
if (settings.containsKey("dnd")) {
JsonObject dndObj = settings["dnd"];
if (dndObj.containsKey("timeBasedEnabled")) {
ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as<bool>());
}
if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") &&
dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) {
ledHandler.setDNDTimeRange(
dndObj["startHour"].as<uint8_t>(),
dndObj["startMinute"].as<uint8_t>(),
dndObj["endHour"].as<uint8_t>(),
dndObj["endMinute"].as<uint8_t>());
}
}
}

View file

@ -19,6 +19,7 @@
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"
#include "lib/config.hpp" #include "lib/config.hpp"
#include "lib/led_handler.hpp" #include "lib/led_handler.hpp"
#include "lib/block_notify.hpp"
uint wifiLostConnection; uint wifiLostConnection;
uint priceNotifyLostConnection = 0; uint priceNotifyLostConnection = 0;
@ -49,7 +50,8 @@ void handleBlockNotifyDisconnection() {
if ((getUptime() - blockNotifyLostConnection) > 300) { // 5 minutes timeout if ((getUptime() - blockNotifyLostConnection) > 300) { // 5 minutes timeout
Serial.println(F("Block notification connection lost for 5 minutes, restarting handler...")); Serial.println(F("Block notification connection lost for 5 minutes, restarting handler..."));
restartBlockNotify(); auto& blockNotify = BlockNotify::getInstance();
blockNotify.restart();
blockNotifyLostConnection = 0; blockNotifyLostConnection = 0;
} }
} }
@ -92,13 +94,14 @@ void checkWiFiConnection() {
void checkMissedBlocks() { void checkMissedBlocks() {
Serial.println(F("Long time (45 min) since last block, checking if I missed anything...")); 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 != -1) {
if (currentBlock != getBlockHeight()) { if (currentBlock != blockNotify.getBlockHeight()) {
Serial.println(F("Detected stuck block height... restarting block handler.")); 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 // Block notification monitoring
if (getBlockNotifyInit() && !isBlockNotifyConnected()) { auto& blockNotify = BlockNotify::getInstance();
if (blockNotify.isInitialized() && !blockNotify.isConnected()) {
handleBlockNotifyDisconnection(); handleBlockNotifyDisconnection();
} else if (blockNotifyLostConnection > 0 && isBlockNotifyConnected()) { } else if (blockNotifyLostConnection > 0 && blockNotify.isConnected()) {
blockNotifyLostConnection = 0; blockNotifyLostConnection = 0;
} }
@ -125,7 +129,7 @@ void monitorDataConnections() {
} }
// Check for missed blocks // Check for missed blocks
if ((getLastBlockUpdate() - getUptime()) > 45 * 60) { if ((blockNotify.getLastBlockUpdate() - getUptime()) > 45 * 60) {
checkMissedBlocks(); checkMissedBlocks();
} }
} }