Make zap notify more lightning like, verify SSL certificates, remove price fetch code

This commit is contained in:
Djuri 2024-09-11 03:23:41 +02:00
parent 5425ea7fbf
commit 1f2110fc5a
21 changed files with 494 additions and 190 deletions

View file

@ -7,49 +7,42 @@ uint blockMedianFee = 1;
bool blockNotifyInit = false;
unsigned long int lastBlockUpdate;
// const char *mempoolWsCert = R"(-----BEGIN CERTIFICATE-----
// MIIHfTCCBmWgAwIBAgIRANFX3mhqRYDt1NFuENoSyaAwDQYJKoZIhvcNAQELBQAw
// gZUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
// BgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDE9MDsGA1UE
// AxM0U2VjdGlnbyBSU0EgT3JnYW5pemF0aW9uIFZhbGlkYXRpb24gU2VjdXJlIFNl
// cnZlciBDQTAeFw0yMzA3MjQwMDAwMDBaFw0yNDA4MjIyMzU5NTlaMFcxCzAJBgNV
// BAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEgMB4GA1UEChMXTUVNUE9PTCBTUEFDRSBD
// Ty4sIExURC4xFjAUBgNVBAMTDW1lbXBvb2wuc3BhY2UwggEiMA0GCSqGSIb3DQEB
// AQUAA4IBDwAwggEKAoIBAQCqmiPRWgo58d25R0biQjAksXMq5ciH7z7ZQo2w2AbB
// rHxpnlIry74b9S4wRY5UJeYmd6ZwA76NdSioDvxTJc29bLplY+Ftmfc4ET0zYb2k
// Fi86z7GOWb6Ezor/qez9uMM9cxd021Bvcs0/2OrL6Sgp66u9keDZv9NyvFPpXfuR
// tdV2r4HF57VJqZn105PN4k80kNWgDbae8aw+BuUNvQYKEe71yfB7Bh6zSh9pCSfM
// I6pIJdQzoada2uY1dQMoJeIq8qKNKqAPKGsH5McemUT5ZIKU/tjk3nfX0pz/sQa4
// CN7tLH6UeUlctei92GFd6Xtn7RbKLhDUbc4Sq02Cc9iXAgMBAAGjggQDMIID/zAf
// BgNVHSMEGDAWgBQX2dYlJ2f5McJJQ9kwNkSMbKlP6zAdBgNVHQ4EFgQUXkxoddJ6
// rKobsbmDdtuCK1ywXuIwDgYDVR0PAQH/BAQDAgWgMAwGA1UdEwEB/wQCMAAwHQYD
// VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMEoGA1UdIARDMEEwNQYMKwYBBAGy
// MQECAQMEMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgG
// BmeBDAECAjBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLnNlY3RpZ28uY29t
// L1NlY3RpZ29SU0FPcmdhbml6YXRpb25WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0Eu
// Y3JsMIGKBggrBgEFBQcBAQR+MHwwVQYIKwYBBQUHMAKGSWh0dHA6Ly9jcnQuc2Vj
// dGlnby5jb20vU2VjdGlnb1JTQU9yZ2FuaXphdGlvblZhbGlkYXRpb25TZWN1cmVT
// ZXJ2ZXJDQS5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29t
// MIIBgAYKKwYBBAHWeQIEAgSCAXAEggFsAWoAdwB2/4g/Crb7lVHCYcz1h7o0tKTN
// uyncaEIKn+ZnTFo6dAAAAYmc9m/gAAAEAwBIMEYCIQD8XOozx411S/bnZambGjTB
// yTcr2fCmggUfQLSmqksD5gIhAIjiEMg0o1VSuQW31gWzfzL6idCkIZeSKN104cdp
// xa4SAHcA2ra/az+1tiKfm8K7XGvocJFxbLtRhIU0vaQ9MEjX+6sAAAGJnPZwPwAA
// BAMASDBGAiEA2sPTZTzvxewzQ8vk36+BWAKuJS7AvJ5W3clvfwCa8OUCIQC74ekT
// Ged2fqQE4sVy74aS6HRA2ihC9VLtNrASJx1YjQB2AO7N0GTV2xrOxVy3nbTNE6Iy
// h0Z8vOzew1FIWUZxH7WbAAABiZz2cA8AAAQDAEcwRQIgEklH7wYCFuuJIFUHX5PY
// /vZ3bDoxOp+061PT3caa+rICIQC0abgfGlBKiHxp47JZxnW3wcVqWdiYX4ViLm9H
// xfx4ljCBxgYDVR0RBIG+MIG7gg1tZW1wb29sLnNwYWNlghMqLmZtdC5tZW1wb29s
// LnNwYWNlghMqLmZyYS5tZW1wb29sLnNwYWNlgg8qLm1lbXBvb2wuc3BhY2WCEyou
// dGs3Lm1lbXBvb2wuc3BhY2WCEyoudmExLm1lbXBvb2wuc3BhY2WCDGJpc3EubWFy
// a2V0c4IKYmlzcS5uaW5qYYIObGlxdWlkLm5ldHdvcmuCDGxpcXVpZC5wbGFjZYIN
// bWVtcG9vbC5uaW5qYTANBgkqhkiG9w0BAQsFAAOCAQEAFvOSRnlHDfq9C8acjZEG
// 5XIqjNYigyWyjOvx83of6Z3PBKkAZB5D/UHBPp+jBDJiEb/QXC7Z7Y7kpuvnoVib
// b4jDc0RjGEsxL+3F7cSw26m3wILJhhHooGZRmFY4GOAeCZtYCOTzJsiZvFpDoQjU
// hTBxtaps05z0Ly9/eYvkXnjnBNROZJVR+KYHlq4TIoGNc4q4KvpfHv2I/vhS2M1e
// bECNNPEyRxHGKdXXO3huocE7aVKpy+JDR6cWwDu6hpdc1j/SCDqdTDFQ7McHOrqA
// fpPh4FcfePMh7Mqxtg2pSs5pXPtiP0ZjLgxd7HbAXct8Y+/jGk+k3sx3SeYXVimr
// ew==
// -----END CERTIFICATE-----)";
const char *mempoolWsCert = R"EOF(
-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
jjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----
)EOF";
void setupBlockNotify()
{
@ -103,13 +96,18 @@ void setupBlockNotify()
esp_websocket_client_config_t config = {
// .uri = "wss://mempool.space/api/v1/ws",
// .task_stack = (6*1024),
// .cert_pem = mempoolWsCert,
.user_agent = USER_AGENT,
.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);
@ -302,7 +300,10 @@ int getBlockFetch()
{
try {
WiFiClientSecure client;
client.setInsecure();
if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) {
client.setCACert(mempoolWsCert);
}
String mempoolInstance =
preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);

View file

@ -753,4 +753,16 @@ bool isActiveCurrency(std::string &currency)
return true;
}
return false;
}
const char* getFirmwareFilename() {
if (HW_REV == "REV_B_EPD_2_13") {
return "btclock_rev_b_213epd_firmware.bin";
} else if (HW_REV == "REV_A_EPD_2_13") {
return "lolin_s3_mini_213epd_firmware.bin";
} else if (HW_REV == "REV_A_EPD_2_9") {
return "lolin_s3_mini_29epd_firmware.bin";
} else {
return "";
}
}

View file

@ -82,4 +82,5 @@ void addScreenMapping(int value, const char* name);
// void addScreenMapping(int value, const std::string& name);
int findScreenIndexByValue(int value);
String replaceAmbiguousChars(String input);
String replaceAmbiguousChars(String input);
const char* getFirmwareFilename();

View file

@ -298,9 +298,14 @@ void ledTask(void *parameter)
}
}
#endif
blinkDelayColor(250, 3, 142, 48, 235);
// blinkDelayTwoColor(250, 3, pixels.Color(142, 48, 235),
// pixels.Color(169, 21, 255));
for (int flash = 0; flash < random(7, 10); flash++)
{
lightningStrike();
delay(random(50, 150));
}
// blinkDelayColor(250, 3, 142, 48, 235);
// blinkDelayTwoColor(250, 3, pixels.Color(142, 48, 235),
// pixels.Color(169, 21, 255));
#ifdef HAS_FRONTLIGHT
if (preferences.getBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE))
{
@ -668,4 +673,29 @@ void ledTheaterChaseRainbow(int wait)
}
}
void lightningStrike()
{
uint32_t PURPLE = pixels.Color(128, 0, 128);
uint32_t YELLOW = pixels.Color(255, 226, 41);
// Randomly choose which LEDs to light up
for (int i = 0; i < pixels.numPixels(); i++)
{
if (random(2) == 0)
{ // 50% chance for each LED
pixels.setPixelColor(i, YELLOW);
}
else
{
pixels.setPixelColor(i, PURPLE);
}
}
pixels.show();
delay(random(10, 50)); // Flash duration
// Return to purple background
// setAllPixels(PURPLE);
}
Adafruit_NeoPixel getPixels() { return pixels; }

View file

@ -61,6 +61,7 @@ void ledRainbow(int wait);
void ledTheaterChaseRainbow(int wait);
void ledTheaterChase(uint32_t color, int wait);
Adafruit_NeoPixel getPixels();
void lightningStrike();
#ifdef HAS_FRONTLIGHT
void frontlightFlash(int flDelayTime);

View file

@ -70,62 +70,62 @@ void handleOTATask(void *parameter) {
}
}
void downloadUpdate() {
WiFiClientSecure client;
client.setInsecure();
HTTPClient http;
http.setUserAgent(USER_AGENT);
// void downloadUpdate() {
// WiFiClientSecure client;
// client.setInsecure();
// HTTPClient http;
// http.setUserAgent(USER_AGENT);
// Send HTTP request to CoinGecko API
http.useHTTP10(true);
// // Send HTTP request to CoinGecko API
// http.useHTTP10(true);
http.begin(client,
"https://api.github.com/repos/btclock/btclock_v3/releases/latest");
int httpCode = http.GET();
// http.begin(client,
// "https://api.github.com/repos/btclock/btclock_v3/releases/latest");
// int httpCode = http.GET();
if (httpCode == 200) {
// WiFiClient * stream = http->getStreamPtr();
// if (httpCode == 200) {
// // WiFiClient * stream = http->getStreamPtr();
JsonDocument filter;
// JsonDocument filter;
JsonObject filter_assets_0 = filter["assets"].add<JsonObject>();
filter_assets_0["name"] = true;
filter_assets_0["browser_download_url"] = true;
// JsonObject filter_assets_0 = filter["assets"].add<JsonObject>();
// filter_assets_0["name"] = true;
// filter_assets_0["browser_download_url"] = true;
JsonDocument doc;
// JsonDocument doc;
DeserializationError error = deserializeJson(
doc, http.getStream(), DeserializationOption::Filter(filter));
// DeserializationError error = deserializeJson(
// doc, http.getStream(), DeserializationOption::Filter(filter));
if (error) {
Serial.print("deserializeJson() failed: ");
Serial.println(error.c_str());
return;
}
// if (error) {
// Serial.print("deserializeJson() failed: ");
// Serial.println(error.c_str());
// return;
// }
String downloadUrl;
for (JsonObject asset : doc["assets"].as<JsonArray>()) {
if (asset["name"].as<String>().compareTo("firmware.bin") == 0) {
downloadUrl = asset["browser_download_url"].as<String>();
break;
}
}
// String downloadUrl;
// for (JsonObject asset : doc["assets"].as<JsonArray>()) {
// if (asset["name"].as<String>().compareTo("firmware.bin") == 0) {
// downloadUrl = asset["browser_download_url"].as<String>();
// break;
// }
// }
Serial.printf("Download update from %s", downloadUrl);
// Serial.printf("Download update from %s", downloadUrl);
// esp_http_client_config_t config = {
// .url = CONFIG_FIRMWARE_UPGRADE_URL,
// };
// esp_https_ota_config_t ota_config = {
// .http_config = &config,
// };
// esp_err_t ret = esp_https_ota(&ota_config);
// if (ret == ESP_OK)
// {
// esp_restart();
// }
}
}
// // esp_http_client_config_t config = {
// // .url = CONFIG_FIRMWARE_UPGRADE_URL,
// // };
// // esp_https_ota_config_t ota_config = {
// // .http_config = &config,
// // };
// // esp_err_t ret = esp_https_ota(&ota_config);
// // if (ret == ESP_OK)
// // {
// // esp_restart();
// // }
// }
// }
void onOTAError(ota_error_t error) {
Serial.println(F("\nOTA update error, restarting"));

View file

@ -8,7 +8,7 @@ void setupOTA();
void onOTAStart();
void handleOTATask(void *parameter);
void onOTAProgress(unsigned int progress, unsigned int total);
void downloadUpdate();
// void downloadUpdate();
void onOTAError(ota_error_t error);
void onOTAComplete();

View file

@ -1,57 +0,0 @@
#include "price_fetch.hpp"
const PROGMEM char *cgApiUrl =
"https://api.coingecko.com/api/v3/simple/"
"price?ids=bitcoin&vs_currencies=usd%2Ceur";
TaskHandle_t priceFetchTaskHandle;
void taskPriceFetch(void *pvParameters) {
WiFiClientSecure *client = new WiFiClientSecure;
client->setInsecure();
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
HTTPClient *http = new HTTPClient();
http->setUserAgent(USER_AGENT);
// Send HTTP request to CoinGecko API
http->begin(*client, cgApiUrl);
int httpCode = http->GET();
// Parse JSON response and extract average price
uint usdPrice, eurPrice;
if (httpCode == 200) {
String payload = http->getString();
JsonDocument doc;
deserializeJson(doc, payload);
// usdPrice = doc["bitcoin"]["usd"];
eurPrice = doc["bitcoin"]["eur"].as<uint>();
setPrice(eurPrice, CURRENCY_EUR);
if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER ||
getCurrentScreen() == SCREEN_SATS_PER_CURRENCY ||
getCurrentScreen() == SCREEN_MARKET_CAP)) {
WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0};
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
}
preferences.putUInt("lastPrice", eurPrice);
} else {
Serial.print(
F("Error retrieving BTC/USD price (CoinGecko). HTTP status code: "));
Serial.println(httpCode);
if (httpCode == -1) {
WiFi.reconnect();
}
}
}
}
void setupPriceFetchTask() {
xTaskCreate(taskPriceFetch, "priceFetch", (6 * 1024), NULL, tskIDLE_PRIORITY,
&priceFetchTaskHandle);
xTaskNotifyGive(priceFetchTaskHandle);
}

View file

@ -1,10 +0,0 @@
#include <Arduino.h>
#include <HTTPClient.h>
#include "lib/config.hpp"
#include "lib/shared.hpp"
extern TaskHandle_t priceFetchTaskHandle;
void setupPriceFetchTask();
void taskPriceFetch(void *pvParameters);

View file

@ -5,6 +5,39 @@ const char *wsOwnServerV2 = "wss://ws-staging.btclock.dev/api/v2/ws";
const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin";
const char* coincapWsCert = R"EOF(
-----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";
// WebsocketsClient client;
esp_websocket_client_handle_t clientPrice = NULL;
@ -14,6 +47,7 @@ unsigned long int lastPriceUpdate;
bool priceNotifyInit = false;
std::map<char, std::uint64_t> currencyMap;
std::map<char, unsigned long int> lastUpdateMap;
WebSocketsClient priceNotifyWs;
void setupPriceNotify()
{
@ -26,14 +60,51 @@ void setupPriceNotify()
{
config = {.uri = wsServerPrice,
.user_agent = USER_AGENT};
config.cert_pem = coincapWsCert;
config.task_stack = (6*1024);
}
clientPrice = esp_websocket_client_init(&config);
esp_websocket_register_events(clientPrice, WEBSOCKET_EVENT_ANY,
onWebsocketPriceEvent, clientPrice);
esp_websocket_client_start(clientPrice);
// priceNotifyWs.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin");
// priceNotifyWs.onEvent(onWebsocketPriceEvent);
// priceNotifyWs.setReconnectInterval(5000);
// priceNotifyWs.enableHeartbeat(15000, 3000, 2);
}
// void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) {
// switch(type) {
// case WStype_DISCONNECTED:
// Serial.printf("[WSc] Disconnected!\n");
// break;
// case WStype_CONNECTED:
// {
// Serial.printf("[WSc] Connected to url: %s\n", payload);
// break;
// }
// case WStype_TEXT:
// String message = String((char*)payload);
// onWebsocketPriceMessage(message);
// break;
// case WStype_BIN:
// break;
// case WStype_ERROR:
// case WStype_FRAGMENT_TEXT_START:
// case WStype_FRAGMENT_BIN_START:
// case WStype_FRAGMENT:
// case WStype_PING:
// case WStype_PONG:
// case WStype_FRAGMENT_FIN:
// break;
// }
// }
void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data)
{
@ -83,7 +154,7 @@ void processNewPrice(uint newPrice, char currency)
"minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE);
uint currentTime = esp_timer_get_time() / 1000000;
if (lastUpdateMap.find(currency) == lastUpdateMap.end()||
if (lastUpdateMap.find(currency) == lastUpdateMap.end() ||
(currentTime - lastUpdateMap[currency]) > minSecPriceUpd)
{
// const unsigned long oldPrice = currentPrice;
@ -108,22 +179,26 @@ void processNewPrice(uint newPrice, char currency)
uint getLastPriceUpdate(char currency)
{
if (lastUpdateMap.find(currency) == lastUpdateMap.end()) {
if (lastUpdateMap.find(currency) == lastUpdateMap.end())
{
return 0;
}
return lastUpdateMap[currency];
}
uint getPrice(char currency) {
if (currencyMap.find(currency) == currencyMap.end()) {
uint getPrice(char currency)
{
if (currencyMap.find(currency) == currencyMap.end())
{
return 0;
}
return currencyMap[currency];
return currencyMap[currency];
}
void setPrice(uint newPrice, char currency) {
currencyMap[currency] = newPrice;
void setPrice(uint newPrice, char currency)
{
currencyMap[currency] = newPrice;
}
bool isPriceNotifyConnected()

View file

@ -12,6 +12,8 @@ void setupPriceNotify();
void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data);
//void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length);
void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data);
uint getPrice(char currency);

View file

@ -126,9 +126,6 @@ void IRAM_ATTR minuteTimerISR(void *arg) {
// vTaskNotifyGiveFromISR(timeUpdateTaskHandle, &xHigherPriorityTaskWoken);
WorkItem timeUpdate = {TASK_TIME_UPDATE, 0};
xQueueSendFromISR(workQueue, &timeUpdate, &xHigherPriorityTaskWoken);
if (priceFetchTaskHandle != NULL) {
vTaskNotifyGiveFromISR(priceFetchTaskHandle, &xHigherPriorityTaskWoken);
}
if (bitaxeFetchTaskHandle != NULL) {
vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken);

View file

@ -8,7 +8,6 @@
#include <bitaxe_handler.hpp>
#include "lib/epd.hpp"
#include "lib/price_fetch.hpp"
#include "lib/shared.hpp"
// extern TaskHandle_t priceUpdateTaskHandle;

View file

@ -1,10 +1,74 @@
#include "shared.hpp"
const char *github_root_ca =
"-----BEGIN CERTIFICATE-----\n"
"MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL\n"
"MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\n"
"eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\n"
"JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx\n"
"MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\n"
"Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg\n"
"VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm\n"
"aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo\n"
"I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng\n"
"o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G\n"
"A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD\n"
"VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB\n"
"zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW\n"
"RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=\n"
"-----END CERTIFICATE-----\n"
"-----BEGIN CERTIFICATE-----\n"
"MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\n"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n"
"MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\n"
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n"
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n"
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n"
"2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n"
"1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n"
"q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\n"
"tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\n"
"vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\n"
"BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n"
"5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n"
"1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\n"
"NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\n"
"Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n"
"8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\n"
"pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\n"
"MrY=\n"
"-----END CERTIFICATE-----\n";
#ifdef TEST_SCREENS
uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24
uint8_t output_row_mono_buffer[max_row_width / 8]; // buffer for at least one row of b/w bits
uint8_t output_row_color_buffer[max_row_width / 8]; // buffer for at least one row of color bits
uint8_t mono_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 b/w
uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24
uint8_t output_row_mono_buffer[max_row_width / 8]; // buffer for at least one row of b/w bits
uint8_t output_row_color_buffer[max_row_width / 8]; // buffer for at least one row of color bits
uint8_t mono_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 b/w
uint8_t color_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 c/w
uint16_t rgb_palette_buffer[max_palette_pixels]; // palette buffer for depth <= 8 for buffered graphics, needed for 7-color display
#endif
uint16_t rgb_palette_buffer[max_palette_pixels]; // palette buffer for depth <= 8 for buffered graphics, needed for 7-color display
#endif
// Function to calculate SHA-256 hash
String calculateSHA256(uint8_t *data, size_t len)
{
byte shaResult[32];
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
mbedtls_md_starts(&ctx);
mbedtls_md_update(&ctx, data, len);
mbedtls_md_finish(&ctx, shaResult);
mbedtls_md_free(&ctx);
char sha256_str[65];
for (int i = 0; i < 32; i++)
{
sprintf(sha256_str + (i * 2), "%02x", shaResult[i]);
}
sha256_str[64] = 0;
return String(sha256_str);
}

View file

@ -7,6 +7,7 @@
#include <freertos/task.h>
#include <GxEPD2.h>
#include <GxEPD2_BW.h>
#include <mbedtls/md.h>
#include <mutex>
#include <utils.hpp>
@ -65,7 +66,16 @@ const PROGMEM int screens[SCREEN_COUNT] = {
const int usPerSecond = 1000000;
const int usPerMinute = 60 * usPerSecond;
extern const char *github_root_ca;
const PROGMEM char UPDATE_FIRMWARE = 0;
const PROGMEM char UPDATE_WEBUI = 1;
struct ScreenMapping {
int value;
const char* name;
};
};
String calculateSHA256(uint8_t* data, size_t len);

View file

@ -9,7 +9,9 @@ void setupV2Notify()
if ( preferences.getBool("stagingSource", DEFAULT_STAGING_SOURCE)) {
Serial.println(F("Connecting to V2 staging source"));
hostname = "ws-staging.btclock.dev";
}
} else {
Serial.println(F("Connecting to V2 source"));
}
webSocket.beginSSL(hostname, 443, "/api/v2/ws");
webSocket.onEvent(onWebsocketV2Event);
@ -120,7 +122,7 @@ void handleV2Message(JsonDocument doc) {
processNewPrice(newPrice, getCurrencyChar(currency));
}
}
}
}
void taskV2Notify(void *pvParameters) {

View file

@ -86,6 +86,8 @@ void setupWebserver()
{
server.on("/upload/firmware", HTTP_POST, onFirmwareUpdate, asyncFirmwareUpdateHandler);
server.on("/upload/webui", HTTP_POST, onFirmwareUpdate, asyncWebuiUpdateHandler);
// server.on("/update/webui", HTTP_GET, onUpdateWebUi);
// server.on("/update/firmware", HTTP_GET, onUpdateFirmware);
}
server.on("/api/restart", HTTP_GET, onApiRestart);
@ -662,7 +664,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["mcapBigChar"] = preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR);
root["mdnsEnabled"] = preferences.getBool("mdnsEnabled", DEFAULT_MDNS_ENABLED);
root["otaEnabled"] = preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED);
root["fetchEurPrice"] = preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE);
// root["fetchEurPrice"] = preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE);
root["useSatsSymbol"] = preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL);
root["useBlkCountdown"] = preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN);
root["suffixPrice"] = preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE);
@ -1043,6 +1045,167 @@ void onApiShowCurrency(AsyncWebServerRequest *request)
request->send(404);
}
String getLatestRelease(const String &fileToDownload)
{
// const char *fileToDownload = "littlefs.bin";
String releaseUrl = "https://api.github.com/repos/btclock/btclock_v3/releases/latest";
WiFiClientSecure client;
client.setCACert(github_root_ca);
HTTPClient http;
http.begin(client, releaseUrl);
http.setUserAgent(USER_AGENT);
int httpCode = http.GET();
String downloadUrl = "";
if (httpCode > 0)
{
String payload = http.getString();
JsonDocument doc;
deserializeJson(doc, payload);
JsonArray assets = doc["assets"];
for (JsonObject asset : assets)
{
if (asset["name"] == fileToDownload)
{
downloadUrl = asset["browser_download_url"].as<String>();
break;
}
}
Serial.printf("Latest release URL: %s\r\n", downloadUrl.c_str());
}
return downloadUrl;
}
void onUpdateWebUi(AsyncWebServerRequest *request)
{
request->send(downloadUpdateHandler(UPDATE_WEBUI));
}
void onUpdateFirmware(AsyncWebServerRequest *request)
{
request->send(downloadUpdateHandler(UPDATE_FIRMWARE));
}
int downloadUpdateHandler(char updateType)
{
WiFiClientSecure client;
client.setCACert(github_root_ca);
HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
String latestRelease = "";
switch (updateType)
{
case UPDATE_FIRMWARE:
latestRelease = getLatestRelease(getFirmwareFilename());
break;
case UPDATE_WEBUI:
latestRelease = getLatestRelease("littlefs.bin");
break;
}
if (latestRelease.equals(""))
{
return 503;
}
http.begin(client, latestRelease);
http.setUserAgent(USER_AGENT);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK)
{
int contentLength = http.getSize();
if (contentLength > 0)
{
uint8_t *buffer = (uint8_t *)malloc(contentLength);
if (buffer)
{
WiFiClient *stream = http.getStreamPtr();
size_t written = stream->readBytes(buffer, contentLength);
if (written == contentLength)
{
String calculated_sha256 = calculateSHA256(buffer, contentLength);
Serial.print("Checksum is ");
Serial.println(calculated_sha256);
if (true)
{
Serial.println("Checksum verified. Proceeding with update.");
Update.onProgress(onOTAProgress);
int updateType = U_FLASH;
switch (updateType)
{
case UPDATE_WEBUI:
updateType = U_SPIFFS;
break;
default:
{
updateType = U_FLASH;
}
}
if (Update.begin(contentLength, updateType))
{
Update.write(buffer, contentLength);
if (Update.end())
{
Serial.println("Update complete. Rebooting.");
ESP.restart();
}
else
{
Serial.println("Error in update process.");
}
}
else
{
Serial.println("Not enough space to begin OTA");
}
}
else
{
Serial.println("Checksum mismatch. Aborting update.");
}
}
else
{
Serial.println("Error downloading firmware");
}
free(buffer);
}
else
{
Serial.println("Not enough memory to allocate buffer");
}
}
else
{
Serial.println("Invalid content length");
}
}
else
{
Serial.print(httpCode);
Serial.println("Error on HTTP request");
return 503;
}
http.end();
return 200;
}
#ifdef HAS_FRONTLIGHT
void onApiFrontlightOn(AsyncWebServerRequest *request)
{

View file

@ -21,10 +21,17 @@ void stopWebServer();
void setupWebserver();
bool processEpdColorSettings(AsyncWebServerRequest *request);
void onApiStatus(AsyncWebServerRequest *request);
void onApiSystemStatus(AsyncWebServerRequest *request);
void onApiSetWifiTxPower(AsyncWebServerRequest *request);
void onUpdateWebUi(AsyncWebServerRequest *request);
void onUpdateFirmware(AsyncWebServerRequest *request);
int downloadUpdateHandler(char updateType);
String getLatestRelease(const String& fileToDownload);
void onApiScreenNext(AsyncWebServerRequest *request);
void onApiScreenPrevious(AsyncWebServerRequest *request);