Compare commits
13 commits
feature/pr
...
main
Author | SHA1 | Date | |
---|---|---|---|
dc8e348aa3 | |||
e4ac3c5c94 | |||
0b1a362b53 | |||
3265eec308 | |||
678a4ba099 | |||
9ea0210864 | |||
b01003f075 | |||
1083a3222b | |||
963f3b10b7 | |||
bf64b2f64f | |||
1d61453563 | |||
e330984ba2 | |||
ebbec75e6b |
33 changed files with 689 additions and 911 deletions
2
data
2
data
|
@ -1 +1 @@
|
||||||
Subproject commit 732dd260ea708841f0e15ee1ee64a3d5115cd475
|
Subproject commit 0116cd68cdfdf383823f74e0f9665a1700cf0500
|
|
@ -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
|
||||||
|
|
|
@ -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.4.5
|
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
|
||||||
|
|
|
@ -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,144 +43,153 @@ 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;
|
||||||
IPAddress result;
|
BlockNotify& instance = BlockNotify::getInstance();
|
||||||
|
|
||||||
int dnsErr = -1;
|
switch (event_id) {
|
||||||
String mempoolInstance =
|
case WEBSOCKET_EVENT_CONNECTED:
|
||||||
preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
|
{
|
||||||
|
notifyInit = true;
|
||||||
|
Serial.print(F("Connected to "));
|
||||||
|
Serial.println(preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE));
|
||||||
|
|
||||||
while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':'))
|
JsonDocument doc;
|
||||||
{
|
doc["action"] = "want";
|
||||||
dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result);
|
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;
|
||||||
|
|
||||||
if (dnsErr != 1)
|
case WEBSOCKET_EVENT_DISCONNECTED:
|
||||||
{
|
Serial.println(F("Mempool.space WS Connection Closed"));
|
||||||
Serial.print(mempoolInstance);
|
break;
|
||||||
Serial.println(F("mempool DNS could not be resolved"));
|
|
||||||
WiFi.reconnect();
|
case WEBSOCKET_EVENT_ERROR:
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
Serial.println(F("Mempool.space WS Connection Error"));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Get current block height through regular API
|
|
||||||
int blockFetch = getBlockFetch();
|
|
||||||
|
|
||||||
if (blockFetch > currentBlockHeight)
|
|
||||||
currentBlockHeight = blockFetch;
|
|
||||||
|
|
||||||
if (currentBlockHeight != -1)
|
|
||||||
{
|
|
||||||
lastBlockUpdate = esp_timer_get_time() / 1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (workQueue != nullptr)
|
|
||||||
{
|
|
||||||
WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0};
|
|
||||||
xQueueSend(workQueue, &blockUpdate, portMAX_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// std::strcpy(wsServer, String("wss://" + mempoolInstance +
|
|
||||||
// "/api/v1/ws").c_str());
|
|
||||||
|
|
||||||
const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "wss" : "ws";
|
|
||||||
|
|
||||||
String mempoolUri = protocol + "://" + preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE) + "/api/v1/ws";
|
|
||||||
|
|
||||||
esp_websocket_client_config_t config = {
|
|
||||||
// .uri = "wss://mempool.space/api/v1/ws",
|
|
||||||
.task_stack = (6*1024),
|
|
||||||
.user_agent = USER_AGENT
|
|
||||||
};
|
|
||||||
|
|
||||||
if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) {
|
|
||||||
config.cert_pem = mempoolWsCert;
|
|
||||||
}
|
|
||||||
|
|
||||||
config.uri = mempoolUri.c_str();
|
|
||||||
|
|
||||||
Serial.printf("Connecting to %s\r\n", preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE));
|
|
||||||
|
|
||||||
blockNotifyClient = esp_websocket_client_init(&config);
|
|
||||||
esp_websocket_register_events(blockNotifyClient, WEBSOCKET_EVENT_ANY,
|
|
||||||
onWebsocketBlockEvent, blockNotifyClient);
|
|
||||||
esp_websocket_client_start(blockNotifyClient);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onWebsocketBlockEvent(void *handler_args, esp_event_base_t base,
|
void BlockNotify::onWebsocketMessage(esp_websocket_event_data_t *data) {
|
||||||
int32_t event_id, void *event_data)
|
JsonDocument doc;
|
||||||
{
|
JsonDocument filter;
|
||||||
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
|
filter["block"]["height"] = true;
|
||||||
const String sub = "{\"action\": \"want\", \"data\":[\"blocks\", \"mempool-blocks\"]}";
|
filter["mempool-blocks"][0]["medianFee"] = true;
|
||||||
switch (event_id)
|
|
||||||
{
|
|
||||||
case WEBSOCKET_EVENT_CONNECTED:
|
|
||||||
blockNotifyInit = true;
|
|
||||||
|
|
||||||
Serial.println(F("Connected to Mempool.space WebSocket"));
|
deserializeJson(doc, (char*)data->data_ptr, DeserializationOption::Filter(filter));
|
||||||
|
|
||||||
Serial.println(sub);
|
if (doc["block"].is<JsonObject>()) {
|
||||||
if (esp_websocket_client_send_text(blockNotifyClient, sub.c_str(),
|
JsonObject block = doc["block"];
|
||||||
sub.length(), portMAX_DELAY) == -1)
|
if (block["height"].as<uint>() != currentBlockHeight) {
|
||||||
{
|
processNewBlock(block["height"].as<uint>());
|
||||||
Serial.println(F("Mempool.space WS Block Subscribe Error"));
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
void BlockNotify::setup() {
|
||||||
{
|
IPAddress result;
|
||||||
JsonDocument doc;
|
int dnsErr = -1;
|
||||||
|
String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
|
||||||
|
|
||||||
JsonDocument filter;
|
while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) {
|
||||||
filter["block"]["height"] = true;
|
dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result);
|
||||||
filter["mempool-blocks"][0]["medianFee"] = true;
|
|
||||||
|
|
||||||
deserializeJson(doc, (char *)event_data->data_ptr, DeserializationOption::Filter(filter));
|
if (dnsErr != 1) {
|
||||||
|
Serial.print(mempoolInstance);
|
||||||
// if (error) {
|
Serial.println(F("mempool DNS could not be resolved"));
|
||||||
// Serial.print("deserializeJson() failed: ");
|
WiFi.reconnect();
|
||||||
// Serial.println(error.c_str());
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
// return;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
if (doc["block"].is<JsonObject>())
|
|
||||||
{
|
|
||||||
JsonObject block = doc["block"];
|
|
||||||
|
|
||||||
if (block["height"].as<uint>() == currentBlockHeight) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processNewBlock(block["height"].as<uint>());
|
// Get current block height through regular API
|
||||||
}
|
int blockFetch = fetchLatestBlock();
|
||||||
else if (doc["mempool-blocks"].is<JsonArray>())
|
|
||||||
{
|
|
||||||
JsonArray blockInfo = doc["mempool-blocks"].as<JsonArray>();
|
|
||||||
|
|
||||||
uint medianFee = (uint)round(blockInfo[0]["medianFee"].as<double>());
|
if (blockFetch > currentBlockHeight)
|
||||||
|
currentBlockHeight = blockFetch;
|
||||||
|
|
||||||
processNewBlockFee(medianFee);
|
if (currentBlockHeight != -1) {
|
||||||
}
|
lastBlockUpdate = esp_timer_get_time() / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
doc.clear();
|
if (workQueue != nullptr) {
|
||||||
|
WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0};
|
||||||
|
xQueueSend(workQueue, &blockUpdate, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool useSSL = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE);
|
||||||
|
const String protocol = useSSL ? "wss" : "ws";
|
||||||
|
String wsUri = protocol + "://" + mempoolInstance + "/api/v1/ws";
|
||||||
|
|
||||||
|
esp_websocket_client_config_t config = {
|
||||||
|
.task_stack = (6*1024),
|
||||||
|
.user_agent = USER_AGENT
|
||||||
|
};
|
||||||
|
|
||||||
|
if (useSSL) {
|
||||||
|
config.cert_pem = mempoolWsCert;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.uri = wsUri.c_str();
|
||||||
|
|
||||||
|
Serial.printf("Connecting to %s\r\n", mempoolInstance.c_str());
|
||||||
|
|
||||||
|
wsClient = esp_websocket_client_init(&config);
|
||||||
|
esp_websocket_register_events(wsClient, WEBSOCKET_EVENT_ANY, onWebsocketEvent, wsClient);
|
||||||
|
esp_websocket_client_start(wsClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
void processNewBlock(uint32_t newBlockHeight) {
|
|
||||||
|
|
||||||
|
void BlockNotify::processNewBlock(uint32_t newBlockHeight) {
|
||||||
if (newBlockHeight <= currentBlockHeight)
|
if (newBlockHeight <= currentBlockHeight)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -220,76 +229,69 @@ 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)
|
||||||
{
|
{
|
||||||
WorkItem blockUpdate = {TASK_FEE_UPDATE, 0};
|
WorkItem blockUpdate = {TASK_FEE_UPDATE, 0};
|
||||||
xQueueSend(workQueue, &blockUpdate, portMAX_DELAY);
|
xQueueSend(workQueue, &blockUpdate, portMAX_DELAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t getBlockHeight() { return currentBlockHeight; }
|
uint32_t BlockNotify::getBlockHeight() const {
|
||||||
|
return currentBlockHeight;
|
||||||
void setBlockHeight(uint32_t newBlockHeight)
|
|
||||||
{
|
|
||||||
currentBlockHeight = newBlockHeight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t getBlockMedianFee() { return blockMedianFee; }
|
void BlockNotify::setBlockHeight(uint32_t newBlockHeight)
|
||||||
|
|
||||||
void setBlockMedianFee(uint16_t newBlockMedianFee)
|
|
||||||
{
|
{
|
||||||
blockMedianFee = newBlockMedianFee;
|
currentBlockHeight = newBlockHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isBlockNotifyConnected()
|
uint16_t BlockNotify::getBlockMedianFee() const {
|
||||||
{
|
return blockMedianFee;
|
||||||
if (blockNotifyClient == NULL)
|
|
||||||
return false;
|
|
||||||
return esp_websocket_client_is_connected(blockNotifyClient);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getBlockNotifyInit()
|
void BlockNotify::setBlockMedianFee(uint16_t newBlockMedianFee)
|
||||||
{
|
{
|
||||||
return blockNotifyInit;
|
blockMedianFee = newBlockMedianFee;
|
||||||
}
|
}
|
||||||
|
|
||||||
void stopBlockNotify()
|
bool BlockNotify::isConnected() const
|
||||||
{
|
{
|
||||||
if (blockNotifyClient == NULL)
|
if (wsClient == NULL)
|
||||||
return;
|
return false;
|
||||||
|
return esp_websocket_client_is_connected(wsClient);
|
||||||
esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000));
|
|
||||||
esp_websocket_client_stop(blockNotifyClient);
|
|
||||||
esp_websocket_client_destroy(blockNotifyClient);
|
|
||||||
|
|
||||||
blockNotifyClient = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void restartBlockNotify()
|
bool BlockNotify::isInitialized() const
|
||||||
{
|
{
|
||||||
stopBlockNotify();
|
return notifyInit;
|
||||||
|
|
||||||
if (blockNotifyClient == NULL) {
|
|
||||||
setupBlockNotify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000));
|
|
||||||
// esp_websocket_client_stop(blockNotifyClient);
|
|
||||||
// esp_websocket_client_start(blockNotifyClient);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BlockNotify::stop()
|
||||||
|
{
|
||||||
|
if (wsClient == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
int getBlockFetch() {
|
esp_websocket_client_close(wsClient, portMAX_DELAY);
|
||||||
|
esp_websocket_client_stop(wsClient);
|
||||||
|
esp_websocket_client_destroy(wsClient);
|
||||||
|
wsClient = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlockNotify::restart()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
int BlockNotify::fetchLatestBlock() {
|
||||||
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;
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
|
||||||
void setBlockHeight(uint32_t newBlockHeight);
|
// Block height management
|
||||||
uint32_t getBlockHeight();
|
void setBlockHeight(uint32_t newBlockHeight);
|
||||||
|
uint32_t getBlockHeight() const;
|
||||||
|
|
||||||
void setBlockMedianFee(uint16_t blockMedianFee);
|
// Block fee management
|
||||||
uint16_t getBlockMedianFee();
|
void setBlockMedianFee(uint16_t blockMedianFee);
|
||||||
|
uint16_t getBlockMedianFee() const;
|
||||||
|
|
||||||
bool isBlockNotifyConnected();
|
// Block processing
|
||||||
void stopBlockNotify();
|
void processNewBlock(uint32_t newBlockHeight);
|
||||||
void restartBlockNotify();
|
void processNewBlockFee(uint16_t newBlockFee);
|
||||||
|
|
||||||
void processNewBlock(uint32_t newBlockHeight);
|
// Block fetch and update tracking
|
||||||
void processNewBlockFee(uint16_t newBlockFee);
|
int fetchLatestBlock();
|
||||||
|
uint getLastBlockUpdate() const;
|
||||||
|
void setLastBlockUpdate(uint lastUpdate);
|
||||||
|
|
||||||
bool getBlockNotifyInit();
|
private:
|
||||||
uint32_t getLastBlockUpdate();
|
BlockNotify() = default; // Private constructor for singleton
|
||||||
int getBlockFetch();
|
|
||||||
void setLastBlockUpdate(uint32_t lastUpdate);
|
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;
|
||||||
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
#include "led_handler.hpp"
|
||||||
|
|
||||||
// Global instance definitions
|
#define MAX_ATTEMPTS_WIFI_CONNECTION 20
|
||||||
PriceNotify::PriceNotifyManager priceManager;
|
|
||||||
|
|
||||||
// zlib_turbo zt;
|
// zlib_turbo zt;
|
||||||
|
|
||||||
|
@ -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,8 +282,8 @@ 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));
|
||||||
priceManager.processNewPrice(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")) {
|
||||||
preferences.putBool("enableDebugLog", DEFAULT_ENABLE_DEBUG_LOG);
|
preferences.putBool("enableDebugLog", DEFAULT_ENABLE_DEBUG_LOG);
|
||||||
|
@ -373,16 +390,8 @@ void setupWebsocketClients(void *pvParameters)
|
||||||
}
|
}
|
||||||
else if (dataSource == THIRD_PARTY_SOURCE)
|
else if (dataSource == THIRD_PARTY_SOURCE)
|
||||||
{
|
{
|
||||||
setupBlockNotify();
|
BlockNotify::getInstance().setup();
|
||||||
setupPriceNotify();
|
setupPriceNotify();
|
||||||
|
|
||||||
// Create task for price manager loop
|
|
||||||
xTaskCreate([](void* param) {
|
|
||||||
for (;;) {
|
|
||||||
priceManager.loop();
|
|
||||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
|
||||||
}
|
|
||||||
}, "priceManagerLoop", (6 * 1024), NULL, tskIDLE_PRIORITY, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
|
@ -761,17 +770,3 @@ DataSourceType getDataSource() {
|
||||||
void setDataSource(DataSourceType source) {
|
void setDataSource(DataSourceType source) {
|
||||||
preferences.putUChar("dataSource", static_cast<uint8_t>(source));
|
preferences.putUChar("dataSource", static_cast<uint8_t>(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupPriceNotify() {
|
|
||||||
priceManager.init(PriceNotify::PriceSource::COINCAP);
|
|
||||||
priceManager.onPriceUpdate([](PriceNotify::Currency currency, uint64_t price) {
|
|
||||||
if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BTC_TICKER ||
|
|
||||||
ScreenHandler::getCurrentScreen() == SCREEN_SATS_PER_CURRENCY ||
|
|
||||||
ScreenHandler::getCurrentScreen() == SCREEN_MARKET_CAP))
|
|
||||||
{
|
|
||||||
WorkItem priceUpdate = {TASK_PRICE_UPDATE, static_cast<char>(currency)};
|
|
||||||
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
priceManager.connect();
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,88 +1,107 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <MCP23017.h>
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <Preferences.h>
|
#include <Preferences.h>
|
||||||
#include <WiFi.h>
|
|
||||||
#include <esp_task_wdt.h>
|
|
||||||
#include <WiFiClientSecure.h>
|
#include <WiFiClientSecure.h>
|
||||||
|
|
||||||
#include <nvs_flash.h>
|
|
||||||
#include <WiFiManager.h>
|
#include <WiFiManager.h>
|
||||||
#include <ESPmDNS.h>
|
#include <base64.h>
|
||||||
|
#include <esp_task_wdt.h>
|
||||||
|
#include <nvs_flash.h>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
#include "block_notify.hpp"
|
#include "lib/block_notify.hpp"
|
||||||
#include "led_handler.hpp"
|
#include "lib/button_handler.hpp"
|
||||||
#include "nostr_notify.hpp"
|
#include "lib/epd.hpp"
|
||||||
#include "price_notify/price_notify.hpp"
|
// #include "lib/improv.hpp"
|
||||||
#include "screen_handler.hpp"
|
#include "lib/led_handler.hpp"
|
||||||
#include "shared.hpp"
|
#include "lib/ota.hpp"
|
||||||
#include "timers.hpp"
|
#include "lib/nostr_notify.hpp"
|
||||||
#include "v2_notify.hpp"
|
#include "lib/bitaxe_fetch.hpp"
|
||||||
#include "webserver.hpp"
|
#include "lib/mining_pool_stats_fetch.hpp"
|
||||||
#include "button_handler.hpp"
|
|
||||||
#include "bitaxe_fetch.hpp"
|
|
||||||
#include "mining_pool_stats_fetch.hpp"
|
|
||||||
#include "epd.hpp"
|
|
||||||
|
|
||||||
|
#include "lib/v2_notify.hpp"
|
||||||
|
|
||||||
|
#include "lib/price_notify.hpp"
|
||||||
|
#include "lib/screen_handler.hpp"
|
||||||
|
#include "lib/shared.hpp"
|
||||||
|
#include "lib/webserver.hpp"
|
||||||
#ifdef HAS_FRONTLIGHT
|
#ifdef HAS_FRONTLIGHT
|
||||||
#include <BH1750.h>
|
#include "PCA9685.h"
|
||||||
#include <PCA9685.h>
|
#include "BH1750.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern Preferences preferences;
|
#include "shared.hpp"
|
||||||
extern QueueHandle_t workQueue;
|
#include "defaults.hpp"
|
||||||
extern PriceNotify::PriceNotifyManager priceManager;
|
|
||||||
|
|
||||||
void setupConfig();
|
#define NTP_SERVER "pool.ntp.org"
|
||||||
void setupWifi();
|
#define DEFAULT_TIME_OFFSET_SECONDS 3600
|
||||||
void setupOTA();
|
#ifndef MCP_DEV_ADDR
|
||||||
void setupMDNS();
|
#define MCP_DEV_ADDR 0x20
|
||||||
void setupDataSources();
|
#endif
|
||||||
void stopDataSources();
|
|
||||||
void restartDataSources();
|
|
||||||
void setupTasks();
|
void setup();
|
||||||
void setupTimers();
|
|
||||||
void setupLittleFS();
|
|
||||||
void setupPreferences();
|
|
||||||
void setupWDT();
|
|
||||||
void setupWorkQueue();
|
|
||||||
void setupLedHandler();
|
|
||||||
void setupScreenHandler();
|
|
||||||
void setupWebserver();
|
|
||||||
void setupNostrNotify();
|
|
||||||
void setupBlockNotify();
|
|
||||||
void setupV2Notify();
|
|
||||||
void setupPriceNotify();
|
|
||||||
void setupHardware();
|
|
||||||
void finishSetup();
|
|
||||||
void syncTime();
|
void syncTime();
|
||||||
|
|
||||||
void handleOTA();
|
|
||||||
void handleWifi();
|
|
||||||
void handleWDT();
|
|
||||||
void handleWorkQueue();
|
|
||||||
|
|
||||||
void setWifiTxPower(int8_t power);
|
|
||||||
void onWifiEvent(WiFiEvent_t event);
|
|
||||||
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
|
|
||||||
|
|
||||||
DataSourceType getDataSource();
|
|
||||||
void setDataSource(DataSourceType source);
|
|
||||||
String getMyHostname();
|
|
||||||
uint getLastTimeSync();
|
uint getLastTimeSync();
|
||||||
bool debugLogEnabled();
|
void setupPreferences();
|
||||||
|
void setupWebsocketClients(void *pvParameters);
|
||||||
|
void setupHardware();
|
||||||
|
void setupWifi();
|
||||||
|
void setupTimers();
|
||||||
|
void finishSetup();
|
||||||
|
void setupMcp();
|
||||||
#ifdef HAS_FRONTLIGHT
|
#ifdef HAS_FRONTLIGHT
|
||||||
|
extern BH1750 bh1750;
|
||||||
|
extern bool hasLuxSensor;
|
||||||
float getLightLevel();
|
float getLightLevel();
|
||||||
bool hasLightLevel();
|
bool hasLightLevel();
|
||||||
|
|
||||||
extern PCA9685 flArray;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
String getMyHostname();
|
||||||
|
std::vector<ScreenMapping> getScreenNameMap();
|
||||||
|
|
||||||
|
std::vector<std::string> getLocalUrl();
|
||||||
|
// bool improv_connectWifi(std::string ssid, std::string password);
|
||||||
|
// void improvGetAvailableWifiNetworks();
|
||||||
|
// bool onImprovCommandCallback(improv::ImprovCommand cmd);
|
||||||
|
// void onImprovErrorCallback(improv::Error err);
|
||||||
|
// void improv_set_state(improv::State state);
|
||||||
|
// void improv_send_response(std::vector<uint8_t> &response);
|
||||||
|
// void improv_set_error(improv::Error error);
|
||||||
|
//void addCurrencyMappings(const std::vector<std::string>& currencies);
|
||||||
|
std::vector<std::string> getActiveCurrencies();
|
||||||
|
std::vector<std::string> getAvailableCurrencies();
|
||||||
|
|
||||||
|
bool isActiveCurrency(std::string ¤cy);
|
||||||
|
|
||||||
|
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||||
String getHwRev();
|
String getHwRev();
|
||||||
bool isWhiteVersion();
|
bool isWhiteVersion();
|
||||||
String getFsRev();
|
String getFsRev();
|
||||||
|
|
||||||
#define NTP_SERVER "pool.ntp.org"
|
bool debugLogEnabled();
|
||||||
#define DEFAULT_TIME_OFFSET_SECONDS 3600
|
|
||||||
|
void addScreenMapping(int value, const char* name);
|
||||||
|
// void addScreenMapping(int value, const String& name);
|
||||||
|
// void addScreenMapping(int value, const std::string& name);
|
||||||
|
|
||||||
|
int findScreenIndexByValue(int value);
|
||||||
|
String replaceAmbiguousChars(String input);
|
||||||
|
const char* getFirmwareFilename();
|
||||||
|
const char* getWebUiFilename();
|
||||||
|
// void loadIcons();
|
||||||
|
|
||||||
|
extern Preferences preferences;
|
||||||
|
extern MCP23017 mcp1;
|
||||||
|
#ifdef IS_BTCLOCK_V8
|
||||||
|
extern MCP23017 mcp2;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_FRONTLIGHT
|
||||||
|
extern PCA9685 flArray;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Expose DataSourceType enum
|
||||||
|
extern DataSourceType getDataSource();
|
||||||
|
extern void setDataSource(DataSourceType source);
|
|
@ -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"
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "price_notify/price_notify.hpp"
|
|
||||||
|
|
||||||
// Global instances
|
|
||||||
extern PriceNotify::PriceNotifyManager priceManager;
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>(); }}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
11
src/lib/mining_pool/public_pool/local_public_pool.cpp
Normal file
11
src/lib/mining_pool/public_pool/local_public_pool.cpp
Normal 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;
|
||||||
|
}
|
11
src/lib/mining_pool/public_pool/local_public_pool.hpp
Normal file
11
src/lib/mining_pool/public_pool/local_public_pool.hpp
Normal 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;
|
||||||
|
};
|
|
@ -1,6 +1,5 @@
|
||||||
#include "nostr_notify.hpp"
|
#include "nostr_notify.hpp"
|
||||||
#include "led_handler.hpp"
|
#include "led_handler.hpp"
|
||||||
#include "globals.hpp"
|
|
||||||
|
|
||||||
std::vector<nostr::NostrPool *> pools;
|
std::vector<nostr::NostrPool *> pools;
|
||||||
nostr::Transport *transport;
|
nostr::Transport *transport;
|
||||||
|
@ -42,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}},
|
||||||
}},
|
}},
|
||||||
|
@ -80,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)
|
||||||
|
@ -146,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;
|
||||||
|
@ -166,20 +167,31 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the data
|
// Process the data
|
||||||
if (!typeValue.isEmpty()) {
|
if (!typeValue.isEmpty()) {
|
||||||
if (typeValue == "priceUsd") {
|
if (typeValue == "priceUsd") {
|
||||||
priceManager.processNewPrice(obj["content"].as<uint>(), static_cast<PriceNotify::Currency>(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
#include "NostrEvent.h"
|
#include "NostrEvent.h"
|
||||||
#include "NostrPool.h"
|
#include "NostrPool.h"
|
||||||
|
|
||||||
|
#include "price_notify.hpp"
|
||||||
#include "block_notify.hpp"
|
#include "block_notify.hpp"
|
||||||
#include "price_notify/price_notify.hpp"
|
|
||||||
#include "lib/timers.hpp"
|
#include "lib/timers.hpp"
|
||||||
|
|
||||||
void setupNostrNotify(bool asDatasource, bool zapNotify);
|
void setupNostrNotify(bool asDatasource, bool zapNotify);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
175
src/lib/price_notify.cpp
Normal file
175
src/lib/price_notify.cpp
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
#include "price_notify.hpp"
|
||||||
|
|
||||||
|
const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin";
|
||||||
|
|
||||||
|
WebSocketsClient webSocket;
|
||||||
|
uint currentPrice = 90000;
|
||||||
|
unsigned long int lastPriceUpdate;
|
||||||
|
bool priceNotifyInit = false;
|
||||||
|
std::map<char, std::uint64_t> currencyMap;
|
||||||
|
std::map<char, unsigned long int> lastUpdateMap;
|
||||||
|
TaskHandle_t priceNotifyTaskHandle;
|
||||||
|
|
||||||
|
void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length);
|
||||||
|
|
||||||
|
void setupPriceNotify()
|
||||||
|
{
|
||||||
|
webSocket.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin");
|
||||||
|
webSocket.onEvent([](WStype_t type, uint8_t * payload, size_t length) {
|
||||||
|
onWebsocketPriceEvent(type, payload, length);
|
||||||
|
});
|
||||||
|
webSocket.setReconnectInterval(5000);
|
||||||
|
webSocket.enableHeartbeat(15000, 3000, 2);
|
||||||
|
|
||||||
|
setupPriceNotifyTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) {
|
||||||
|
switch(type) {
|
||||||
|
case WStype_DISCONNECTED:
|
||||||
|
Serial.println(F("Price WS Connection Closed"));
|
||||||
|
break;
|
||||||
|
case WStype_CONNECTED:
|
||||||
|
{
|
||||||
|
Serial.println("Connected to " + String(wsServerPrice));
|
||||||
|
priceNotifyInit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WStype_TEXT:
|
||||||
|
{
|
||||||
|
JsonDocument doc;
|
||||||
|
deserializeJson(doc, (char *)payload);
|
||||||
|
|
||||||
|
if (doc["bitcoin"].is<JsonObject>())
|
||||||
|
{
|
||||||
|
if (currentPrice != doc["bitcoin"].as<long>())
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
uint minSecPriceUpd = preferences.getUInt(
|
||||||
|
"minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE);
|
||||||
|
uint currentTime = esp_timer_get_time() / 1000000;
|
||||||
|
|
||||||
|
if (lastUpdateMap.find(currency) == lastUpdateMap.end() ||
|
||||||
|
(currentTime - lastUpdateMap[currency]) > minSecPriceUpd)
|
||||||
|
{
|
||||||
|
currencyMap[currency] = newPrice;
|
||||||
|
|
||||||
|
// Store price in preferences if enough time has passed
|
||||||
|
if (lastUpdateMap[currency] == 0 || (currentTime - lastUpdateMap[currency]) > 120)
|
||||||
|
{
|
||||||
|
String prefKey = String("lastPrice_") + getCurrencyCode(currency).c_str();
|
||||||
|
preferences.putUInt(prefKey.c_str(), newPrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastUpdateMap[currency] = currentTime;
|
||||||
|
|
||||||
|
if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BTC_TICKER ||
|
||||||
|
ScreenHandler::getCurrentScreen() == SCREEN_SATS_PER_CURRENCY ||
|
||||||
|
ScreenHandler::getCurrentScreen() == SCREEN_MARKET_CAP))
|
||||||
|
{
|
||||||
|
WorkItem priceUpdate = {TASK_PRICE_UPDATE, currency};
|
||||||
|
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadStoredPrices()
|
||||||
|
{
|
||||||
|
// Load prices for all supported currencies
|
||||||
|
std::vector<std::string> currencies = getAvailableCurrencies();
|
||||||
|
|
||||||
|
for (const std::string ¤cy : currencies) {
|
||||||
|
// Get first character as the currency identifier
|
||||||
|
String prefKey = String("lastPrice_") + currency.c_str();
|
||||||
|
uint storedPrice = preferences.getUInt(prefKey.c_str(), 0);
|
||||||
|
|
||||||
|
if (storedPrice > 0) {
|
||||||
|
currencyMap[getCurrencyChar(currency)] = storedPrice;
|
||||||
|
// Initialize lastUpdateMap to 0 so next update will store immediately
|
||||||
|
lastUpdateMap[getCurrencyChar(currency)] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint getLastPriceUpdate(char currency)
|
||||||
|
{
|
||||||
|
if (lastUpdateMap.find(currency) == lastUpdateMap.end())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastUpdateMap[currency];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint getPrice(char currency)
|
||||||
|
{
|
||||||
|
if (currencyMap.find(currency) == currencyMap.end())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return currencyMap[currency];
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPrice(uint newPrice, char currency)
|
||||||
|
{
|
||||||
|
currencyMap[currency] = newPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isPriceNotifyConnected()
|
||||||
|
{
|
||||||
|
return webSocket.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getPriceNotifyInit()
|
||||||
|
{
|
||||||
|
return priceNotifyInit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopPriceNotify()
|
||||||
|
{
|
||||||
|
webSocket.disconnect();
|
||||||
|
if (priceNotifyTaskHandle != NULL) {
|
||||||
|
vTaskDelete(priceNotifyTaskHandle);
|
||||||
|
priceNotifyTaskHandle = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void restartPriceNotify()
|
||||||
|
{
|
||||||
|
stopPriceNotify();
|
||||||
|
setupPriceNotify();
|
||||||
|
}
|
||||||
|
|
||||||
|
void taskPriceNotify(void *pvParameters)
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
webSocket.loop();
|
||||||
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupPriceNotifyTask()
|
||||||
|
{
|
||||||
|
xTaskCreate(taskPriceNotify, "priceNotify", (6 * 1024), NULL, tskIDLE_PRIORITY,
|
||||||
|
&priceNotifyTaskHandle);
|
||||||
|
}
|
29
src/lib/price_notify.hpp
Normal file
29
src/lib/price_notify.hpp
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <WebSocketsClient.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "lib/screen_handler.hpp"
|
||||||
|
|
||||||
|
extern TaskHandle_t priceNotifyTaskHandle;
|
||||||
|
|
||||||
|
void setupPriceNotify();
|
||||||
|
void setupPriceNotifyTask();
|
||||||
|
void taskPriceNotify(void *pvParameters);
|
||||||
|
|
||||||
|
void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length);
|
||||||
|
|
||||||
|
uint getPrice(char currency);
|
||||||
|
void setPrice(uint newPrice, char currency);
|
||||||
|
|
||||||
|
void processNewPrice(uint newPrice, char currency);
|
||||||
|
|
||||||
|
bool isPriceNotifyConnected();
|
||||||
|
void stopPriceNotify();
|
||||||
|
void restartPriceNotify();
|
||||||
|
|
||||||
|
bool getPriceNotifyInit();
|
||||||
|
uint getLastPriceUpdate(char currency);
|
||||||
|
void loadStoredPrices();
|
|
@ -1,53 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <WebSocketsClient.h>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace PriceNotify {
|
|
||||||
|
|
||||||
using std::function;
|
|
||||||
using std::map;
|
|
||||||
using std::string;
|
|
||||||
|
|
||||||
// Convert char-based currency to enum for type safety
|
|
||||||
enum class Currency : char {
|
|
||||||
USD = '$',
|
|
||||||
EUR = '[',
|
|
||||||
GBP = ']',
|
|
||||||
JPY = '^',
|
|
||||||
AUD = '_',
|
|
||||||
CAD = '`'
|
|
||||||
};
|
|
||||||
|
|
||||||
class IPriceSource {
|
|
||||||
public:
|
|
||||||
virtual ~IPriceSource() = default;
|
|
||||||
|
|
||||||
// Initialize and connect to the websocket
|
|
||||||
virtual void connect() = 0;
|
|
||||||
|
|
||||||
// Disconnect and cleanup
|
|
||||||
virtual void disconnect() = 0;
|
|
||||||
|
|
||||||
// Check connection status
|
|
||||||
virtual bool isConnected() const = 0;
|
|
||||||
|
|
||||||
// Get the last known price for a currency
|
|
||||||
virtual uint64_t getPrice(Currency currency) const = 0;
|
|
||||||
|
|
||||||
// Get the last update timestamp for a currency
|
|
||||||
virtual uint32_t getLastUpdate(Currency currency) const = 0;
|
|
||||||
|
|
||||||
// Set callback for price updates
|
|
||||||
virtual void onPriceUpdate(function<void(Currency, uint64_t)> callback) = 0;
|
|
||||||
|
|
||||||
// Process websocket loop - should be called regularly
|
|
||||||
virtual void loop() = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
function<void(Currency, uint64_t)> priceUpdateCallback;
|
|
||||||
};
|
|
||||||
} // namespace PriceNotify
|
|
|
@ -1,115 +0,0 @@
|
||||||
#include "price_notify.hpp"
|
|
||||||
#include <esp_timer.h>
|
|
||||||
|
|
||||||
using std::make_unique;
|
|
||||||
|
|
||||||
namespace PriceNotify {
|
|
||||||
|
|
||||||
PriceNotifyManager::PriceNotifyManager() : currentSource(PriceSource::NONE) {}
|
|
||||||
|
|
||||||
PriceNotifyManager::~PriceNotifyManager() {
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PriceNotifyManager::init(PriceSource source) {
|
|
||||||
currentSource = source;
|
|
||||||
setPriceSource(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PriceNotifyManager::connect() {
|
|
||||||
if (priceSource) {
|
|
||||||
priceSource->connect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PriceNotifyManager::disconnect() {
|
|
||||||
if (priceSource) {
|
|
||||||
priceSource->disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PriceNotifyManager::isConnected() const {
|
|
||||||
return priceSource ? priceSource->isConnected() : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t PriceNotifyManager::getPrice(Currency currency) const {
|
|
||||||
if (priceSource) {
|
|
||||||
return priceSource->getPrice(currency);
|
|
||||||
}
|
|
||||||
// If no price source, return from internal storage
|
|
||||||
auto it = prices.find(currency);
|
|
||||||
return (it != prices.end()) ? it->second : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t PriceNotifyManager::getLastUpdate(Currency currency) const {
|
|
||||||
if (priceSource) {
|
|
||||||
return priceSource->getLastUpdate(currency);
|
|
||||||
}
|
|
||||||
// If no price source, return from internal storage
|
|
||||||
auto it = lastUpdates.find(currency);
|
|
||||||
return (it != lastUpdates.end()) ? it->second : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PriceNotifyManager::onPriceUpdate(function<void(Currency, uint64_t)> callback) {
|
|
||||||
userCallback = callback;
|
|
||||||
if (priceSource) {
|
|
||||||
priceSource->onPriceUpdate([this](Currency currency, uint64_t price) {
|
|
||||||
if (userCallback) {
|
|
||||||
userCallback(currency, price);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PriceNotifyManager::loop() {
|
|
||||||
if (priceSource) {
|
|
||||||
priceSource->loop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PriceNotifyManager::setPriceSource(PriceSource source) {
|
|
||||||
// Store the callback before destroying the old source
|
|
||||||
auto oldCallback = userCallback;
|
|
||||||
|
|
||||||
// Disconnect and destroy old source
|
|
||||||
if (priceSource) {
|
|
||||||
priceSource->disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new source
|
|
||||||
switch (source) {
|
|
||||||
case PriceSource::COINCAP:
|
|
||||||
priceSource = make_unique<CoinCapSource>();
|
|
||||||
break;
|
|
||||||
case PriceSource::KRAKEN:
|
|
||||||
priceSource = make_unique<KrakenSource>();
|
|
||||||
break;
|
|
||||||
case PriceSource::NONE:
|
|
||||||
priceSource.reset();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentSource = source;
|
|
||||||
|
|
||||||
// Restore callback if it exists
|
|
||||||
if (oldCallback) {
|
|
||||||
onPriceUpdate(oldCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PriceSource PriceNotifyManager::getCurrentSource() const {
|
|
||||||
return currentSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PriceNotifyManager::processNewPrice(uint64_t price, Currency currency) {
|
|
||||||
// Store the price and timestamp
|
|
||||||
prices[currency] = price;
|
|
||||||
lastUpdates[currency] = esp_timer_get_time() / 1000000; // Current time in seconds
|
|
||||||
|
|
||||||
// If we have a callback, notify about the price update
|
|
||||||
if (userCallback) {
|
|
||||||
userCallback(currency, price);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace PriceNotify
|
|
|
@ -1,68 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "interfaces/price_source.hpp"
|
|
||||||
#include "sources/coincap_source.hpp"
|
|
||||||
#include "sources/kraken_source.hpp"
|
|
||||||
#include <memory>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
namespace PriceNotify {
|
|
||||||
|
|
||||||
using std::unique_ptr;
|
|
||||||
using std::function;
|
|
||||||
using std::map;
|
|
||||||
|
|
||||||
enum class PriceSource {
|
|
||||||
NONE, // Added for when no explicit source is set
|
|
||||||
COINCAP,
|
|
||||||
KRAKEN
|
|
||||||
};
|
|
||||||
|
|
||||||
class PriceNotifyManager {
|
|
||||||
public:
|
|
||||||
PriceNotifyManager();
|
|
||||||
~PriceNotifyManager();
|
|
||||||
|
|
||||||
// Initialize with a specific price source
|
|
||||||
void init(PriceSource source);
|
|
||||||
|
|
||||||
// Connect to the price source
|
|
||||||
void connect();
|
|
||||||
|
|
||||||
// Disconnect from the price source
|
|
||||||
void disconnect();
|
|
||||||
|
|
||||||
// Check if connected to price source
|
|
||||||
bool isConnected() const;
|
|
||||||
|
|
||||||
// Get the last known price for a currency
|
|
||||||
uint64_t getPrice(Currency currency) const;
|
|
||||||
|
|
||||||
// Get the last update timestamp for a currency
|
|
||||||
uint32_t getLastUpdate(Currency currency) const;
|
|
||||||
|
|
||||||
// Set callback for price updates
|
|
||||||
void onPriceUpdate(function<void(Currency, uint64_t)> callback);
|
|
||||||
|
|
||||||
// Process websocket loop - should be called regularly
|
|
||||||
void loop();
|
|
||||||
|
|
||||||
// Change the price source
|
|
||||||
void setPriceSource(PriceSource source);
|
|
||||||
|
|
||||||
// Get current price source
|
|
||||||
PriceSource getCurrentSource() const;
|
|
||||||
|
|
||||||
// Process new price from external source
|
|
||||||
void processNewPrice(uint64_t price, Currency currency);
|
|
||||||
|
|
||||||
private:
|
|
||||||
unique_ptr<IPriceSource> priceSource;
|
|
||||||
PriceSource currentSource;
|
|
||||||
function<void(Currency, uint64_t)> userCallback;
|
|
||||||
|
|
||||||
// For storing prices when no explicit source is set
|
|
||||||
map<Currency, uint64_t> prices;
|
|
||||||
map<Currency, uint32_t> lastUpdates;
|
|
||||||
};
|
|
||||||
} // namespace PriceNotify
|
|
|
@ -1,96 +0,0 @@
|
||||||
#include "coincap_source.hpp"
|
|
||||||
|
|
||||||
namespace PriceNotify {
|
|
||||||
|
|
||||||
CoinCapSource* CoinCapSource::instance = nullptr;
|
|
||||||
|
|
||||||
CoinCapSource::CoinCapSource() : connected(false) {
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoinCapSource::~CoinCapSource() {
|
|
||||||
disconnect();
|
|
||||||
instance = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CoinCapSource::connect() {
|
|
||||||
webSocket.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin");
|
|
||||||
webSocket.onEvent([](WStype_t type, uint8_t* payload, size_t length) {
|
|
||||||
if (instance) {
|
|
||||||
instance->handleWebSocketEvent(type, payload, length);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
webSocket.setReconnectInterval(5000);
|
|
||||||
webSocket.enableHeartbeat(15000, 3000, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CoinCapSource::disconnect() {
|
|
||||||
webSocket.disconnect();
|
|
||||||
connected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CoinCapSource::isConnected() const {
|
|
||||||
return connected && webSocket.isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t CoinCapSource::getPrice(Currency currency) const {
|
|
||||||
auto it = prices.find(currency);
|
|
||||||
return (it != prices.end()) ? it->second : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t CoinCapSource::getLastUpdate(Currency currency) const {
|
|
||||||
auto it = lastUpdates.find(currency);
|
|
||||||
return (it != lastUpdates.end()) ? it->second : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CoinCapSource::onPriceUpdate(function<void(Currency, uint64_t)> callback) {
|
|
||||||
priceUpdateCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CoinCapSource::loop() {
|
|
||||||
webSocket.loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CoinCapSource::handleWebSocketEvent(WStype_t type, uint8_t* payload, size_t length) {
|
|
||||||
switch(type) {
|
|
||||||
case WStype_DISCONNECTED:
|
|
||||||
Serial.println(F("CoinCap WS Disconnected"));
|
|
||||||
connected = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WStype_CONNECTED:
|
|
||||||
Serial.println(F("CoinCap WS Connected"));
|
|
||||||
connected = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WStype_TEXT:
|
|
||||||
processMessage((char*)payload);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CoinCapSource::processMessage(const char* payload) {
|
|
||||||
StaticJsonDocument<512> doc;
|
|
||||||
DeserializationError error = deserializeJson(doc, payload);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
Serial.println(F("Failed to parse CoinCap message"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doc["bitcoin"].is<JsonObject>()) {
|
|
||||||
uint64_t price = doc["bitcoin"].as<uint64_t>();
|
|
||||||
uint32_t timestamp = esp_timer_get_time() / 1000000; // Current time in seconds
|
|
||||||
|
|
||||||
prices[Currency::USD] = price;
|
|
||||||
lastUpdates[Currency::USD] = timestamp;
|
|
||||||
|
|
||||||
if (priceUpdateCallback) {
|
|
||||||
priceUpdateCallback(Currency::USD, price);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace PriceNotify
|
|
|
@ -1,36 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../interfaces/price_source.hpp"
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <esp_timer.h>
|
|
||||||
|
|
||||||
namespace PriceNotify {
|
|
||||||
|
|
||||||
using std::map;
|
|
||||||
|
|
||||||
class CoinCapSource : public IPriceSource {
|
|
||||||
public:
|
|
||||||
CoinCapSource();
|
|
||||||
~CoinCapSource() override;
|
|
||||||
|
|
||||||
void connect() override;
|
|
||||||
void disconnect() override;
|
|
||||||
bool isConnected() const override;
|
|
||||||
uint64_t getPrice(Currency currency) const override;
|
|
||||||
uint32_t getLastUpdate(Currency currency) const override;
|
|
||||||
void onPriceUpdate(function<void(Currency, uint64_t)> callback) override;
|
|
||||||
void loop() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void onWebSocketEvent(WStype_t type, uint8_t* payload, size_t length);
|
|
||||||
void handleWebSocketEvent(WStype_t type, uint8_t* payload, size_t length);
|
|
||||||
void processMessage(const char* payload);
|
|
||||||
|
|
||||||
WebSocketsClient webSocket;
|
|
||||||
map<Currency, uint64_t> prices;
|
|
||||||
map<Currency, uint32_t> lastUpdates;
|
|
||||||
bool connected;
|
|
||||||
|
|
||||||
static CoinCapSource* instance; // For callback handling
|
|
||||||
};
|
|
||||||
} // namespace PriceNotify
|
|
|
@ -1,118 +0,0 @@
|
||||||
#include "kraken_source.hpp"
|
|
||||||
|
|
||||||
namespace PriceNotify {
|
|
||||||
|
|
||||||
KrakenSource* KrakenSource::instance = nullptr;
|
|
||||||
|
|
||||||
KrakenSource::KrakenSource() : connected(false) {
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
KrakenSource::~KrakenSource() {
|
|
||||||
disconnect();
|
|
||||||
instance = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void KrakenSource::connect() {
|
|
||||||
webSocket.beginSSL("ws.kraken.com", 443, "/ws");
|
|
||||||
webSocket.onEvent([](WStype_t type, uint8_t* payload, size_t length) {
|
|
||||||
if (instance) {
|
|
||||||
instance->handleWebSocketEvent(type, payload, length);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
webSocket.setReconnectInterval(5000);
|
|
||||||
webSocket.enableHeartbeat(15000, 3000, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void KrakenSource::disconnect() {
|
|
||||||
webSocket.disconnect();
|
|
||||||
connected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool KrakenSource::isConnected() const {
|
|
||||||
return connected && webSocket.isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t KrakenSource::getPrice(Currency currency) const {
|
|
||||||
auto it = prices.find(currency);
|
|
||||||
return (it != prices.end()) ? it->second : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t KrakenSource::getLastUpdate(Currency currency) const {
|
|
||||||
auto it = lastUpdates.find(currency);
|
|
||||||
return (it != lastUpdates.end()) ? it->second : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void KrakenSource::onPriceUpdate(function<void(Currency, uint64_t)> callback) {
|
|
||||||
priceUpdateCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void KrakenSource::loop() {
|
|
||||||
webSocket.loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void KrakenSource::handleWebSocketEvent(WStype_t type, uint8_t* payload, size_t length) {
|
|
||||||
switch(type) {
|
|
||||||
case WStype_DISCONNECTED:
|
|
||||||
Serial.println(F("Kraken WS Disconnected"));
|
|
||||||
connected = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WStype_CONNECTED:
|
|
||||||
Serial.println(F("Kraken WS Connected"));
|
|
||||||
connected = true;
|
|
||||||
subscribe();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WStype_TEXT:
|
|
||||||
processMessage((char*)payload);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void KrakenSource::subscribe() {
|
|
||||||
// Subscribe to XBT/USD ticker
|
|
||||||
StaticJsonDocument<256> doc;
|
|
||||||
doc["event"] = "subscribe";
|
|
||||||
JsonArray pair = doc.createNestedArray("pair");
|
|
||||||
pair.add("XBT/USD");
|
|
||||||
doc["subscription"]["name"] = "ticker";
|
|
||||||
|
|
||||||
String message;
|
|
||||||
serializeJson(doc, message);
|
|
||||||
webSocket.sendTXT(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
void KrakenSource::processMessage(const char* payload) {
|
|
||||||
StaticJsonDocument<512> doc;
|
|
||||||
DeserializationError error = deserializeJson(doc, payload);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
Serial.println(F("Failed to parse Kraken message"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it's a ticker update (array format)
|
|
||||||
if (doc.is<JsonArray>() && doc.size() >= 4) {
|
|
||||||
JsonArray arr = doc.as<JsonArray>();
|
|
||||||
if (arr[2] == "ticker" && arr[3] == "XBT/USD") {
|
|
||||||
JsonObject tickerData = arr[1].as<JsonObject>();
|
|
||||||
if (tickerData.containsKey("c")) {
|
|
||||||
// Get the first value from the "c" array which is the last trade price
|
|
||||||
uint64_t price = tickerData["c"][0].as<float>() * 100; // Convert to cents
|
|
||||||
uint32_t timestamp = esp_timer_get_time() / 1000000; // Current time in seconds
|
|
||||||
|
|
||||||
prices[Currency::USD] = price;
|
|
||||||
lastUpdates[Currency::USD] = timestamp;
|
|
||||||
|
|
||||||
if (priceUpdateCallback) {
|
|
||||||
priceUpdateCallback(Currency::USD, price);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace PriceNotify
|
|
|
@ -1,37 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../interfaces/price_source.hpp"
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <esp_timer.h>
|
|
||||||
|
|
||||||
namespace PriceNotify {
|
|
||||||
|
|
||||||
using std::map;
|
|
||||||
|
|
||||||
class KrakenSource : public IPriceSource {
|
|
||||||
public:
|
|
||||||
KrakenSource();
|
|
||||||
~KrakenSource() override;
|
|
||||||
|
|
||||||
void connect() override;
|
|
||||||
void disconnect() override;
|
|
||||||
bool isConnected() const override;
|
|
||||||
uint64_t getPrice(Currency currency) const override;
|
|
||||||
uint32_t getLastUpdate(Currency currency) const override;
|
|
||||||
void onPriceUpdate(function<void(Currency, uint64_t)> callback) override;
|
|
||||||
void loop() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void onWebSocketEvent(WStype_t type, uint8_t* payload, size_t length);
|
|
||||||
void handleWebSocketEvent(WStype_t type, uint8_t* payload, size_t length);
|
|
||||||
void processMessage(const char* payload);
|
|
||||||
void subscribe();
|
|
||||||
|
|
||||||
WebSocketsClient webSocket;
|
|
||||||
map<Currency, uint64_t> prices;
|
|
||||||
map<Currency, uint32_t> lastUpdates;
|
|
||||||
bool connected;
|
|
||||||
|
|
||||||
static KrakenSource* instance; // For callback handling
|
|
||||||
};
|
|
||||||
} // namespace PriceNotify
|
|
|
@ -241,7 +241,7 @@ void workerTask(void *pvParameters) {
|
||||||
|
|
||||||
case TASK_PRICE_UPDATE: {
|
case TASK_PRICE_UPDATE: {
|
||||||
uint currency = ScreenHandler::getCurrentCurrency();
|
uint currency = ScreenHandler::getCurrentCurrency();
|
||||||
uint price = priceManager.getPrice(static_cast<PriceNotify::Currency>(currency));
|
uint price = getPrice(currency);
|
||||||
|
|
||||||
if (currentScreenValue == SCREEN_BTC_TICKER) {
|
if (currentScreenValue == SCREEN_BTC_TICKER) {
|
||||||
taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE),
|
taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE),
|
||||||
|
@ -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 ||
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include "v2_notify.hpp"
|
#include "v2_notify.hpp"
|
||||||
#include "globals.hpp"
|
|
||||||
|
|
||||||
using namespace V2Notify;
|
using namespace V2Notify;
|
||||||
|
|
||||||
|
@ -128,34 +127,42 @@ namespace V2Notify
|
||||||
|
|
||||||
void handleV2Message(JsonDocument doc)
|
void handleV2Message(JsonDocument doc)
|
||||||
{
|
{
|
||||||
if (doc["blockheight"].is<JsonObject>())
|
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);
|
||||||
|
}
|
||||||
|
BlockNotify::getInstance().processNewBlock(newBlockHeight);
|
||||||
}
|
}
|
||||||
else if (doc["blockfee"].is<JsonObject>())
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockNotify::getInstance().processNewBlockFee(medianFee);
|
||||||
}
|
}
|
||||||
else if (doc["price"].is<JsonObject>())
|
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
|
||||||
for (JsonPair kv : doc["price"].as<JsonObject>())
|
for (JsonPair kv : doc["price"].as<JsonObject>())
|
||||||
{
|
{
|
||||||
const char *currency = kv.key().c_str();
|
const char *currency = kv.key().c_str();
|
||||||
uint newPrice = kv.value().as<uint>();
|
uint newPrice = kv.value().as<uint>();
|
||||||
|
|
||||||
// Convert currency string to PriceNotify::Currency using data_handler's conversion
|
processNewPrice(newPrice, getCurrencyChar(currency));
|
||||||
char currencyChar = getCurrencyChar(currency);
|
|
||||||
priceManager.processNewPrice(newPrice, static_cast<PriceNotify::Currency>(currencyChar));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,7 +172,7 @@ namespace V2Notify
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
webSocket.loop();
|
webSocket.loop();
|
||||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
|
@ -246,8 +247,9 @@ JsonDocument getStatusObject()
|
||||||
|
|
||||||
JsonObject conStatus = root["connectionStatus"].to<JsonObject>();
|
JsonObject conStatus = root["connectionStatus"].to<JsonObject>();
|
||||||
|
|
||||||
conStatus["price"] = priceManager.isConnected();
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
#include "lib/block_notify.hpp"
|
#include "lib/block_notify.hpp"
|
||||||
#include "lib/led_handler.hpp"
|
#include "lib/led_handler.hpp"
|
||||||
|
#include "lib/price_notify.hpp"
|
||||||
#include "lib/screen_handler.hpp"
|
#include "lib/screen_handler.hpp"
|
||||||
#include "webserver/OneParamRewrite.hpp"
|
#include "webserver/OneParamRewrite.hpp"
|
||||||
#include "lib/mining_pool/pool_factory.hpp"
|
#include "lib/mining_pool/pool_factory.hpp"
|
||||||
|
|
20
src/main.cpp
20
src/main.cpp
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue