From 1f2110fc5a4e458acf5935c72300c262e8718a8f Mon Sep 17 00:00:00 2001 From: Djuri Baars Date: Wed, 11 Sep 2024 03:23:41 +0200 Subject: [PATCH] Make zap notify more lightning like, verify SSL certificates, remove price fetch code --- README.md | 7 +- data | 2 +- sdkconfig.defaults | 8 +- src/lib/block_notify.cpp | 95 ++++++++++----------- src/lib/config.cpp | 12 +++ src/lib/config.hpp | 3 +- src/lib/led_handler.cpp | 36 +++++++- src/lib/led_handler.hpp | 1 + src/lib/ota.cpp | 90 ++++++++++---------- src/lib/ota.hpp | 2 +- src/lib/price_fetch.cpp | 57 ------------- src/lib/price_fetch.hpp | 10 --- src/lib/price_notify.cpp | 89 ++++++++++++++++++-- src/lib/price_notify.hpp | 2 + src/lib/screen_handler.cpp | 3 - src/lib/screen_handler.hpp | 1 - src/lib/shared.cpp | 76 +++++++++++++++-- src/lib/shared.hpp | 12 ++- src/lib/v2_notify.cpp | 6 +- src/lib/webserver.cpp | 165 ++++++++++++++++++++++++++++++++++++- src/lib/webserver.hpp | 7 ++ 21 files changed, 494 insertions(+), 190 deletions(-) delete mode 100644 src/lib/price_fetch.cpp delete mode 100644 src/lib/price_fetch.hpp diff --git a/README.md b/README.md index 9e22ee3..5dfc0ad 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,16 @@ Biggest differences with v2 are: - Added market capitalization screen - LED flash on new block (and focus to block height screen on new block) +New features: +- BitAxe integration +- Zap notifier +- + "Steal focus on new block" means that when a new block is mined, the display will switch to the block height screen if it's not on it already. Most [information](https://github.com/btclock/btclock_v2/wiki) about BTClock v2 is still valid for this version. -**NOTE**: The software assumes that the hardware is run in a controlled private network. The Web UI and the OTA update mechanism are not password protected and accessible to anyone in the network. Also, since the device only fetches numbers through WebSockets it will skip server certificate verification to save resources. +**NOTE**: The software assumes that the hardware is run in a controlled private network. ~~The Web UI and the OTA update mechanism are not password protected and accessible to anyone in the network. Also, since the device only fetches numbers through WebSockets it will skip server certificate verification to save resources.~~ Since 3.2.0 the WebUI is password protectable and all certificates are verified. OTA update mechanism is not password-protected. ## Building diff --git a/data b/data index 1fa62ca..1c2d8dc 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 1fa62ca88dc9cf85109712082e2b0d3916d03323 +Subproject commit 1c2d8dcdd0efd846f39b0f24977c982a96f16392 diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 09888de..ffb944e 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -7,8 +7,8 @@ CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y #CONFIG_FREERTOS_USE_TRACE_FACILITY=y #CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y #CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=n -CONFIG_ESP_TLS_INSECURE=y -CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y +#CONFIG_ESP_TLS_INSECURE=y +#CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y CONFIG_HEAP_CORRUPTION_DETECTION=CONFIG_HEAP_POISONING_LIGHT CONFIG_HEAP_POISONING_LIGHT=y @@ -50,4 +50,6 @@ CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120 CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n CONFIG_SPIRAM_CACHE_WORKAROUND=y CONFIG_COMPILER_OPTIMIZATION_PERF=y -CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y \ No newline at end of file +CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y +CONFIG_MBEDTLS_SSL_SERVER_VERIFY=n +CONFIG_MBEDTLS_SSL_VERIFY_CLIENT_CERTIFICATE=n \ No newline at end of file diff --git a/src/lib/block_notify.cpp b/src/lib/block_notify.cpp index 19f1893..bf5d337 100644 --- a/src/lib/block_notify.cpp +++ b/src/lib/block_notify.cpp @@ -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); diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 4acc5d3..c295b93 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -753,4 +753,16 @@ bool isActiveCurrency(std::string ¤cy) 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 ""; + } } \ No newline at end of file diff --git a/src/lib/config.hpp b/src/lib/config.hpp index e615431..e8fcffa 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -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); \ No newline at end of file +String replaceAmbiguousChars(String input); +const char* getFirmwareFilename(); \ No newline at end of file diff --git a/src/lib/led_handler.cpp b/src/lib/led_handler.cpp index 0d780a6..1b6756e 100644 --- a/src/lib/led_handler.cpp +++ b/src/lib/led_handler.cpp @@ -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; } diff --git a/src/lib/led_handler.hpp b/src/lib/led_handler.hpp index 898b121..b86475e 100644 --- a/src/lib/led_handler.hpp +++ b/src/lib/led_handler.hpp @@ -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); diff --git a/src/lib/ota.cpp b/src/lib/ota.cpp index c457a01..709de61 100644 --- a/src/lib/ota.cpp +++ b/src/lib/ota.cpp @@ -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(); - filter_assets_0["name"] = true; - filter_assets_0["browser_download_url"] = true; +// JsonObject filter_assets_0 = filter["assets"].add(); +// 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()) { - if (asset["name"].as().compareTo("firmware.bin") == 0) { - downloadUrl = asset["browser_download_url"].as(); - break; - } - } +// String downloadUrl; +// for (JsonObject asset : doc["assets"].as()) { +// if (asset["name"].as().compareTo("firmware.bin") == 0) { +// downloadUrl = asset["browser_download_url"].as(); +// 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")); diff --git a/src/lib/ota.hpp b/src/lib/ota.hpp index 42f28cc..a3c4ef6 100644 --- a/src/lib/ota.hpp +++ b/src/lib/ota.hpp @@ -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(); diff --git a/src/lib/price_fetch.cpp b/src/lib/price_fetch.cpp deleted file mode 100644 index d2ddf67..0000000 --- a/src/lib/price_fetch.cpp +++ /dev/null @@ -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(); - - 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); -} \ No newline at end of file diff --git a/src/lib/price_fetch.hpp b/src/lib/price_fetch.hpp deleted file mode 100644 index 7016dda..0000000 --- a/src/lib/price_fetch.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -#include "lib/config.hpp" -#include "lib/shared.hpp" - -extern TaskHandle_t priceFetchTaskHandle; - -void setupPriceFetchTask(); -void taskPriceFetch(void *pvParameters); \ No newline at end of file diff --git a/src/lib/price_notify.cpp b/src/lib/price_notify.cpp index f5ad972..d6a1e71 100644 --- a/src/lib/price_notify.cpp +++ b/src/lib/price_notify.cpp @@ -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 currencyMap; std::map 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() diff --git a/src/lib/price_notify.hpp b/src/lib/price_notify.hpp index 541f899..4aad356 100644 --- a/src/lib/price_notify.hpp +++ b/src/lib/price_notify.hpp @@ -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); diff --git a/src/lib/screen_handler.cpp b/src/lib/screen_handler.cpp index 13e93b5..091a6e0 100644 --- a/src/lib/screen_handler.cpp +++ b/src/lib/screen_handler.cpp @@ -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); diff --git a/src/lib/screen_handler.hpp b/src/lib/screen_handler.hpp index a5c8f8e..91e824a 100644 --- a/src/lib/screen_handler.hpp +++ b/src/lib/screen_handler.hpp @@ -8,7 +8,6 @@ #include #include "lib/epd.hpp" -#include "lib/price_fetch.hpp" #include "lib/shared.hpp" // extern TaskHandle_t priceUpdateTaskHandle; diff --git a/src/lib/shared.cpp b/src/lib/shared.cpp index e6acfab..8a6b470 100644 --- a/src/lib/shared.cpp +++ b/src/lib/shared.cpp @@ -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 \ No newline at end of file +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); +} diff --git a/src/lib/shared.hpp b/src/lib/shared.hpp index 5c111df..26c4c95 100644 --- a/src/lib/shared.hpp +++ b/src/lib/shared.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -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; -}; \ No newline at end of file +}; + +String calculateSHA256(uint8_t* data, size_t len); + diff --git a/src/lib/v2_notify.cpp b/src/lib/v2_notify.cpp index e23c0af..13d302d 100644 --- a/src/lib/v2_notify.cpp +++ b/src/lib/v2_notify.cpp @@ -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) { diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index bad307f..392e6c7 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -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(); + 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) { diff --git a/src/lib/webserver.hpp b/src/lib/webserver.hpp index 3f71ac9..546930a 100644 --- a/src/lib/webserver.hpp +++ b/src/lib/webserver.hpp @@ -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);