Compare commits

...
Sign in to create a new pull request.

23 commits

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

2
data

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

View file

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

View file

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

View file

@ -1,201 +1,235 @@
const uint8_t Satoshi_Symbol90pt7bBitmaps[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F,
0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F,
0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF,
0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F,
0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF,
0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF,
0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F,
0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF,
0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xE0 };
#pragma once
#include <Adafruit_GFX.h>
#include <Arduino.h>
#include "fonts.hpp"
const uint8_t Satoshi_Symbol90pt7bBitmaps_Gzip[] = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x63, 0x60,
0x60, 0x60, 0xe0, 0xff, 0xc7, 0x00, 0x05, 0xcc, 0xff, 0x1b, 0x60, 0xcc,
0xff, 0x0f, 0x60, 0x2c, 0xfb, 0x1f, 0x30, 0xd6, 0x60, 0x53, 0x38, 0x28,
0x81, 0xfd, 0x7f, 0x3a, 0x83, 0x81, 0xf6, 0x30, 0x1a, 0xe0, 0xa7, 0xb7,
0xff, 0x0f, 0x0c, 0xb4, 0x8f, 0x51, 0x01, 0x33, 0xbd, 0xfd, 0xff, 0x61,
0xa0, 0x7d, 0x8c, 0x17, 0x0c, 0x8e, 0x92, 0x82, 0x44, 0x85, 0x54, 0x8f,
0x23, 0x88, 0xe1, 0xec, 0xff, 0xa1, 0x56, 0xf2, 0xff, 0xff, 0x03, 0x61,
0xc8, 0xff, 0x87, 0x3a, 0xc7, 0x1e, 0x16, 0x8d, 0xf5, 0x30, 0xa7, 0xc2,
0x12, 0x36, 0xe3, 0x7f, 0xa8, 0xeb, 0x98, 0x47, 0x8d, 0x19, 0x35, 0x66,
0x78, 0x1b, 0x43, 0x65, 0xf0, 0x00, 0x00, 0xc7, 0x63, 0x9f, 0x4b, 0xde,
0x08, 0x00, 0x00
};
// unsigned int satoshi_bin_gz_len = 123;
// const uint8_t Satoshi_Symbol90pt7bBitmaps[] PROGMEM = {
// 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F,
// 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
// 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
// 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
// 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x0F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
// 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F,
// 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
// 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF,
// 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
// 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F,
// 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF,
// 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF,
// 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF,
// 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F,
// 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
// 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF,
// 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
// 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
// 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
// 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF,
// 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// 0xFF, 0xE0 };
const GFXglyph Satoshi_Symbol90pt7bGlyphs[] PROGMEM = {
{ 0, 82, 127, 99, 8, -126 }, { 1302, 71, 109, 93, 0, -117 } }; // 0x53 'S'
const GFXfont Satoshi_Symbol90pt7b PROGMEM = {
(uint8_t *)Satoshi_Symbol90pt7bBitmaps,
(GFXglyph *)Satoshi_Symbol90pt7bGlyphs,
0x53, 0x53, 192 };
// const GFXfont Satoshi_Symbol90pt7b PROGMEM = {
// (uint8_t *)Satoshi_Symbol90pt7bBitmaps,
// (GFXglyph *)Satoshi_Symbol90pt7bGlyphs,
// 0x53, 0x53, 192 };
// Font properties
static constexpr FontData Satoshi_Symbol90pt7b_Properties = {
Satoshi_Symbol90pt7bBitmaps_Gzip,
Satoshi_Symbol90pt7bGlyphs,
sizeof(Satoshi_Symbol90pt7bBitmaps_Gzip),
2270, // Original size
0x53, // First char
0x53, // Last char
192 // yAdvance
};
// Approx. 2284 bytes

View file

@ -1,24 +1,19 @@
#include "bitaxe_fetch.hpp"
TaskHandle_t bitaxeFetchTaskHandle;
uint64_t bitaxeHashrate;
uint64_t bitaxeBestDiff;
uint64_t getBitAxeHashRate()
{
return bitaxeHashrate;
void BitAxeFetch::taskWrapper(void* pvParameters) {
BitAxeFetch::getInstance().task();
}
uint64_t getBitaxeBestDiff()
{
return bitaxeBestDiff;
uint64_t BitAxeFetch::getHashRate() const {
return hashrate;
}
void taskBitaxeFetch(void *pvParameters)
{
for (;;)
{
uint64_t BitAxeFetch::getBestDiff() const {
return bestDiff;
}
void BitAxeFetch::task() {
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
HTTPClient http;
@ -28,46 +23,38 @@ void taskBitaxeFetch(void *pvParameters)
int httpCode = http.GET();
if (httpCode == 200)
{
if (httpCode == 200) {
String payload = http.getString();
JsonDocument doc;
deserializeJson(doc, payload);
// Convert GH/s to H/s (multiply by 10^9)
float hashRateGH = doc["hashRate"].as<float>();
bitaxeHashrate = static_cast<uint64_t>(std::round(hashRateGH * std::pow(10, getHashrateMultiplier('G'))));
hashrate = static_cast<uint64_t>(std::round(hashRateGH * std::pow(10, getHashrateMultiplier('G'))));
// Parse difficulty string and convert to uint64_t
std::string diffStr = doc["bestDiff"].as<std::string>();
char diffUnit = diffStr[diffStr.length() - 1];
if (std::isalpha(diffUnit)) {
float diffValue = std::stof(diffStr.substr(0, diffStr.length() - 1));
bitaxeBestDiff = static_cast<uint64_t>(std::round(diffValue * std::pow(10, getDifficultyMultiplier(diffUnit))));
bestDiff = static_cast<uint64_t>(std::round(diffValue * std::pow(10, getDifficultyMultiplier(diffUnit))));
} else {
bitaxeBestDiff = std::stoull(diffStr);
bestDiff = std::stoull(diffStr);
}
if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_BESTDIFF))
{
if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_BITAXE_BESTDIFF)) {
WorkItem priceUpdate = {TASK_BITAXE_UPDATE, 0};
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
}
}
else
{
Serial.print(
F("Error retrieving BitAxe data. HTTP status code: "));
} else {
Serial.print(F("Error retrieving BitAxe data. HTTP status code: "));
Serial.println(httpCode);
Serial.println(bitaxeApiUrl);
}
}
}
void setupBitaxeFetchTask()
{
xTaskCreate(taskBitaxeFetch, "bitaxeFetch", (3 * 1024), NULL, tskIDLE_PRIORITY,
&bitaxeFetchTaskHandle);
xTaskNotifyGive(bitaxeFetchTaskHandle);
void BitAxeFetch::setup() {
xTaskCreate(taskWrapper, "bitaxeFetch", (3 * 1024), NULL, tskIDLE_PRIORITY, &taskHandle);
xTaskNotifyGive(taskHandle);
}

View file

@ -7,10 +7,28 @@
#include "lib/config.hpp"
#include "lib/shared.hpp"
extern TaskHandle_t bitaxeFetchTaskHandle;
class BitAxeFetch {
public:
static BitAxeFetch& getInstance() {
static BitAxeFetch instance;
return instance;
}
void setupBitaxeFetchTask();
void taskBitaxeFetch(void *pvParameters);
void setup();
uint64_t getHashRate() const;
uint64_t getBestDiff() const;
static void taskWrapper(void* pvParameters);
TaskHandle_t getTaskHandle() const { return taskHandle; }
uint64_t getBitAxeHashRate();
uint64_t getBitaxeBestDiff();
private:
BitAxeFetch() = default;
~BitAxeFetch() = default;
BitAxeFetch(const BitAxeFetch&) = delete;
BitAxeFetch& operator=(const BitAxeFetch&) = delete;
void task();
TaskHandle_t taskHandle = nullptr;
uint64_t hashrate = 0;
uint64_t bestDiff = 0;
};

View file

@ -1,13 +1,14 @@
#include "block_notify.hpp"
char *wsServer;
esp_websocket_client_handle_t blockNotifyClient = NULL;
uint32_t currentBlockHeight = 873400;
uint16_t blockMedianFee = 1;
bool blockNotifyInit = false;
unsigned long int lastBlockUpdate;
// Initialize static members
esp_websocket_client_handle_t BlockNotify::wsClient = nullptr;
uint32_t BlockNotify::currentBlockHeight = 878000;
uint16_t BlockNotify::blockMedianFee = 1;
bool BlockNotify::notifyInit = false;
unsigned long int BlockNotify::lastBlockUpdate = 0;
TaskHandle_t BlockNotify::taskHandle = nullptr;
const char *mempoolWsCert = R"EOF(
const char* BlockNotify::mempoolWsCert = R"EOF(
-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
@ -42,22 +43,106 @@ VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
jjxDah2nGN59PRbxYvnKkKj9
-----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";
void setupBlockNotify()
{
IPAddress result;
void BlockNotify::onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
BlockNotify& instance = BlockNotify::getInstance();
int dnsErr = -1;
String mempoolInstance =
preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':'))
switch (event_id) {
case WEBSOCKET_EVENT_CONNECTED:
{
notifyInit = true;
Serial.print(F("Connected to "));
Serial.println(preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE));
JsonDocument doc;
doc["action"] = "want";
JsonArray dataArray = doc.createNestedArray("data");
dataArray.add("blocks");
dataArray.add("mempool-blocks");
String sub;
serializeJson(doc, sub);
esp_websocket_client_send_text(wsClient, sub.c_str(), sub.length(), portMAX_DELAY);
break;
}
case WEBSOCKET_EVENT_DATA:
instance.onWebsocketMessage(data);
break;
case WEBSOCKET_EVENT_DISCONNECTED:
Serial.println(F("Mempool.space WS Connection Closed"));
break;
case WEBSOCKET_EVENT_ERROR:
Serial.println(F("Mempool.space WS Connection Error"));
break;
}
}
void BlockNotify::onWebsocketMessage(esp_websocket_event_data_t *data) {
JsonDocument doc;
JsonDocument filter;
filter["block"]["height"] = true;
filter["mempool-blocks"][0]["medianFee"] = true;
deserializeJson(doc, (char*)data->data_ptr, DeserializationOption::Filter(filter));
if (doc["block"].is<JsonObject>()) {
JsonObject block = doc["block"];
if (block["height"].as<uint>() != currentBlockHeight) {
processNewBlock(block["height"].as<uint>());
}
}
else if (doc["mempool-blocks"].is<JsonArray>()) {
JsonArray blockInfo = doc["mempool-blocks"].as<JsonArray>();
uint medianFee = (uint)round(blockInfo[0]["medianFee"].as<double>());
processNewBlockFee(medianFee);
}
}
void BlockNotify::setup() {
IPAddress result;
int dnsErr = -1;
String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) {
dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result);
if (dnsErr != 1)
{
if (dnsErr != 1) {
Serial.print(mempoolInstance);
Serial.println(F("mempool DNS could not be resolved"));
WiFi.reconnect();
@ -66,134 +151,58 @@ void setupBlockNotify()
}
// Get current block height through regular API
int blockFetch = getBlockFetch();
int blockFetch = fetchLatestBlock();
if (blockFetch > currentBlockHeight)
currentBlockHeight = blockFetch;
if (currentBlockHeight != -1)
{
if (currentBlockHeight != -1) {
lastBlockUpdate = esp_timer_get_time() / 1000000;
}
if (workQueue != nullptr)
{
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";
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 = {
// .uri = "wss://mempool.space/api/v1/ws",
.task_stack = (6*1024),
.user_agent = USER_AGENT
};
if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) {
if (useSSL) {
config.cert_pem = mempoolWsCert;
}
config.uri = mempoolUri.c_str();
config.uri = wsUri.c_str();
Serial.printf("Connecting to %s\r\n", preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE));
Serial.printf("Connecting to %s\r\n", mempoolInstance.c_str());
blockNotifyClient = esp_websocket_client_init(&config);
esp_websocket_register_events(blockNotifyClient, WEBSOCKET_EVENT_ANY,
onWebsocketBlockEvent, blockNotifyClient);
esp_websocket_client_start(blockNotifyClient);
wsClient = esp_websocket_client_init(&config);
esp_websocket_register_events(wsClient, WEBSOCKET_EVENT_ANY, onWebsocketEvent, wsClient);
esp_websocket_client_start(wsClient);
}
void onWebsocketBlockEvent(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data)
{
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
const String sub = "{\"action\": \"want\", \"data\":[\"blocks\", \"mempool-blocks\"]}";
switch (event_id)
void BlockNotify::processNewBlock(uint32_t newBlockHeight) {
if (newBlockHeight <= currentBlockHeight)
{
case WEBSOCKET_EVENT_CONNECTED:
blockNotifyInit = true;
Serial.println(F("Connected to Mempool.space WebSocket"));
Serial.println(sub);
if (esp_websocket_client_send_text(blockNotifyClient, sub.c_str(),
sub.length(), portMAX_DELAY) == -1)
{
Serial.println(F("Mempool.space WS Block Subscribe Error"));
}
break;
case WEBSOCKET_EVENT_DATA:
onWebsocketBlockMessage(data);
break;
case WEBSOCKET_EVENT_ERROR:
Serial.println(F("Mempool.space WS Connnection error"));
break;
case WEBSOCKET_EVENT_DISCONNECTED:
Serial.println(F("Mempool.space WS Connnection Closed"));
break;
}
}
void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data)
{
JsonDocument doc;
JsonDocument filter;
filter["block"]["height"] = true;
filter["mempool-blocks"][0]["medianFee"] = true;
deserializeJson(doc, (char *)event_data->data_ptr, DeserializationOption::Filter(filter));
// if (error) {
// Serial.print("deserializeJson() failed: ");
// Serial.println(error.c_str());
// return;
// }
if (doc.containsKey("block"))
{
JsonObject block = doc["block"];
if (block["height"].as<uint>() == currentBlockHeight) {
return;
}
processNewBlock(block["height"].as<uint>());
}
else if (doc.containsKey("mempool-blocks"))
{
JsonArray blockInfo = doc["mempool-blocks"].as<JsonArray>();
uint medianFee = (uint)round(blockInfo[0]["medianFee"].as<double>());
processNewBlockFee(medianFee);
}
doc.clear();
}
void processNewBlock(uint32_t newBlockHeight) {
if (newBlockHeight < currentBlockHeight)
return;
currentBlockHeight = newBlockHeight;
// Serial.printf("New block found: %d\r\n", block["height"].as<uint>());
preferences.putUInt("blockHeight", currentBlockHeight);
lastBlockUpdate = esp_timer_get_time() / 1000000;
if (workQueue != nullptr)
{
WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0};
xQueueSend(workQueue, &blockUpdate, portMAX_DELAY);
// xTaskNotifyGive(blockUpdateTaskHandle);
}
if (ScreenHandler::getCurrentScreen() != SCREEN_BLOCK_HEIGHT &&
preferences.getBool("stealFocus", DEFAULT_STEAL_FOCUS))
@ -201,7 +210,6 @@ void processNewBlock(uint32_t newBlockHeight) {
uint64_t timerPeriod = 0;
if (isTimerActive())
{
// store timer periode before making inactive to prevent artifacts
timerPeriod = getTimerSeconds();
esp_timer_stop(screenRotateTimer);
}
@ -217,18 +225,16 @@ void processNewBlock(uint32_t newBlockHeight) {
if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD))
{
vTaskDelay(pdMS_TO_TICKS(250)); // Wait until screens are updated
queueLedEffect(LED_FLASH_BLOCK_NOTIFY);
}
getLedHandler().queueEffect(LED_FLASH_BLOCK_NOTIFY);
}
}
void processNewBlockFee(uint16_t newBlockFee) {
void BlockNotify::processNewBlockFee(uint16_t newBlockFee) {
if (blockMedianFee == newBlockFee)
{
return;
}
// Serial.printf("New median fee: %d\r\n", medianFee);
blockMedianFee = newBlockFee;
if (workQueue != nullptr)
@ -238,60 +244,54 @@ void processNewBlockFee(uint16_t newBlockFee) {
}
}
uint32_t getBlockHeight() { return currentBlockHeight; }
uint32_t BlockNotify::getBlockHeight() const {
return currentBlockHeight;
}
void setBlockHeight(uint32_t newBlockHeight)
void BlockNotify::setBlockHeight(uint32_t newBlockHeight)
{
currentBlockHeight = newBlockHeight;
}
uint16_t getBlockMedianFee() { return blockMedianFee; }
uint16_t BlockNotify::getBlockMedianFee() const {
return blockMedianFee;
}
void setBlockMedianFee(uint16_t newBlockMedianFee)
void BlockNotify::setBlockMedianFee(uint16_t newBlockMedianFee)
{
blockMedianFee = newBlockMedianFee;
}
bool isBlockNotifyConnected()
bool BlockNotify::isConnected() const
{
if (blockNotifyClient == NULL)
if (wsClient == NULL)
return false;
return esp_websocket_client_is_connected(blockNotifyClient);
return esp_websocket_client_is_connected(wsClient);
}
bool getBlockNotifyInit()
bool BlockNotify::isInitialized() const
{
return blockNotifyInit;
return notifyInit;
}
void stopBlockNotify()
void BlockNotify::stop()
{
if (blockNotifyClient == NULL)
if (wsClient == NULL)
return;
esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000));
esp_websocket_client_stop(blockNotifyClient);
esp_websocket_client_destroy(blockNotifyClient);
blockNotifyClient = NULL;
esp_websocket_client_close(wsClient, portMAX_DELAY);
esp_websocket_client_stop(wsClient);
esp_websocket_client_destroy(wsClient);
wsClient = NULL;
}
void restartBlockNotify()
void BlockNotify::restart()
{
stopBlockNotify();
if (blockNotifyClient == NULL) {
setupBlockNotify();
return;
}
// esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000));
// esp_websocket_client_stop(blockNotifyClient);
// esp_websocket_client_start(blockNotifyClient);
stop();
setup();
}
int getBlockFetch() {
int BlockNotify::fetchLatestBlock() {
try {
String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "https" : "http";
@ -314,12 +314,12 @@ int getBlockFetch() {
return 2203; // B-T-C
}
uint getLastBlockUpdate()
uint BlockNotify::getLastBlockUpdate() const
{
return lastBlockUpdate;
}
void setLastBlockUpdate(uint lastUpdate)
void BlockNotify::setLastBlockUpdate(uint lastUpdate)
{
lastBlockUpdate = lastUpdate;
}

View file

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

View file

@ -1,4 +1,5 @@
#include "config.hpp"
#include "led_handler.hpp"
#define MAX_ATTEMPTS_WIFI_CONNECTION 20
@ -47,10 +48,11 @@ void setup()
setupPreferences();
setupHardware();
setupDisplays();
EPDManager::getInstance().initialize();
if (preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER))
{
queueLedEffect(LED_POWER_TEST);
auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_POWER_TEST);
}
{
std::lock_guard<std::mutex> lockMcp(mcpMutex);
@ -60,7 +62,8 @@ void setup()
preferences.remove("txPower");
WiFi.eraseAP();
queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS);
auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_EFFECT_WIFI_ERASE_SETTINGS);
}
}
@ -76,7 +79,8 @@ void setup()
else if (mcp1.read1(1) == LOW)
{
preferences.clear();
queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS);
auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_EFFECT_WIFI_ERASE_SETTINGS);
nvs_flash_erase();
delay(1000);
@ -100,37 +104,53 @@ void setup()
if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED))
{
setupBitaxeFetchTask();
BitAxeFetch::getInstance().setup();
}
if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED))
{
setupMiningPoolStatsFetchTask();
MiningPoolStatsFetch::getInstance().setup();
}
ButtonHandler::setup();
setupOTA();
waitUntilNoneBusy();
EPDManager::getInstance().waitUntilNoneBusy();
#ifdef HAS_FRONTLIGHT
if (!preferences.getBool("flAlwaysOn", DEFAULT_FL_ALWAYS_ON))
{
frontlightFadeOutAll(preferences.getUInt("flEffectDelay"), true);
auto& ledHandler = getLedHandler();
ledHandler.frontlightFadeOutAll(preferences.getUInt("flEffectDelay"), true);
flArray.allOFF();
}
#endif
forceFullRefresh();
EPDManager::getInstance().forceFullRefresh();
}
void setupWifi()
{
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.setAutoReconnect(true);
WiFi.begin();
if (preferences.getInt("txPower", DEFAULT_TX_POWER))
{
if (WiFi.setTxPower(
@ -144,7 +164,8 @@ void setupWifi()
// if (!preferences.getBool("wifiConfigured", DEFAULT_WIFI_CONFIGURED)
{
queueLedEffect(LED_EFFECT_WIFI_WAIT_FOR_CONFIG);
auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_EFFECT_WIFI_WAIT_FOR_CONFIG);
bool buttonPress = false;
{
@ -167,6 +188,7 @@ void setupWifi()
wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT));
wm.setWiFiAutoReconnect(false);
wm.setDebugOutput(false);
wm.setCountry("NL");
wm.setConfigPortalBlocking(true);
wm.setAPCallback([&](WiFiManager *wifiManager)
@ -176,8 +198,8 @@ void setupWifi()
wifiManager->getConfigPortalSSID().c_str(),
softAP_password.c_str());
// delay(6000);
setFgColor(GxEPD_BLACK);
setBgColor(GxEPD_WHITE);
EPDManager::getInstance().setForegroundColor(GxEPD_BLACK);
EPDManager::getInstance().setBackgroundColor(GxEPD_WHITE);
const String qrText = "qrWIFI:S:" + wifiManager->getConfigPortalSSID() +
";T:WPA;P:" + softAP_password.c_str() + ";;";
const String explainText = "*SSID: *\r\n" +
@ -207,58 +229,22 @@ void setupWifi()
#endif
"\r\n\r\n*FW build date:*\r\n" + formattedDate,
qrText};
setEpdContent(epdContent); });
EPDManager::getInstance().setContent(epdContent); });
wm.setSaveConfigCallback([]()
{
preferences.putBool("wifiConfigured", true);
delay(1000);
// just restart after succes
// just restart after success
ESP.restart(); });
bool ac = wm.autoConnect(softAP_SSID.c_str(), softAP_password.c_str());
// waitUntilNoneBusy();
// std::array<String, NUM_SCREENS> epdContent = {"Welcome!",
// "Bienvenidos!", "Use\r\nweb-interface\r\nto configure", "Use\r\nla
// interfaz web\r\npara configurar", "Or
// restart\r\nwhile\r\nholding\r\n2nd button\r\r\nto start\r\n QR-config",
// "O reinicie\r\nmientras\r\n mantiene presionado\r\nel segundo
// botón\r\r\npara iniciar\r\nQR-config", ""}; setEpdContent(epdContent);
// esp_task_wdt_init(30, false);
// uint count = 0;
// while (WiFi.status() != WL_CONNECTED)
// {
// if (Serial.available() > 0)
// {
// uint8_t b = Serial.read();
// if (parse_improv_serial_byte(x_position, b, x_buffer,
// onImprovCommandCallback, onImprovErrorCallback))
// {
// x_buffer[x_position++] = b;
// }
// else
// {
// x_position = 0;
// }
// }
// count++;
// if (count > 2000000) {
// queueLedEffect(LED_EFFECT_HEARTBEAT);
// count = 0;
// }
// }
// esp_task_wdt_deinit();
// esp_task_wdt_reset();
}
setFgColor(preferences.getUInt("fgColor", isWhiteVersion() ? GxEPD_BLACK : GxEPD_WHITE));
setBgColor(preferences.getUInt("bgColor", isWhiteVersion() ? GxEPD_WHITE : GxEPD_BLACK));
EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", isWhiteVersion() ? GxEPD_BLACK : GxEPD_WHITE));
EPDManager::getInstance().setBackgroundColor(preferences.getUInt("bgColor", isWhiteVersion() ? GxEPD_WHITE : GxEPD_BLACK));
}
// else
// {
@ -279,7 +265,8 @@ void syncTime()
while (!getLocalTime(&timeinfo))
{
queueLedEffect(LED_EFFECT_CONFIGURING);
auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_EFFECT_CONFIGURING);
configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0,
NTP_SERVER);
delay(500);
@ -293,9 +280,9 @@ void setupPreferences()
{
preferences.begin("btclock", false);
setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR));
setBgColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR));
setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT));
EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR));
EPDManager::getInstance().setBackgroundColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR));
BlockNotify::getInstance().setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT));
setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD);
if (!preferences.isKey("enableDebugLog")) {
@ -374,7 +361,7 @@ void setupPreferences()
if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED))
{
addScreenMapping(SCREEN_MINING_POOL_STATS_HASHRATE, "Mining Pool Hashrate");
if (getMiningPool()->supportsDailyEarnings()) {
if (MiningPoolStatsFetch::getInstance().getPool()->supportsDailyEarnings()) {
addScreenMapping(SCREEN_MINING_POOL_STATS_EARNINGS, "Mining Pool Earnings");
}
}
@ -403,7 +390,7 @@ void setupWebsocketClients(void *pvParameters)
}
else if (dataSource == THIRD_PARTY_SOURCE)
{
setupBlockNotify();
BlockNotify::getInstance().setup();
setupPriceNotify();
}
@ -420,13 +407,14 @@ void setupTimers()
void finishSetup()
{
auto& ledHandler = getLedHandler();
if (preferences.getBool("ledStatus", DEFAULT_LED_STATUS))
{
restoreLedState();
ledHandler.restoreLedState();
}
else
{
clearLeds();
ledHandler.clear();
}
}
@ -475,22 +463,9 @@ void setupHardware()
Serial.println(F("Error loading WebUI"));
}
// {
// File f = LittleFS.open("/qr.txt", "w");
// if(f) {
// if (f.print("Hello")) {
// Serial.println(F("Written QR to FS"));
// Serial.printf("\nLittleFS free: %zu\n", LittleFS.totalBytes() - LittleFS.usedBytes());
// }
// } else {
// Serial.println(F("Can't write QR to FS"));
// }
// f.close();
// }
setupLeds();
// Initialize LED handler
auto& ledHandler = getLedHandler();
ledHandler.setup();
WiFi.setHostname(getMyHostname().c_str());
if (!psramInit())
@ -548,7 +523,8 @@ void setupHardware()
#endif
#ifdef HAS_FRONTLIGHT
setupFrontlight();
// Initialize frontlight through LedHandler
ledHandler.initializeFrontlight();
Wire.beginTransmission(0x5C);
byte error = Wire.endTransmission();
@ -570,6 +546,7 @@ void setupHardware()
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
{
static bool first_connect = true;
auto& ledHandler = getLedHandler(); // Get ledHandler reference once at the start
Serial.printf("[WiFi-event] event: %d\n", event);
@ -595,7 +572,7 @@ void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
if (!first_connect)
{
Serial.println(F("Disconnected from WiFi access point"));
queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR);
ledHandler.queueEffect(LED_EFFECT_WIFI_CONNECT_ERROR);
uint8_t reason = info.wifi_sta_disconnected.reason;
if (reason)
Serial.printf("Disconnect reason: %s, ",
@ -611,13 +588,13 @@ void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
Serial.print("Obtained IP address: ");
Serial.println(WiFi.localIP());
if (!first_connect)
queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS);
ledHandler.queueEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS);
first_connect = false;
break;
}
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
Serial.println(F("Lost IP address and IP address is reset to 0"));
queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR);
ledHandler.queueEffect(LED_EFFECT_WIFI_CONNECT_ERROR);
WiFi.reconnect();
break;
case ARDUINO_EVENT_WIFI_AP_START:
@ -667,30 +644,6 @@ uint getLastTimeSync()
}
#ifdef HAS_FRONTLIGHT
void setupFrontlight()
{
if (!flArray.begin(PCA9685_MODE1_AUTOINCR | PCA9685_MODE1_ALLCALL, PCA9685_MODE2_TOTEMPOLE))
{
Serial.println(F("FL driver error"));
return;
}
flArray.setFrequency(200);
Serial.println(F("FL driver active"));
if (!preferences.isKey("flMaxBrightness"))
{
preferences.putUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS);
}
if (!preferences.isKey("flEffectDelay"))
{
preferences.putUInt("flEffectDelay", DEFAULT_FL_EFFECT_DELAY);
}
if (!preferences.isKey("flFlashOnUpd"))
{
preferences.putBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE);
}
}
float getLightLevel()
{

View file

@ -52,10 +52,10 @@ void setupTimers();
void finishSetup();
void setupMcp();
#ifdef HAS_FRONTLIGHT
void setupFrontlight();
extern BH1750 bh1750;
extern bool hasLuxSensor;
float getLightLevel();
bool hasLightLevel();
extern PCA9685 flArray;
#endif
String getMyHostname();
@ -98,6 +98,10 @@ extern MCP23017 mcp1;
extern MCP23017 mcp2;
#endif
#ifdef HAS_FRONTLIGHT
extern PCA9685 flArray;
#endif
// Expose DataSourceType enum
extern DataSourceType getDataSource();
extern void setDataSource(DataSourceType source);

View file

@ -46,8 +46,8 @@
#define DEFAULT_LUX_LIGHT_TOGGLE 128
#define DEFAULT_FL_OFF_WHEN_DARK true
#define DEFAULT_FL_ALWAYS_ON false
#define DEFAULT_FL_FLASH_ON_UPDATE false
#define DEFAULT_FL_ALWAYS_ON true
#define DEFAULT_FL_FLASH_ON_UPDATE true
#define DEFAULT_LED_STATUS false
#define DEFAULT_TIMER_ACTIVE true
@ -60,6 +60,7 @@
#define DEFAULT_MINING_POOL_STATS_ENABLED false
#define DEFAULT_MINING_POOL_NAME "ocean"
#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_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422"

View file

@ -1,217 +1,146 @@
#include "epd.hpp"
// Initialize static members
#ifdef IS_BTCLOCK_REV_B
Native_Pin EPD_CS[NUM_SCREENS] = {
Native_Pin(2),
Native_Pin(4),
Native_Pin(6),
Native_Pin(10),
Native_Pin(38),
Native_Pin(21),
Native_Pin(17),
Native_Pin EPDManager::EPD_DC(14);
std::array<Native_Pin, NUM_SCREENS> EPDManager::EPD_CS = {
Native_Pin(2), Native_Pin(4), Native_Pin(6), Native_Pin(10),
Native_Pin(38), Native_Pin(21), Native_Pin(17)
};
Native_Pin EPD_BUSY[NUM_SCREENS] = {
Native_Pin(3),
Native_Pin(5),
Native_Pin(7),
Native_Pin(9),
Native_Pin(37),
Native_Pin(18),
Native_Pin(16),
std::array<Native_Pin, NUM_SCREENS> EPDManager::EPD_BUSY = {
Native_Pin(3), Native_Pin(5), Native_Pin(7), Native_Pin(9),
Native_Pin(37), Native_Pin(18), Native_Pin(16)
};
MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = {
MCP23X17_Pin(mcp1, 8),
MCP23X17_Pin(mcp1, 9),
MCP23X17_Pin(mcp1, 10),
MCP23X17_Pin(mcp1, 11),
MCP23X17_Pin(mcp1, 12),
MCP23X17_Pin(mcp1, 13),
MCP23X17_Pin(mcp1, 14),
std::array<MCP23X17_Pin, NUM_SCREENS> EPDManager::EPD_RESET = {
MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10),
MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13),
MCP23X17_Pin(mcp1, 14)
};
Native_Pin EPD_DC = Native_Pin(14);
#elif IS_BTCLOCK_V8
Native_Pin EPD_DC = Native_Pin(38);
MCP23X17_Pin EPD_BUSY[NUM_SCREENS] = {
MCP23X17_Pin(mcp1, 8),
MCP23X17_Pin(mcp1, 9),
MCP23X17_Pin(mcp1, 10),
MCP23X17_Pin(mcp1, 11),
MCP23X17_Pin(mcp1, 12),
MCP23X17_Pin(mcp1, 13),
MCP23X17_Pin(mcp1, 14),
MCP23X17_Pin(mcp1, 4),
#elif defined(IS_BTCLOCK_V8)
Native_Pin EPDManager::EPD_DC(38);
std::array<MCP23X17_Pin, NUM_SCREENS> EPDManager::EPD_BUSY = {
MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10),
MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13),
MCP23X17_Pin(mcp1, 14), MCP23X17_Pin(mcp1, 4)
};
MCP23X17_Pin EPD_CS[NUM_SCREENS] = {
std::array<MCP23X17_Pin, NUM_SCREENS> EPDManager::EPD_CS = {
MCP23X17_Pin(mcp2, 8), MCP23X17_Pin(mcp2, 10), MCP23X17_Pin(mcp2, 12),
MCP23X17_Pin(mcp2, 14), MCP23X17_Pin(mcp2, 0), MCP23X17_Pin(mcp2, 2),
MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6)};
MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = {
MCP23X17_Pin(mcp2, 9),
MCP23X17_Pin(mcp2, 11),
MCP23X17_Pin(mcp2, 13),
MCP23X17_Pin(mcp2, 15),
MCP23X17_Pin(mcp2, 1),
MCP23X17_Pin(mcp2, 3),
MCP23X17_Pin(mcp2, 5),
MCP23X17_Pin(mcp2, 7),
MCP23X17_Pin(mcp2, 4), MCP23X17_Pin(mcp2, 6)
};
std::array<MCP23X17_Pin, NUM_SCREENS> EPDManager::EPD_RESET = {
MCP23X17_Pin(mcp2, 9), MCP23X17_Pin(mcp2, 11), MCP23X17_Pin(mcp2, 13),
MCP23X17_Pin(mcp2, 15), MCP23X17_Pin(mcp2, 1), MCP23X17_Pin(mcp2, 3),
MCP23X17_Pin(mcp2, 5), MCP23X17_Pin(mcp2, 7)
};
#else
Native_Pin EPD_CS[NUM_SCREENS] = {
Native_Pin(2),
Native_Pin(4),
Native_Pin(6),
Native_Pin(10),
Native_Pin(33),
Native_Pin(21),
Native_Pin(17),
#if NUM_SCREENS == 9
// MCP23X17_Pin(mcp2, 7),
Native_Pin(-1),
Native_Pin(-1),
#endif
Native_Pin EPDManager::EPD_DC(14);
std::array<Native_Pin, NUM_SCREENS> EPDManager::EPD_CS = {
Native_Pin(2), Native_Pin(4), Native_Pin(6), Native_Pin(10),
Native_Pin(33), Native_Pin(21), Native_Pin(17)
};
Native_Pin EPD_BUSY[NUM_SCREENS] = {
Native_Pin(3),
Native_Pin(5),
Native_Pin(7),
Native_Pin(9),
Native_Pin(37),
Native_Pin(18),
Native_Pin(16),
std::array<Native_Pin, NUM_SCREENS> EPDManager::EPD_BUSY = {
Native_Pin(3), Native_Pin(5), Native_Pin(7), Native_Pin(9),
Native_Pin(37), Native_Pin(18), Native_Pin(16)
};
MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = {
MCP23X17_Pin(mcp1, 8),
MCP23X17_Pin(mcp1, 9),
MCP23X17_Pin(mcp1, 10),
MCP23X17_Pin(mcp1, 11),
MCP23X17_Pin(mcp1, 12),
MCP23X17_Pin(mcp1, 13),
MCP23X17_Pin(mcp1, 14),
std::array<MCP23X17_Pin, NUM_SCREENS> EPDManager::EPD_RESET = {
MCP23X17_Pin(mcp1, 8), MCP23X17_Pin(mcp1, 9), MCP23X17_Pin(mcp1, 10),
MCP23X17_Pin(mcp1, 11), MCP23X17_Pin(mcp1, 12), MCP23X17_Pin(mcp1, 13),
MCP23X17_Pin(mcp1, 14)
};
Native_Pin EPD_DC = Native_Pin(14);
#endif
GxEPD2_BW<EPD_CLASS, EPD_CLASS::HEIGHT> displays[NUM_SCREENS] = {
EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET_MPD[0], &EPD_BUSY[0]),
EPD_CLASS(&EPD_CS[1], &EPD_DC, &EPD_RESET_MPD[1], &EPD_BUSY[1]),
EPD_CLASS(&EPD_CS[2], &EPD_DC, &EPD_RESET_MPD[2], &EPD_BUSY[2]),
EPD_CLASS(&EPD_CS[3], &EPD_DC, &EPD_RESET_MPD[3], &EPD_BUSY[3]),
EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET_MPD[4], &EPD_BUSY[4]),
EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET_MPD[5], &EPD_BUSY[5]),
EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET_MPD[6], &EPD_BUSY[6]),
#ifdef IS_BTCLOCK_V8
EPD_CLASS(&EPD_CS[7], &EPD_DC, &EPD_RESET_MPD[7], &EPD_BUSY[7]),
#endif
};
EPDManager& EPDManager::getInstance() {
static EPDManager instance;
return instance;
}
std::array<String, NUM_SCREENS> currentEpdContent;
std::array<String, NUM_SCREENS> epdContent;
uint32_t lastFullRefresh[NUM_SCREENS];
TaskHandle_t tasks[NUM_SCREENS];
// TaskHandle_t epdTaskHandle = NULL;
#define UPDATE_QUEUE_SIZE 14
QueueHandle_t updateQueue;
// SemaphoreHandle_t epdUpdateSemaphore[NUM_SCREENS];
int fgColor = GxEPD_WHITE;
int bgColor = GxEPD_BLACK;
struct FontFamily {
GFXfont* big;
GFXfont* medium;
GFXfont* small;
};
FontFamily antonioFonts = {nullptr, nullptr, nullptr};
FontFamily oswaldFonts = {nullptr, nullptr, nullptr};
const GFXfont *FONT_SMALL;
const GFXfont *FONT_BIG;
const GFXfont *FONT_MEDIUM;
const GFXfont *FONT_SATSYMBOL;
std::mutex epdUpdateMutex;
std::mutex epdMutex[NUM_SCREENS];
uint8_t qrcode[800];
#ifdef IS_BTCLOCK_V8
#define EPD_TASK_STACK_SIZE 4096
#else
#define EPD_TASK_STACK_SIZE 2048
#endif
#define BUSY_TIMEOUT_COUNT 200
#define BUSY_RETRY_DELAY pdMS_TO_TICKS(10)
void forceFullRefresh()
EPDManager::EPDManager()
: currentContent{}
, content{}
, lastFullRefresh{}
, tasks{}
, updateQueue{nullptr}
, antonioFonts{nullptr, nullptr, nullptr}
, oswaldFonts{nullptr, nullptr, nullptr}
, fontSmall{nullptr}
, fontBig{nullptr}
, fontMedium{nullptr}
, fontSatsymbol{nullptr}
, bgColor{GxEPD_BLACK}
, fgColor{GxEPD_WHITE}
, displays{
#ifdef IS_BTCLOCK_V8
EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET[0], &EPD_BUSY[0]),
EPD_CLASS(&EPD_CS[1], &EPD_DC, &EPD_RESET[1], &EPD_BUSY[1]),
EPD_CLASS(&EPD_CS[2], &EPD_DC, &EPD_RESET[2], &EPD_BUSY[2]),
EPD_CLASS(&EPD_CS[3], &EPD_DC, &EPD_RESET[3], &EPD_BUSY[3]),
EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET[4], &EPD_BUSY[4]),
EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET[5], &EPD_BUSY[5]),
EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET[6], &EPD_BUSY[6]),
EPD_CLASS(&EPD_CS[7], &EPD_DC, &EPD_RESET[7], &EPD_BUSY[7])
#else
EPD_CLASS(&EPD_CS[0], &EPD_DC, &EPD_RESET[0], &EPD_BUSY[0]),
EPD_CLASS(&EPD_CS[1], &EPD_DC, &EPD_RESET[1], &EPD_BUSY[1]),
EPD_CLASS(&EPD_CS[2], &EPD_DC, &EPD_RESET[2], &EPD_BUSY[2]),
EPD_CLASS(&EPD_CS[3], &EPD_DC, &EPD_RESET[3], &EPD_BUSY[3]),
EPD_CLASS(&EPD_CS[4], &EPD_DC, &EPD_RESET[4], &EPD_BUSY[4]),
EPD_CLASS(&EPD_CS[5], &EPD_DC, &EPD_RESET[5], &EPD_BUSY[5]),
EPD_CLASS(&EPD_CS[6], &EPD_DC, &EPD_RESET[6], &EPD_BUSY[6])
#endif
}
{
for (uint i = 0; i < NUM_SCREENS; i++)
{
lastFullRefresh[i] = NULL;
}
}
GFXfont font90;
void loadFonts(const String& fontName) {
if (fontName == FontNames::ANTONIO) {
// Load Antonio fonts
antonioFonts.big = FontLoader::loadCompressedFont(Antonio_SemiBold90pt7b_Properties);
antonioFonts.medium = FontLoader::loadCompressedFont(Antonio_SemiBold40pt7b_Properties);
antonioFonts.small = FontLoader::loadCompressedFont(Antonio_SemiBold20pt7b_Properties);
FONT_BIG = antonioFonts.big;
FONT_MEDIUM = antonioFonts.medium;
FONT_SMALL = antonioFonts.small;
} else if (fontName == FontNames::OSWALD) {
// Load Oswald fonts
oswaldFonts.big = FontLoader::loadCompressedFont(Oswald_Medium80pt7b_Properties);
oswaldFonts.medium = FontLoader::loadCompressedFont(Oswald_Medium30pt7b_Properties);
oswaldFonts.small = FontLoader::loadCompressedFont(Oswald_Medium20pt7b_Properties);
FONT_BIG = oswaldFonts.big;
FONT_MEDIUM = oswaldFonts.medium;
FONT_SMALL = oswaldFonts.small;
EPDManager::~EPDManager() {
// Clean up tasks
for (auto& task : tasks) {
if (task != nullptr) {
vTaskDelete(task);
}
}
FONT_SATSYMBOL = FontLoader::loadCompressedFont(Satoshi_Symbol90pt7b_Properties);
// Clean up queue
if (updateQueue != nullptr) {
vQueueDelete(updateQueue);
}
// Clean up fonts
delete antonioFonts.big;
delete antonioFonts.medium;
delete antonioFonts.small;
delete oswaldFonts.big;
delete oswaldFonts.medium;
delete oswaldFonts.small;
}
void setupDisplays() {
void EPDManager::initialize() {
// Load fonts based on preference
String fontName = preferences.getString("fontName", DEFAULT_FONT_NAME);
loadFonts(fontName);
// Initialize displays
std::lock_guard<std::mutex> lockMcp(mcpMutex);
for (uint i = 0; i < NUM_SCREENS; i++) {
displays[i].init(0, true, 30);
for (auto& display : displays) {
display.init(0, true, 30);
}
// Create update queue and task
updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem));
xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE * 2, NULL, 11, NULL);
xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE * 2, nullptr, 11, nullptr);
// Create display update tasks
for (uint i = 0; i < NUM_SCREENS; i++) {
int *taskParam = new int;
*taskParam = i;
xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), EPD_TASK_STACK_SIZE, taskParam, 11, &tasks[i]);
for (size_t i = 0; i < NUM_SCREENS; i++) {
auto* taskParam = new int(i);
xTaskCreate(updateDisplayTask, ("EpdUpd" + String(i)).c_str(), EPD_TASK_STACK_SIZE,
taskParam, 11, &tasks[i]);
}
// Check for storage mode (prevents burn-in)
if (mcp1.read1(0) == LOW) {
setFgColor(GxEPD_BLACK);
setBgColor(GxEPD_WHITE);
epdContent.fill("");
setForegroundColor(GxEPD_BLACK);
setBackgroundColor(GxEPD_WHITE);
content.fill("");
} else {
// Initialize with custom text or default
String customText = preferences.getString("displayText", DEFAULT_BOOT_TEXT);
@ -222,238 +151,128 @@ void setupDisplays() {
newContent[i] = String(customText[i]);
}
epdContent = newContent;
content = newContent;
}
setEpdContent(epdContent);
setContent(content);
}
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent)
{
setEpdContent(newEpdContent, false);
}
void EPDManager::loadFonts(const String& fontName) {
if (fontName == FontNames::ANTONIO) {
// Load Antonio fonts
antonioFonts.big = FontLoader::loadCompressedFont(Antonio_SemiBold90pt7b_Properties);
antonioFonts.medium = FontLoader::loadCompressedFont(Antonio_SemiBold40pt7b_Properties);
antonioFonts.small = FontLoader::loadCompressedFont(Antonio_SemiBold20pt7b_Properties);
void setEpdContent(std::array<std::string, NUM_SCREENS> newEpdContent)
{
std::array<String, NUM_SCREENS> conv;
fontBig = antonioFonts.big;
fontMedium = antonioFonts.medium;
fontSmall = antonioFonts.small;
} else if (fontName == FontNames::OSWALD) {
// Load Oswald fonts
oswaldFonts.big = FontLoader::loadCompressedFont(Oswald_Medium80pt7b_Properties);
oswaldFonts.medium = FontLoader::loadCompressedFont(Oswald_Medium30pt7b_Properties);
oswaldFonts.small = FontLoader::loadCompressedFont(Oswald_Medium20pt7b_Properties);
for (size_t i = 0; i < newEpdContent.size(); ++i)
{
conv[i] = String(newEpdContent[i].c_str());
fontBig = oswaldFonts.big;
fontMedium = oswaldFonts.medium;
fontSmall = oswaldFonts.small;
}
return setEpdContent(conv);
fontSatsymbol = FontLoader::loadCompressedFont(Satoshi_Symbol90pt7b_Properties);
}
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent,
bool forceUpdate)
{
std::lock_guard<std::mutex> lock(epdUpdateMutex);
void EPDManager::forceFullRefresh() {
std::fill(lastFullRefresh.begin(), lastFullRefresh.end(), 0);
}
void EPDManager::setContent(const std::array<String, NUM_SCREENS>& newContent, bool forceUpdate) {
std::lock_guard<std::mutex> lock(updateMutex);
waitUntilNoneBusy();
for (uint i = 0; i < NUM_SCREENS; i++)
{
if (newEpdContent[i].compareTo(currentEpdContent[i]) != 0 || forceUpdate)
{
epdContent[i] = newEpdContent[i];
UpdateDisplayTaskItem dispUpdate = {i};
for (size_t i = 0; i < NUM_SCREENS; i++) {
if (newContent[i].compareTo(currentContent[i]) != 0 || forceUpdate) {
content[i] = newContent[i];
UpdateDisplayTaskItem dispUpdate{static_cast<char>(i)};
xQueueSend(updateQueue, &dispUpdate, portMAX_DELAY);
}
}
}
void prepareDisplayUpdateTask(void *pvParameters)
{
UpdateDisplayTaskItem receivedItem;
while (1)
{
// Wait for a work item to be available in the queue
if (xQueueReceive(updateQueue, &receivedItem, portMAX_DELAY))
{
uint epdIndex = receivedItem.dispNum;
std::lock_guard<std::mutex> lock(epdMutex[epdIndex]);
// displays[epdIndex].init(0, false); // Little longer reset duration
// because of MCP
bool updatePartial = true;
if (epdContent[epdIndex].length() > 1 && strstr(epdContent[epdIndex].c_str(), "/") != NULL)
{
String top = epdContent[epdIndex].substring(
0, epdContent[epdIndex].indexOf("/"));
String bottom = epdContent[epdIndex].substring(
epdContent[epdIndex].indexOf("/") + 1);
splitText(epdIndex, top, bottom, updatePartial);
}
else if (epdContent[epdIndex].startsWith(F("qr")))
{
renderQr(epdIndex, epdContent[epdIndex], updatePartial);
}
else if (epdContent[epdIndex].startsWith(F("mdi")))
{
bool updated = renderIcon(epdIndex, epdContent[epdIndex], updatePartial);
if (!updated)
{
continue;
}
}
else if (epdContent[epdIndex].length() > 5)
{
renderText(epdIndex, epdContent[epdIndex], updatePartial);
}
else
{
if (epdContent[epdIndex].length() == 2)
{
showChars(epdIndex, epdContent[epdIndex], updatePartial, FONT_BIG);
}
else if (epdContent[epdIndex].length() > 1 && epdContent[epdIndex].indexOf(".") == -1)
{
if (epdContent[epdIndex].equals("STS"))
{
showDigit(epdIndex, 'S', updatePartial,
FONT_SATSYMBOL);
}
else
{
showChars(epdIndex, epdContent[epdIndex], updatePartial,
FONT_MEDIUM);
}
}
else
{
showDigit(epdIndex, epdContent[epdIndex].c_str()[0], updatePartial,
FONT_BIG);
}
}
xTaskNotifyGive(tasks[epdIndex]);
}
void EPDManager::setContent(const std::array<std::string, NUM_SCREENS>& newContent) {
std::array<String, NUM_SCREENS> conv;
for (size_t i = 0; i < newContent.size(); ++i) {
conv[i] = String(newContent[i].c_str());
}
setContent(conv);
}
extern "C" void updateDisplay(void *pvParameters) noexcept
{
const int epdIndex = *(int *)pvParameters;
delete (int *)pvParameters;
std::array<String, NUM_SCREENS> EPDManager::getCurrentContent() const {
return currentContent;
}
for (;;)
{
// Wait for the task notification
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
std::lock_guard<std::mutex> lock(epdMutex[epdIndex]);
{
std::lock_guard<std::mutex> lockMcp(mcpMutex);
displays[epdIndex].init(0, false, 40);
}
uint count = 0;
while (EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10)
{
vTaskDelay(pdMS_TO_TICKS(100));
void EPDManager::waitUntilNoneBusy() {
for (size_t i = 0; i < NUM_SCREENS; i++) {
uint32_t count = 0;
while (EPD_BUSY[i].digitalRead()) {
count++;
}
bool updatePartial = true;
// Full Refresh every x minutes
if (!lastFullRefresh[epdIndex] ||
(millis() - lastFullRefresh[epdIndex]) >
(preferences.getUInt("fullRefreshMin",
DEFAULT_MINUTES_FULL_REFRESH) *
60 * 1000))
{
updatePartial = false;
}
char tries = 0;
while (tries < 3)
{
if (displays[epdIndex].displayWithReturn(updatePartial))
{
displays[epdIndex].powerOff();
currentEpdContent[epdIndex] = epdContent[epdIndex];
if (!updatePartial)
lastFullRefresh[epdIndex] = millis();
if (eventSourceTaskHandle != NULL)
xTaskNotifyGive(eventSourceTaskHandle);
vTaskDelay(BUSY_RETRY_DELAY);
if (count == BUSY_TIMEOUT_COUNT) {
vTaskDelay(pdMS_TO_TICKS(100));
} else if (count > BUSY_TIMEOUT_COUNT + 5) {
log_e("Display %d busy timeout", i);
break;
}
vTaskDelay(pdMS_TO_TICKS(100));
tries++;
}
}
}
void splitText(const uint dispNum, const String &top, const String &bottom,
bool partial)
{
if (preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0)
{
void EPDManager::setupDisplay(uint dispNum, const GFXfont* font) {
displays[dispNum].setRotation(2);
displays[dispNum].setFont(font);
displays[dispNum].setTextColor(fgColor);
displays[dispNum].fillScreen(bgColor);
}
void EPDManager::splitText(uint dispNum, const String& top, const String& bottom, bool partial) {
if (preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) {
displays[dispNum].setRotation(1);
}
else
{
} else {
displays[dispNum].setRotation(2);
}
displays[dispNum].setFont(FONT_SMALL);
displays[dispNum].setTextColor(getFgColor());
displays[dispNum].setFont(fontSmall);
displays[dispNum].setTextColor(fgColor);
// Top text
int16_t ttbx, ttby;
uint16_t ttbw, ttbh;
displays[dispNum].getTextBounds(top, 0, 0, &ttbx, &ttby, &ttbw, &ttbh);
uint16_t tx = ((displays[dispNum].width() - ttbw) / 2) - ttbx;
uint16_t ty =
((displays[dispNum].height() - ttbh) / 2) - ttby - ttbh / 2 - 12;
uint16_t ty = ((displays[dispNum].height() - ttbh) / 2) - ttby - ttbh / 2 - 12;
// Bottom text
int16_t tbbx, tbby;
uint16_t tbbw, tbbh;
displays[dispNum].getTextBounds(bottom, 0, 0, &tbbx, &tbby, &tbbw, &tbbh);
uint16_t bx = ((displays[dispNum].width() - tbbw) / 2) - tbbx;
uint16_t by =
((displays[dispNum].height() - tbbh) / 2) - tbby + tbbh / 2 + 12;
uint16_t by = ((displays[dispNum].height() - tbbh) / 2) - tbby + tbbh / 2 + 12;
// Make separator as wide as the shortest text.
uint16_t lineWidth, lineX;
if (tbbw < ttbh)
lineWidth = tbbw;
else
lineWidth = ttbw;
lineX = round((displays[dispNum].width() - lineWidth) / 2);
// Make separator as wide as the shortest text
uint16_t lineWidth = (tbbw < ttbh) ? tbbw : ttbw;
uint16_t lineX = round((displays[dispNum].width() - lineWidth) / 2);
displays[dispNum].fillScreen(getBgColor());
displays[dispNum].fillScreen(bgColor);
displays[dispNum].setCursor(tx, ty);
displays[dispNum].print(top);
displays[dispNum].fillRoundRect(lineX, displays[dispNum].height() / 2 - 3,
lineWidth, 6, 3, getFgColor());
lineWidth, 6, 3, fgColor);
displays[dispNum].setCursor(bx, by);
displays[dispNum].print(bottom);
}
// Consolidate common display setup code into a helper function
void setupDisplay(const uint dispNum, const GFXfont *font)
{
displays[dispNum].setRotation(2);
displays[dispNum].setFont(font);
displays[dispNum].setTextColor(getFgColor());
displays[dispNum].fillScreen(getBgColor());
}
void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font)
{
void EPDManager::showDigit(uint dispNum, char chr, bool partial, const GFXfont* font) {
String str(chr);
if (chr == '.')
{
if (chr == '.') {
str = "!";
}
@ -469,56 +288,34 @@ void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font)
displays[dispNum].setCursor(x, y);
displays[dispNum].print(str);
if (chr == '.')
{
if (chr == '.') {
displays[dispNum].fillRect(0, 0, displays[dispNum].width(),
round(displays[dispNum].height() * 0.67), getBgColor());
round(displays[dispNum].height() * 0.67), bgColor);
}
}
int16_t calculateDescent(const GFXfont *font)
{
int16_t maxDescent = 0;
for (uint16_t i = font->first; i <= font->last; i++)
{
GFXglyph *glyph = &font->glyph[i - font->first];
int16_t descent = glyph->yOffset;
if (descent > maxDescent)
{
maxDescent = descent;
}
}
return maxDescent;
}
void showChars(const uint dispNum, const String &chars, bool partial,
const GFXfont *font)
{
void EPDManager::showChars(uint dispNum, const String& chars, bool partial, const GFXfont* font) {
setupDisplay(dispNum, font);
int16_t tbx, tby;
uint16_t tbw, tbh;
displays[dispNum].getTextBounds(chars, 0, 0, &tbx, &tby, &tbw, &tbh);
// center the bounding box by transposition of the origin:
// Center the bounding box by transposition of the origin
uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx;
uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby;
for (int i = 0; i < chars.length(); i++)
{
for (size_t i = 0; i < chars.length(); i++) {
char c = chars[i];
if (c == '.' || c == ',')
{
if (c == '.' || c == ',') {
// For the dot, calculate its specific descent
GFXglyph *dotGlyph = &font->glyph[c - font->first];
GFXglyph* dotGlyph = &font->glyph[c - font->first];
int16_t dotDescent = dotGlyph->yOffset;
// Draw the dot with adjusted y-position
displays[dispNum].setCursor(x, y + dotDescent + dotGlyph->height + 8);
displays[dispNum].print(c);
}
else
{
} else {
// For other characters, use the original y-position
displays[dispNum].setCursor(x, y);
displays[dispNum].print(c);
@ -529,20 +326,47 @@ void showChars(const uint dispNum, const String &chars, bool partial,
}
}
int getBgColor() { return bgColor; }
bool EPDManager::renderIcon(uint dispNum, const String& text, bool partial) {
displays[dispNum].setRotation(2);
displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(),
displays[dispNum].height());
displays[dispNum].fillScreen(bgColor);
displays[dispNum].setTextColor(fgColor);
int getFgColor() { return fgColor; }
uint iconIndex = 0;
uint width = 122;
uint height = 122;
void setBgColor(int color) { bgColor = color; }
if (text.endsWith("rocket")) {
iconIndex = 1;
} else if (text.endsWith("lnbolt")) {
iconIndex = 2;
} else if (text.endsWith("bitaxe")) {
width = 88;
height = 220;
iconIndex = 3;
} else if (text.endsWith("miningpool")) {
LogoData logo = MiningPoolStatsFetch::getInstance().getLogo();
if (logo.size == 0) {
Serial.println(F("No logo found"));
return false;
}
void setFgColor(int color) { fgColor = color; }
int x_offset = (displays[dispNum].width() - logo.width) / 2;
int y_offset = (displays[dispNum].height() - logo.height) / 2;
displays[dispNum].drawInvertedBitmap(x_offset, y_offset, logo.data,
logo.width, logo.height, fgColor);
return true;
}
std::array<String, NUM_SCREENS> getCurrentEpdContent()
{
return currentEpdContent;
int x_offset = (displays[dispNum].width() - width) / 2;
int y_offset = (displays[dispNum].height() - height) / 2;
displays[dispNum].drawInvertedBitmap(x_offset, y_offset, epd_icons_allArray[iconIndex],
width, height, fgColor);
return true;
}
void renderText(const uint dispNum, const String &text, bool partial)
{
void EPDManager::renderText(uint dispNum, const String& text, bool partial) {
displays[dispNum].setRotation(2);
displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(),
displays[dispNum].height());
@ -552,104 +376,45 @@ void renderText(const uint dispNum, const String &text, bool partial)
std::stringstream ss;
ss.str(text.c_str());
std::string line;
while (std::getline(ss, line, '\n'))
{
if (line.rfind("*", 0) == 0)
{
while (std::getline(ss, line, '\n')) {
if (line.rfind("*", 0) == 0) {
line.erase(std::remove(line.begin(), line.end(), '*'), line.end());
displays[dispNum].setFont(&FreeSansBold9pt7b);
displays[dispNum].println(line.c_str());
}
else
{
} else {
displays[dispNum].setFont(&FreeSans9pt7b);
}
displays[dispNum].println(line.c_str());
}
}
}
bool renderIcon(const uint dispNum, const String &text, bool partial)
{
displays[dispNum].setRotation(2);
displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(),
displays[dispNum].height());
displays[dispNum].fillScreen(getBgColor());
displays[dispNum].setTextColor(getFgColor());
uint iconIndex = 0;
uint width = 122;
uint height = 122;
if (text.endsWith("rocket"))
{
iconIndex = 1;
}
else if (text.endsWith("lnbolt"))
{
iconIndex = 2;
}
else if (text.endsWith("bitaxe"))
{
width = 88;
height = 220;
iconIndex = 3;
}
else if (text.endsWith("miningpool"))
{
LogoData logo = getMiningPoolLogo();
if (logo.size == 0)
{
Serial.println(F("No logo found"));
return false;
}
int x_offset = (displays[dispNum].width() - logo.width) / 2;
int y_offset = (displays[dispNum].height() - logo.height) / 2;
// Close the file
displays[dispNum].drawInvertedBitmap(x_offset, y_offset, logo.data, logo.width, logo.height, getFgColor());
return true;
}
int x_offset = (displays[dispNum].width() - width) / 2;
int y_offset = (displays[dispNum].height() - height) / 2;
displays[dispNum].drawInvertedBitmap(x_offset, y_offset, epd_icons_allArray[iconIndex], width, height, getFgColor());
return true;
// displays[dispNum].drawInvertedBitmap(0,0, getOceanIcon(), 122, 250, getFgColor());
}
void renderQr(const uint dispNum, const String &text, bool partial)
{
void EPDManager::renderQr(uint dispNum, const String& text, bool partial) {
#ifdef USE_QR
// Dynamically allocate QR buffer
uint8_t* qrcode = (uint8_t*)malloc(qrcodegen_BUFFER_LEN_MAX);
if (!qrcode) {
log_e("Failed to allocate QR buffer");
return;
}
uint8_t tempBuffer[800];
bool ok = qrcodegen_encodeText(
text.substring(2).c_str(), tempBuffer, qrcode, qrcodegen_Ecc_LOW,
qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true);
if (ok) {
const int size = qrcodegen_getSize(qrcode);
const int padding = floor(float(displays[dispNum].width() - (size * 4)) / 2);
const int paddingY =
floor(float(displays[dispNum].height() - (size * 4)) / 2);
displays[dispNum].setRotation(2);
const int paddingY = floor(float(displays[dispNum].height() - (size * 4)) / 2);
displays[dispNum].setRotation(2);
displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(),
displays[dispNum].height());
displays[dispNum].fillScreen(GxEPD_WHITE);
const int border = 0;
for (int y = -border; y < size * 4 + border; y++)
{
for (int x = -border; x < size * 4 + border; x++)
{
for (int y = 0; y < size * 4; y++) {
for (int x = 0; x < size * 4; x++) {
displays[dispNum].drawPixel(
padding + x, paddingY + y,
qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4))
@ -657,28 +422,116 @@ void renderQr(const uint dispNum, const String &text, bool partial)
: GxEPD_WHITE);
}
}
}
free(qrcode);
#endif
}
void waitUntilNoneBusy()
{
for (int i = 0; i < NUM_SCREENS; i++)
{
uint count = 0;
while (EPD_BUSY[i].digitalRead())
{
count++;
vTaskDelay(BUSY_RETRY_DELAY);
if (count == BUSY_TIMEOUT_COUNT)
{
vTaskDelay(pdMS_TO_TICKS(100));
int16_t EPDManager::calculateDescent(const GFXfont* font) {
int16_t maxDescent = 0;
for (uint16_t i = font->first; i <= font->last; i++) {
GFXglyph* glyph = &font->glyph[i - font->first];
int16_t descent = glyph->yOffset;
if (descent > maxDescent) {
maxDescent = descent;
}
else if (count > BUSY_TIMEOUT_COUNT + 5)
}
return maxDescent;
}
void EPDManager::updateDisplayTask(void* pvParameters) noexcept {
auto& instance = EPDManager::getInstance();
const int epdIndex = *(int*)pvParameters;
delete (int*)pvParameters;
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
std::lock_guard<std::mutex> lock(instance.displayMutexes[epdIndex]);
{
log_e("Display %d busy timeout", i);
std::lock_guard<std::mutex> lockMcp(mcpMutex);
instance.displays[epdIndex].init(0, false, 40);
}
uint32_t count = 0;
while (instance.EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) {
vTaskDelay(pdMS_TO_TICKS(100));
count++;
}
bool updatePartial = true;
if (!instance.lastFullRefresh[epdIndex] ||
(millis() - instance.lastFullRefresh[epdIndex]) >
(preferences.getUInt("fullRefreshMin", DEFAULT_MINUTES_FULL_REFRESH) * 60 * 1000)) {
updatePartial = false;
}
char tries = 0;
while (tries < 3) {
if (instance.displays[epdIndex].displayWithReturn(updatePartial)) {
instance.displays[epdIndex].powerOff();
instance.currentContent[epdIndex] = instance.content[epdIndex];
if (!updatePartial) {
instance.lastFullRefresh[epdIndex] = millis();
}
if (eventSourceTaskHandle != nullptr) {
xTaskNotifyGive(eventSourceTaskHandle);
}
break;
}
vTaskDelay(pdMS_TO_TICKS(100));
tries++;
}
}
}
void EPDManager::prepareDisplayUpdateTask(void* pvParameters) {
auto& instance = EPDManager::getInstance();
UpdateDisplayTaskItem receivedItem;
for (;;) {
if (xQueueReceive(instance.updateQueue, &receivedItem, portMAX_DELAY)) {
uint epdIndex = receivedItem.dispNum;
std::lock_guard<std::mutex> lock(instance.displayMutexes[epdIndex]);
bool updatePartial = true;
if (instance.content[epdIndex].length() > 1 &&
strstr(instance.content[epdIndex].c_str(), "/") != nullptr) {
String top = instance.content[epdIndex].substring(
0, instance.content[epdIndex].indexOf("/"));
String bottom = instance.content[epdIndex].substring(
instance.content[epdIndex].indexOf("/") + 1);
instance.splitText(epdIndex, top, bottom, updatePartial);
} else if (instance.content[epdIndex].startsWith(F("qr"))) {
instance.renderQr(epdIndex, instance.content[epdIndex], updatePartial);
} else if (instance.content[epdIndex].startsWith(F("mdi"))) {
if (!instance.renderIcon(epdIndex, instance.content[epdIndex], updatePartial)) {
continue;
}
} else if (instance.content[epdIndex].length() > 5) {
instance.renderText(epdIndex, instance.content[epdIndex], updatePartial);
} else {
if (instance.content[epdIndex].length() == 2) {
instance.showChars(epdIndex, instance.content[epdIndex], updatePartial, instance.fontBig);
} else if (instance.content[epdIndex].length() > 1 &&
instance.content[epdIndex].indexOf(".") == -1) {
if (instance.content[epdIndex].equals("STS")) {
instance.showDigit(epdIndex, 'S', updatePartial, instance.fontSatsymbol);
} else {
instance.showChars(epdIndex, instance.content[epdIndex], updatePartial,
instance.fontMedium);
}
} else {
instance.showDigit(epdIndex, instance.content[epdIndex].c_str()[0],
updatePartial, instance.fontBig);
}
}
xTaskNotifyGive(instance.tasks[epdIndex]);
}
}
}

View file

@ -9,6 +9,8 @@
#include <mutex>
#include <native_pin.hpp>
#include <regex>
#include <array>
#include <memory>
#include "fonts/fonts.hpp"
#include "lib/config.hpp"
@ -32,39 +34,102 @@
#include "qrcodegen.h"
#endif
typedef struct {
struct UpdateDisplayTaskItem {
char dispNum;
} UpdateDisplayTaskItem;
};
void forceFullRefresh();
void setupDisplays();
void loadFonts(const String& fontName);
struct FontFamily {
GFXfont* big;
GFXfont* medium;
GFXfont* small;
};
void splitText(const uint dispNum, const String &top, const String &bottom,
bool partial);
class EPDManager {
public:
static EPDManager& getInstance();
void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font);
void showChars(const uint dispNum, const String &chars, bool partial,
const GFXfont *font);
// Delete copy constructor and assignment operator
EPDManager(const EPDManager&) = delete;
EPDManager& operator=(const EPDManager&) = delete;
extern "C" void updateDisplay(void *pvParameters) noexcept;
void updateDisplayAlt(int epdIndex);
void prepareDisplayUpdateTask(void *pvParameters);
void initialize();
void forceFullRefresh();
void loadFonts(const String& fontName);
void setContent(const std::array<String, NUM_SCREENS>& newContent, bool forceUpdate = false);
void setContent(const std::array<std::string, NUM_SCREENS>& newContent);
std::array<String, NUM_SCREENS> getCurrentContent() const;
int getBgColor();
int getFgColor();
void setBgColor(int color);
void setFgColor(int color);
int getBackgroundColor() const { return bgColor; }
int getForegroundColor() const { return fgColor; }
void setBackgroundColor(int color) { bgColor = color; }
void setForegroundColor(int color) { fgColor = color; }
void waitUntilNoneBusy();
bool renderIcon(const uint dispNum, const String &text, bool partial);
void renderText(const uint dispNum, const String &text, bool partial);
void renderQr(const uint dispNum, const String &text, bool partial);
private:
EPDManager(); // Private constructor for singleton
~EPDManager(); // Private destructor
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent,
bool forceUpdate);
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent);
void setupDisplay(uint dispNum, const GFXfont* font);
void splitText(uint dispNum, const String& top, const String& bottom, bool partial);
void showDigit(uint dispNum, char chr, bool partial, const GFXfont* font);
void showChars(uint dispNum, const String& chars, bool partial, const GFXfont* font);
bool renderIcon(uint dispNum, const String& text, bool partial);
void renderText(uint dispNum, const String& text, bool partial);
void renderQr(uint dispNum, const String& text, bool partial);
int16_t calculateDescent(const GFXfont* font);
void setEpdContent(std::array<std::string, NUM_SCREENS> newEpdContent);
static void updateDisplayTask(void* pvParameters) noexcept;
static void prepareDisplayUpdateTask(void* pvParameters);
std::array<String, NUM_SCREENS> getCurrentEpdContent();
void waitUntilNoneBusy();
// Member variables
std::array<String, NUM_SCREENS> currentContent;
std::array<String, NUM_SCREENS> content;
std::array<uint32_t, NUM_SCREENS> lastFullRefresh;
std::array<TaskHandle_t, NUM_SCREENS> tasks;
QueueHandle_t updateQueue;
FontFamily antonioFonts;
FontFamily oswaldFonts;
const GFXfont* fontSmall;
const GFXfont* fontBig;
const GFXfont* fontMedium;
const GFXfont* fontSatsymbol;
int bgColor;
int fgColor;
std::mutex updateMutex;
std::array<std::mutex, NUM_SCREENS> displayMutexes;
// Pin configurations based on board version
#ifdef IS_BTCLOCK_REV_B
static Native_Pin EPD_DC;
static std::array<Native_Pin, NUM_SCREENS> EPD_CS;
static std::array<Native_Pin, NUM_SCREENS> EPD_BUSY;
static std::array<MCP23X17_Pin, NUM_SCREENS> EPD_RESET;
#elif defined(IS_BTCLOCK_V8)
static Native_Pin EPD_DC;
static std::array<MCP23X17_Pin, NUM_SCREENS> EPD_BUSY;
static std::array<MCP23X17_Pin, NUM_SCREENS> EPD_CS;
static std::array<MCP23X17_Pin, NUM_SCREENS> EPD_RESET;
#else
static Native_Pin EPD_DC;
static std::array<Native_Pin, NUM_SCREENS> EPD_CS;
static std::array<Native_Pin, NUM_SCREENS> EPD_BUSY;
static std::array<MCP23X17_Pin, NUM_SCREENS> EPD_RESET;
#endif
// Display array
std::array<GxEPD2_BW<EPD_CLASS, EPD_CLASS::HEIGHT>, NUM_SCREENS> displays;
static constexpr size_t UPDATE_QUEUE_SIZE = 14;
static constexpr uint32_t BUSY_TIMEOUT_COUNT = 200;
static constexpr TickType_t BUSY_RETRY_DELAY = pdMS_TO_TICKS(10);
static constexpr size_t EPD_TASK_STACK_SIZE =
#ifdef IS_BTCLOCK_V8
4096
#else
2048
#endif
;
};

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,7 @@
#include <Arduino.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <memory>
#include "lib/shared.hpp"
#include "lib/webserver.hpp"
@ -15,12 +16,11 @@
#define NEOPIXEL_COUNT 4
#endif
// LED effect constants
const int LED_FLASH_ERROR = 0;
const int LED_FLASH_SUCCESS = 1;
const int LED_FLASH_UPDATE = 2;
const int LED_EFFECT_CONFIGURING = 10;
const int LED_FLASH_BLOCK_NOTIFY = 4;
const int LED_EFFECT_START_TIMER = 5;
const int LED_EFFECT_PAUSE_TIMER = 6;
@ -30,61 +30,15 @@ const int LED_EFFECT_WIFI_CONNECTING = 101;
const int LED_EFFECT_WIFI_CONNECT_ERROR = 102;
const int LED_EFFECT_WIFI_CONNECT_SUCCESS = 103;
const int LED_EFFECT_WIFI_ERASE_SETTINGS = 104;
const int LED_PROGRESS_25 = 200;
const int LED_PROGRESS_50 = 201;
const int LED_PROGRESS_75 = 202;
const int LED_PROGRESS_100 = 203;
const int LED_DATA_PRICE_ERROR = 300;
const int LED_DATA_BLOCK_ERROR = 301;
const int LED_EFFECT_NOSTR_ZAP = 400;
const int LED_FLASH_IDENTIFY = 990;
const int LED_POWER_TEST = 999;
extern TaskHandle_t ledTaskHandle;
extern Adafruit_NeoPixel pixels;
void ledTask(void *pvParameters);
void setupLeds();
void setupLedTask();
void blinkDelay(int d, int times);
void blinkDelayColor(int d, int times, uint r, uint g, uint b);
void blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2);
void clearLeds();
void saveLedState();
void restoreLedState();
QueueHandle_t getLedTaskQueue();
bool queueLedEffect(uint effect);
void setLights(int r, int g, int b);
void setLights(uint32_t color);
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);
void frontlightFadeInAll();
void frontlightFadeOutAll();
void frontlightFadeIn(uint num);
void frontlightFadeOut(uint num);
std::vector<uint16_t> frontlightGetStatus();
void frontlightSetBrightness(uint brightness);
bool frontlightIsOn();
void frontlightFadeInAll(int flDelayTime);
void frontlightFadeInAll(int flDelayTime, bool staggered);
void frontlightFadeOutAll(int flDelayTime);
void frontlightFadeOutAll(int flDelayTime, bool staggered);
void frontlightFadeIn(uint num, int flDelayTime);
void frontlightFadeOut(uint num, int flDelayTime);
#endif
// Do Not Disturb mode settings
struct DNDTimeRange {
@ -94,12 +48,88 @@ struct DNDTimeRange {
uint8_t endMinute;
};
extern bool dndEnabled;
extern bool dndTimeBasedEnabled;
extern DNDTimeRange dndTimeRange;
class LedHandler {
public:
static LedHandler& getInstance();
void setDNDEnabled(bool enabled);
void setDNDTimeBasedEnabled(bool enabled);
void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute);
bool isDNDActive();
bool isTimeInDNDRange(uint8_t hour, uint8_t minute);
// Delete copy constructor and assignment operator
LedHandler(const LedHandler&) = delete;
LedHandler& operator=(const LedHandler&) = delete;
void setup();
void setupTask();
bool queueEffect(uint effect);
void clear();
void setLights(int r, int g, int b);
void setLights(uint32_t color);
void saveLedState();
void restoreLedState();
QueueHandle_t getTaskQueue() const { return ledTaskQueue; }
Adafruit_NeoPixel& getPixels() { return pixels; }
// DND methods
void setDNDEnabled(bool enabled);
void setDNDTimeBasedEnabled(bool enabled);
void setDNDTimeRange(uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute);
bool isDNDActive() const;
bool isTimeInDNDRange(uint8_t hour, uint8_t minute) const;
// DND getters
bool isDNDEnabled() const { return dndEnabled; }
bool isDNDTimeBasedEnabled() const { return dndTimeBasedEnabled; }
uint8_t getDNDStartHour() const { return dndTimeRange.startHour; }
uint8_t getDNDStartMinute() const { return dndTimeRange.startMinute; }
uint8_t getDNDEndHour() const { return dndTimeRange.endHour; }
uint8_t getDNDEndMinute() const { return dndTimeRange.endMinute; }
// Effect methods
void rainbow(int wait);
void theaterChase(uint32_t color, int wait);
void theaterChaseRainbow(int wait);
void lightningStrike();
void blinkDelay(int d, int times);
void blinkDelayColor(int d, int times, uint r, uint g, uint b);
void blinkDelayTwoColor(int d, int times, const uint32_t& c1, const uint32_t& c2);
#ifdef HAS_FRONTLIGHT
void frontlightFlash(int flDelayTime);
void frontlightFadeInAll();
void frontlightFadeOutAll();
void frontlightFadeIn(uint num);
void frontlightFadeOut(uint num);
std::vector<uint16_t> frontlightGetStatus();
void frontlightSetBrightness(uint brightness);
bool frontlightIsOn() const { return frontlightOn; }
void frontlightFadeInAll(int flDelayTime, bool staggered = false);
void frontlightFadeOutAll(int flDelayTime, bool staggered = false);
void frontlightFadeIn(uint num, int flDelayTime);
void frontlightFadeOut(uint num, int flDelayTime);
void initializeFrontlight();
#endif
private:
LedHandler(); // Private constructor for singleton
void loadDNDSettings();
static void ledTask(void* pvParameters);
Adafruit_NeoPixel pixels;
TaskHandle_t ledTaskHandle;
QueueHandle_t ledTaskQueue;
uint ledTaskParams;
// DND members
bool dndEnabled;
bool dndTimeBasedEnabled;
DNDTimeRange dndTimeRange;
#ifdef HAS_FRONTLIGHT
static constexpr uint16_t FL_FADE_STEP = 25;
bool frontlightOn;
bool flInTransition;
#endif
};
// Global accessor function
inline LedHandler& getLedHandler() {
return LedHandler::getInstance();
}

View file

@ -5,6 +5,7 @@ const char* PoolFactory::MINING_POOL_NAME_NODERUNNERS = "noderunners";
const char* PoolFactory::MINING_POOL_NAME_BRAIINS = "braiins";
const char* PoolFactory::MINING_POOL_NAME_SATOSHI_RADIO = "satoshi_radio";
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_CKPOOL = "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_SATOSHI_RADIO, []() { return std::make_unique<SatoshiRadioPool>(); }},
{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_CKPOOL, []() { return std::make_unique<CKPool>(); }},
{MINING_POOL_NAME_EU_CKPOOL, []() { return std::make_unique<EUCKPool>(); }}

View file

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

View file

@ -0,0 +1,11 @@
#include "local_public_pool.hpp"
#include "lib/shared.hpp"
#include "lib/defaults.hpp"
std::string LocalPublicPool::getEndpoint() const {
return preferences.getString("localPoolEndpoint", DEFAULT_LOCAL_POOL_ENDPOINT).c_str();
}
std::string LocalPublicPool::getApiUrl() const {
return "http://" + getEndpoint() + "/api/client/" + poolUser;
}

View file

@ -0,0 +1,11 @@
#pragma once
#include "public_pool.hpp"
class LocalPublicPool : public PublicPool {
public:
std::string getApiUrl() const override;
std::string getDisplayLabel() const override { return "LOCAL/POOL"; }
private:
std::string getEndpoint() const;
};

View file

@ -1,95 +1,98 @@
#include "mining_pool_stats_fetch.hpp"
TaskHandle_t miningPoolStatsFetchTaskHandle;
std::string miningPoolName;
std::string miningPoolStatsHashrate;
int miningPoolStatsDailyEarnings;
std::string getMiningPoolStatsHashRate()
{
return miningPoolStatsHashrate;
void MiningPoolStatsFetch::taskWrapper(void* pvParameters) {
MiningPoolStatsFetch::getInstance().task();
}
int getMiningPoolStatsDailyEarnings()
{
return miningPoolStatsDailyEarnings;
void MiningPoolStatsFetch::downloadLogoTaskWrapper(void* pvParameters) {
MiningPoolStatsFetch::getInstance().downloadLogoTask();
}
void taskMiningPoolStatsFetch(void *pvParameters)
{
std::string MiningPoolStatsFetch::getHashRate() const {
return hashrate;
}
int MiningPoolStatsFetch::getDailyEarnings() const {
return dailyEarnings;
}
MiningPoolInterface* MiningPoolStatsFetch::getPool() {
if (!currentPool) {
std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str();
auto poolInterface = PoolFactory::createPool(poolName);
currentPool = PoolFactory::createPool(poolName);
}
return currentPool.get();
}
const MiningPoolInterface* MiningPoolStatsFetch::getPool() const {
return currentPool.get();
}
LogoData MiningPoolStatsFetch::getLogo() const {
if (const auto* pool = getPool()) {
return pool->getLogo();
}
return LogoData{};
}
void MiningPoolStatsFetch::task() {
std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str();
auto* poolInterface = getPool();
if (!poolInterface) return;
std::string poolUser = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER).c_str();
// Main stats fetching loop
for (;;)
{
for (;;) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
HTTPClient http;
http.setUserAgent(USER_AGENT);
poolInterface->setPoolUser(poolUser);
std::string apiUrl = poolInterface->getApiUrl();
http.begin(apiUrl.c_str());
if (debugLogEnabled())
{
if (debugLogEnabled()) {
Serial.printf("Fetching mining pool stats from %s\r\n", apiUrl.c_str());
}
poolInterface->prepareRequest(http);
int httpCode = http.GET();
if (httpCode == 200)
{
if (httpCode == 200) {
String payload = http.getString();
JsonDocument doc;
deserializeJson(doc, payload);
if (debugLogEnabled())
{
if (debugLogEnabled()) {
Serial.printf("Mining pool stats response: %s\r\n", payload.c_str());
}
PoolStats stats = poolInterface->parseResponse(doc);
hashrate = stats.hashrate;
miningPoolStatsHashrate = stats.hashrate;
if (debugLogEnabled())
{
if (debugLogEnabled()) {
Serial.printf("Mining pool stats parsed hashrate: %s\r\n", stats.hashrate.c_str());
}
if (stats.dailyEarnings)
{
miningPoolStatsDailyEarnings = *stats.dailyEarnings;
}
else
{
miningPoolStatsDailyEarnings = 0; // or any other default value
}
dailyEarnings = stats.dailyEarnings ? *stats.dailyEarnings : 0;
if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS))
{
if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE ||
ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS)) {
WorkItem priceUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0};
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
}
}
else
{
Serial.print(
F("Error retrieving mining pool data. HTTP status code: "));
} else {
Serial.print(F("Error retrieving mining pool data. HTTP status code: "));
Serial.println(httpCode);
}
}
}
void downloadMiningPoolLogoTask(void *pvParameters) {
void MiningPoolStatsFetch::downloadLogoTask() {
std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str();
auto poolInterface = PoolFactory::createPool(poolName);
PoolFactory::downloadPoolLogo(poolName, poolInterface.get());
auto* poolInterface = getPool();
if (!poolInterface) return;
PoolFactory::downloadPoolLogo(poolName, poolInterface);
// If we're on the mining pool stats screen, trigger a display update
if (ScreenHandler::getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) {
@ -97,41 +100,22 @@ void downloadMiningPoolLogoTask(void *pvParameters) {
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
}
xTaskNotifyGive(miningPoolStatsFetchTaskHandle);
xTaskNotifyGive(taskHandle);
vTaskDelete(NULL);
}
void setupMiningPoolStatsFetchTask()
{
xTaskCreate(downloadMiningPoolLogoTask,
void MiningPoolStatsFetch::setup() {
xTaskCreate(downloadLogoTaskWrapper,
"logoDownload",
(6 * 1024),
NULL,
tskIDLE_PRIORITY,
NULL);
xTaskCreate(taskMiningPoolStatsFetch,
xTaskCreate(taskWrapper,
"miningPoolStatsFetch",
(6 * 1024),
NULL,
tskIDLE_PRIORITY,
&miningPoolStatsFetchTaskHandle);
}
std::unique_ptr<MiningPoolInterface>& getMiningPool()
{
static std::unique_ptr<MiningPoolInterface> currentMiningPool;
if (!currentMiningPool) {
std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str();
currentMiningPool = PoolFactory::createPool(poolName);
}
return currentMiningPool;
}
LogoData getMiningPoolLogo()
{
LogoData logo = getMiningPool()->getLogo();
return logo;
&taskHandle);
}

View file

@ -2,18 +2,44 @@
#include <Arduino.h>
#include <HTTPClient.h>
#include "mining_pool/pool_factory.hpp"
#include <utils.hpp>
#include <memory>
#include "lib/config.hpp"
#include "lib/shared.hpp"
#include "lib/mining_pool/mining_pool_interface.hpp"
#include "mining_pool/pool_factory.hpp"
extern TaskHandle_t miningPoolStatsFetchTaskHandle;
class MiningPoolStatsFetch {
public:
static MiningPoolStatsFetch& getInstance() {
static MiningPoolStatsFetch instance;
return instance;
}
void setupMiningPoolStatsFetchTask();
void taskMiningPoolStatsFetch(void *pvParameters);
void setup();
std::string getHashRate() const;
int getDailyEarnings() const;
TaskHandle_t getTaskHandle() const { return taskHandle; }
static void taskWrapper(void* pvParameters);
static void downloadLogoTaskWrapper(void* pvParameters);
std::string getMiningPoolStatsHashRate();
int getMiningPoolStatsDailyEarnings();
// Pool interface methods
MiningPoolInterface* getPool();
const MiningPoolInterface* getPool() const;
LogoData getLogo() const;
std::unique_ptr<MiningPoolInterface>& getMiningPool();
LogoData getMiningPoolLogo();
private:
MiningPoolStatsFetch() = default;
~MiningPoolStatsFetch() = default;
MiningPoolStatsFetch(const MiningPoolStatsFetch&) = delete;
MiningPoolStatsFetch& operator=(const MiningPoolStatsFetch&) = delete;
void task();
void downloadLogoTask();
TaskHandle_t taskHandle = nullptr;
std::string hashrate;
int dailyEarnings = 0;
std::unique_ptr<MiningPoolInterface> currentPool;
};

View file

@ -1,4 +1,5 @@
#include "nostr_notify.hpp"
#include "led_handler.hpp"
std::vector<nostr::NostrPool *> pools;
nostr::Transport *transport;
@ -40,7 +41,7 @@ void setupNostrNotify(bool asDatasource, bool zapNotify)
{relay},
{// First filter
{
{"kinds", {"1"}},
{"kinds", {"12203"}},
{"since", {String(getMinutesAgo(60))}},
{"authors", {pubKey}},
}},
@ -78,8 +79,9 @@ void nostrTask(void *pvParameters)
{
DataSourceType dataSource = getDataSource();
if(dataSource == NOSTR_SOURCE) {
int blockFetch = getBlockFetch();
processNewBlock(blockFetch);
auto& blockNotify = BlockNotify::getInstance();
int blockFetch = blockNotify.fetchLatestBlock();
blockNotify.processNewBlock(blockFetch);
}
while (1)
@ -144,6 +146,7 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even
// Use direct value access instead of multiple comparisons
String typeValue;
uint medianFee = 0;
uint blockHeight = 0;
for (JsonArray tag : tags) {
if (tag.size() != 2) continue;
@ -164,6 +167,11 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even
medianFee = tag[1].as<uint>();
}
break;
case 'b': // blockHeight
if (strcmp(key, "block") == 0) {
blockHeight = tag[1].as<uint>();
}
break;
}
}
@ -171,13 +179,19 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even
if (!typeValue.isEmpty()) {
if (typeValue == "priceUsd") {
processNewPrice(obj["content"].as<uint>(), CURRENCY_USD);
if (blockHeight != 0) {
auto& blockNotify = BlockNotify::getInstance();
blockNotify.processNewBlock(blockHeight);
}
}
else if (typeValue == "blockHeight") {
processNewBlock(obj["content"].as<uint>());
auto& blockNotify = BlockNotify::getInstance();
blockNotify.processNewBlock(obj["content"].as<uint>());
}
if (medianFee != 0) {
processNewBlockFee(medianFee);
auto& blockNotify = BlockNotify::getInstance();
blockNotify.processNewBlockFee(medianFee);
}
}
}
@ -282,11 +296,11 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event)
}
ScreenHandler::setCurrentScreen(SCREEN_CUSTOM);
setEpdContent(textEpdContent);
EPDManager::getInstance().setContent(textEpdContent);
vTaskDelay(pdMS_TO_TICKS(315 * NUM_SCREENS) + pdMS_TO_TICKS(250));
if (preferences.getBool("ledFlashOnZap", DEFAULT_LED_FLASH_ON_ZAP))
{
queueLedEffect(LED_EFFECT_NOSTR_ZAP);
getLedHandler().queueEffect(LED_EFFECT_NOSTR_ZAP);
}
if (timerPeriod > 0)
{
@ -294,3 +308,19 @@ void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event)
timerPeriod * usPerSecond);
}
}
// void onNostrEvent(const String &subId, const nostr::Event &event) {
// // This is the callback that will be called when a new event is received
// if (event.kind == 9735) {
// // Parse the zap amount from the event
// uint16_t amount = parseZapAmount(event);
// if (amount > 0) {
// std::array<std::string, NUM_SCREENS> zapContent = parseZapNotify(amount, true);
// EPDManager::getInstance().setContent(zapContent);
// if (preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD)) {
// getLedHandler().queueEffect(LED_FLASH_BLOCK_NOTIFY);
// }
// }
// }
// }

View file

@ -1,4 +1,5 @@
#include "ota.hpp"
#include "led_handler.hpp"
TaskHandle_t taskOtaHandle = NULL;
bool isOtaUpdating = false;
@ -31,6 +32,9 @@ void setupOTA()
void onOTAProgress(unsigned int progress, unsigned int total)
{
uint percentage = progress / (total / 100);
auto& ledHandler = getLedHandler();
auto& pixels = ledHandler.getPixels();
pixels.fill(pixels.Color(0, 255, 0));
if (percentage < 100)
{
@ -53,10 +57,10 @@ void onOTAProgress(unsigned int progress, unsigned int total)
void onOTAStart()
{
forceFullRefresh();
EPDManager::getInstance().forceFullRefresh();
std::array<String, NUM_SCREENS> epdContent = {"U", "P", "D", "A",
"T", "E", "!"};
setEpdContent(epdContent);
EPDManager::getInstance().setContent(epdContent);
// Stop all timers
esp_timer_stop(screenRotateTimer);
esp_timer_stop(minuteTimer);
@ -70,8 +74,8 @@ void onOTAStart()
ButtonHandler::suspendTask();
// stopWebServer();
stopBlockNotify();
stopPriceNotify();
auto& blockNotify = BlockNotify::getInstance();
blockNotify.stop();
}
void handleOTATask(void *parameter)
@ -84,15 +88,15 @@ void handleOTATask(void *parameter)
{
if (msg.updateType == UPDATE_ALL) {
isOtaUpdating = true;
queueLedEffect(LED_FLASH_UPDATE);
getLedHandler().queueEffect(LED_FLASH_UPDATE);
int resultWebUi = downloadUpdateHandler(UPDATE_WEBUI);
queueLedEffect(LED_FLASH_UPDATE);
getLedHandler().queueEffect(LED_FLASH_UPDATE);
int resultFw = downloadUpdateHandler(UPDATE_FIRMWARE);
if (resultWebUi == 0 && resultFw == 0) {
ESP.restart();
} else {
queueLedEffect(LED_FLASH_ERROR);
getLedHandler().queueEffect(LED_FLASH_ERROR);
vTaskDelay(pdMS_TO_TICKS(3000));
ESP.restart();
}

View file

@ -2,103 +2,64 @@
const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin";
// WebsocketsClient client;
esp_websocket_client_handle_t clientPrice = NULL;
esp_websocket_client_config_t config;
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;
WebSocketsClient priceNotifyWs;
TaskHandle_t priceNotifyTaskHandle;
void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length);
void setupPriceNotify()
{
config = {.uri = wsServerPrice,
.user_agent = USER_AGENT};
config.cert_pem = isrg_root_x1cert;
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);
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);
setupPriceNotifyTask();
}
// 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)
{
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
switch (event_id)
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:
{
case WEBSOCKET_EVENT_CONNECTED:
Serial.println("Connected to " + String(config.uri) + " WebSocket");
Serial.println("Connected to " + String(wsServerPrice));
priceNotifyInit = true;
break;
case WEBSOCKET_EVENT_DATA:
onWebsocketPriceMessage(data);
break;
case WEBSOCKET_EVENT_ERROR:
Serial.println(F("Price WS Connnection error"));
break;
case WEBSOCKET_EVENT_DISCONNECTED:
Serial.println(F("Price WS Connnection Closed"));
break;
}
}
void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data)
{
case WStype_TEXT:
{
JsonDocument doc;
deserializeJson(doc, (char *)payload);
deserializeJson(doc, (char *)event_data->data_ptr);
if (doc.containsKey("bitcoin"))
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)
@ -175,9 +136,7 @@ void setPrice(uint newPrice, char currency)
bool isPriceNotifyConnected()
{
if (clientPrice == NULL)
return false;
return esp_websocket_client_is_connected(clientPrice);
return webSocket.isConnected();
}
bool getPriceNotifyInit()
@ -187,24 +146,30 @@ bool getPriceNotifyInit()
void stopPriceNotify()
{
if (clientPrice == NULL)
return;
esp_websocket_client_close(clientPrice, pdMS_TO_TICKS(5000));
esp_websocket_client_stop(clientPrice);
esp_websocket_client_destroy(clientPrice);
clientPrice = NULL;
webSocket.disconnect();
if (priceNotifyTaskHandle != NULL) {
vTaskDelete(priceNotifyTaskHandle);
priceNotifyTaskHandle = NULL;
}
}
void restartPriceNotify()
{
stopPriceNotify();
if (clientPrice == NULL)
{
setupPriceNotify();
return;
}
// esp_websocket_client_close(clientPrice, pdMS_TO_TICKS(5000));
// esp_websocket_client_stop(clientPrice);
// esp_websocket_client_start(clientPrice);
}
void taskPriceNotify(void *pvParameters)
{
for (;;)
{
webSocket.loop();
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void setupPriceNotifyTask()
{
xTaskCreate(taskPriceNotify, "priceNotify", (6 * 1024), NULL, tskIDLE_PRIORITY,
&priceNotifyTaskHandle);
}

View file

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

View file

@ -203,7 +203,7 @@ void ScreenHandler::showSystemStatusScreen() {
String((int)round(ESP.getFreeHeap() / 1024)) + "/" +
(int)round(ESP.getHeapSize() / 1024);
setCurrentScreen(SCREEN_CUSTOM);
setEpdContent(sysStatusEpdContent);
EPDManager::getInstance().setContent(sysStatusEpdContent);
}
// Keep these as free functions
@ -220,9 +220,9 @@ void workerTask(void *pvParameters) {
currentScreenValue != SCREEN_BITAXE_BESTDIFF) break;
taskEpdContent = (currentScreenValue == SCREEN_BITAXE_HASHRATE) ?
parseBitaxeHashRate(getBitAxeHashRate()) :
parseBitaxeBestDiff(getBitaxeBestDiff());
setEpdContent(taskEpdContent);
parseBitaxeHashRate(BitAxeFetch::getInstance().getHashRate()) :
parseBitaxeBestDiff(BitAxeFetch::getInstance().getBestDiff());
EPDManager::getInstance().setContent(taskEpdContent);
break;
}
@ -231,10 +231,11 @@ void workerTask(void *pvParameters) {
currentScreenValue != SCREEN_MINING_POOL_STATS_EARNINGS) break;
taskEpdContent = (currentScreenValue == SCREEN_MINING_POOL_STATS_HASHRATE) ?
parseMiningPoolStatsHashRate(getMiningPoolStatsHashRate(), *getMiningPool()) :
parseMiningPoolStatsDailyEarnings(getMiningPoolStatsDailyEarnings(),
getMiningPool()->getDailyEarningsLabel(), *getMiningPool());
setEpdContent(taskEpdContent);
parseMiningPoolStatsHashRate(MiningPoolStatsFetch::getInstance().getHashRate(), *MiningPoolStatsFetch::getInstance().getPool()) :
parseMiningPoolStatsDailyEarnings(MiningPoolStatsFetch::getInstance().getDailyEarnings(),
MiningPoolStatsFetch::getInstance().getPool()->getDailyEarningsLabel(),
*MiningPoolStatsFetch::getInstance().getPool());
EPDManager::getInstance().setContent(taskEpdContent);
break;
}
@ -250,31 +251,33 @@ void workerTask(void *pvParameters) {
} else if (currentScreenValue == SCREEN_SATS_PER_CURRENCY) {
taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL));
} else {
taskEpdContent =
parseMarketCap(getBlockHeight(), price, currency,
preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR));
auto& blockNotify = BlockNotify::getInstance();
taskEpdContent = parseMarketCap(blockNotify.getBlockHeight(), price, currency, preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR));
}
setEpdContent(taskEpdContent);
EPDManager::getInstance().setContent(taskEpdContent);
break;
}
case TASK_FEE_UPDATE: {
if (currentScreenValue == SCREEN_BLOCK_FEE_RATE) {
taskEpdContent = parseBlockFees(static_cast<std::uint16_t>(getBlockMedianFee()));
setEpdContent(taskEpdContent);
auto& blockNotify = BlockNotify::getInstance();
taskEpdContent = parseBlockFees(static_cast<std::uint16_t>(blockNotify.getBlockMedianFee()));
EPDManager::getInstance().setContent(taskEpdContent);
}
break;
}
case TASK_BLOCK_UPDATE: {
if (currentScreenValue != SCREEN_HALVING_COUNTDOWN) {
taskEpdContent = parseBlockHeight(getBlockHeight());
auto& blockNotify = BlockNotify::getInstance();
taskEpdContent = parseBlockHeight(blockNotify.getBlockHeight());
} 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 ||
currentScreenValue == SCREEN_BLOCK_HEIGHT) {
setEpdContent(taskEpdContent);
EPDManager::getInstance().setContent(taskEpdContent);
}
break;
}
@ -301,7 +304,7 @@ void workerTask(void *pvParameters) {
for (uint i = 1; i < NUM_SCREENS; i++) {
taskEpdContent[i] = timeString[i];
}
setEpdContent(taskEpdContent);
EPDManager::getInstance().setContent(taskEpdContent);
}
break;
@ -329,8 +332,6 @@ void setupTasks() {
xTaskCreate(taskScreenRotate, "rotateScreen", 4096, NULL, tskIDLE_PRIORITY,
&taskScreenRotateTaskHandle);
waitUntilNoneBusy();
if (findScreenIndexByValue(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)) != -1)
ScreenHandler::setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN));
}

View file

@ -40,39 +40,39 @@
// "MrY=\n"
// "-----END CERTIFICATE-----\n";
const char* isrg_root_x1cert = 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";
// const char* isrg_root_x1cert = 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";
#ifdef TEST_SCREENS
@ -180,3 +180,4 @@ void HttpHelper::end(HTTPClient* http) {
delete http;
}
}

View file

@ -16,6 +16,8 @@
#include <mutex>
#include <utils.hpp>
#include <array>
#include <string>
#include "defaults.hpp"
@ -66,7 +68,7 @@ const int usPerSecond = 1000000;
const int usPerMinute = 60 * usPerSecond;
// 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 ocean_logo_comp[] asm("_binary_ocean_gz_start");
@ -119,3 +121,4 @@ private:
static bool certBundleSet;
static WiFiClient insecureClient;
};

View file

@ -1,4 +1,5 @@
#include "timers.hpp"
#include "led_handler.hpp"
esp_timer_handle_t screenRotateTimer;
esp_timer_handle_t minuteTimer;
@ -49,11 +50,11 @@ void setTimerActive(bool status) {
if (status) {
esp_timer_start_periodic(screenRotateTimer,
getTimerSeconds() * usPerSecond);
queueLedEffect(LED_EFFECT_START_TIMER);
getLedHandler().queueEffect(LED_EFFECT_START_TIMER);
preferences.putBool("timerActive", true);
} else {
esp_timer_stop(screenRotateTimer);
queueLedEffect(LED_EFFECT_PAUSE_TIMER);
getLedHandler().queueEffect(LED_EFFECT_PAUSE_TIMER);
preferences.putBool("timerActive", false);
}
@ -68,12 +69,14 @@ void IRAM_ATTR minuteTimerISR(void *arg) {
WorkItem timeUpdate = {TASK_TIME_UPDATE, 0};
xQueueSendFromISR(workQueue, &timeUpdate, &xHigherPriorityTaskWoken);
if (bitaxeFetchTaskHandle != NULL) {
vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken);
TaskHandle_t bitaxeHandle = BitAxeFetch::getInstance().getTaskHandle();
if (bitaxeHandle != NULL) {
vTaskNotifyGiveFromISR(bitaxeHandle, &xHigherPriorityTaskWoken);
}
if (miningPoolStatsFetchTaskHandle != NULL) {
vTaskNotifyGiveFromISR(miningPoolStatsFetchTaskHandle, &xHigherPriorityTaskWoken);
TaskHandle_t miningPoolHandle = MiningPoolStatsFetch::getInstance().getTaskHandle();
if (miningPoolHandle != NULL) {
vTaskNotifyGiveFromISR(miningPoolHandle, &xHigherPriorityTaskWoken);
}
if (xHigherPriorityTaskWoken == pdTRUE) {

View file

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

View file

@ -1,9 +1,11 @@
#include "webserver.hpp"
#include "lib/led_handler.hpp"
#include "lib/shared.hpp"
static const char* JSON_CONTENT = "application/json";
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"};
@ -28,7 +30,8 @@ TaskHandle_t eventSourceTaskHandle;
void setupWebserver()
{
events.onConnect([](AsyncEventSourceClient *client)
{ client->send("welcome", NULL, millis(), 1000); });
{ client->send("welcome", NULL, millis(), 1000);
});
server.addHandler(&events);
AsyncStaticWebHandler &staticHandler = server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html");
@ -231,6 +234,7 @@ void asyncFirmwareUpdateHandler(AsyncWebServerRequest *request, String filename,
JsonDocument getStatusObject()
{
auto& ledHandler = getLedHandler();
JsonDocument root;
root["currentScreen"] = ScreenHandler::getCurrentScreen();
@ -238,25 +242,22 @@ JsonDocument getStatusObject()
root["timerRunning"] = isTimerActive();
root["isOTAUpdating"] = getIsOTAUpdating();
root["espUptime"] = esp_timer_get_time() / 1000000;
// root["currentPrice"] = getPrice();
// root["currentBlockHeight"] = getBlockHeight();
root["espFreeHeap"] = ESP.getFreeHeap();
root["espHeapSize"] = ESP.getHeapSize();
// root["espFreePsram"] = ESP.getFreePsram();
// root["espPsramSize"] = ESP.getPsramSize();
JsonObject conStatus = root["connectionStatus"].to<JsonObject>();
conStatus["price"] = isPriceNotifyConnected();
conStatus["blocks"] = isBlockNotifyConnected();
auto& blockNotify = BlockNotify::getInstance();
conStatus["blocks"] = blockNotify.isConnected();
conStatus["V2"] = V2Notify::isV2NotifyConnected();
conStatus["nostr"] = nostrConnected();
root["rssi"] = WiFi.RSSI();
root["currency"] = getCurrencyCode(ScreenHandler::getCurrentCurrency());
#ifdef HAS_FRONTLIGHT
std::vector<uint16_t> statuses = frontlightGetStatus();
std::vector<uint16_t> statuses = ledHandler.frontlightGetStatus();
uint16_t arr[NUM_SCREENS];
std::copy(statuses.begin(), statuses.end(), arr);
@ -270,22 +271,24 @@ JsonDocument getStatusObject()
#endif
// Add DND status
root["dnd"]["enabled"] = dndEnabled;
root["dnd"]["timeBasedEnabled"] = dndTimeBasedEnabled;
root["dnd"]["startTime"] = String(dndTimeRange.startHour) + ":" +
(dndTimeRange.startMinute < 10 ? "0" : "") + String(dndTimeRange.startMinute);
root["dnd"]["endTime"] = String(dndTimeRange.endHour) + ":" +
(dndTimeRange.endMinute < 10 ? "0" : "") + String(dndTimeRange.endMinute);
root["dnd"]["active"] = isDNDActive();
root["dnd"]["enabled"] = ledHandler.isDNDEnabled();
root["dnd"]["timeBasedEnabled"] = ledHandler.isDNDTimeBasedEnabled();
root["dnd"]["startTime"] = String(ledHandler.getDNDStartHour()) + ":" +
(ledHandler.getDNDStartMinute() < 10 ? "0" : "") + String(ledHandler.getDNDStartMinute());
root["dnd"]["endTime"] = String(ledHandler.getDNDEndHour()) + ":" +
(ledHandler.getDNDEndMinute() < 10 ? "0" : "") + String(ledHandler.getDNDEndMinute());
root["dnd"]["active"] = ledHandler.isDNDActive();
return root;
}
JsonDocument getLedStatusObject()
{
auto& ledHandler = getLedHandler();
auto& pixels = ledHandler.getPixels();
JsonDocument root;
JsonArray colors = root["data"].to<JsonArray>();
// Adafruit_NeoPixel pix = getPixels();
for (uint i = 0; i < pixels.numPixels(); i++)
{
@ -295,13 +298,7 @@ JsonDocument getLedStatusObject()
uint blue = pixColor & 0xFF;
char hexColor[8];
snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X", red, green, blue);
JsonObject object = colors.add<JsonObject>();
object["red"] = red;
object["green"] = green;
object["blue"] = blue;
object["hex"] = hexColor;
colors.add(hexColor);
}
return root;
@ -310,14 +307,18 @@ JsonDocument getLedStatusObject()
void eventSourceUpdate() {
if (!events.count()) return;
JsonDocument doc = getStatusObject();
doc["leds"] = getLedStatusObject()["data"];
static JsonDocument doc;
doc.clear();
JsonDocument root = getStatusObject();
root["leds"] = getLedStatusObject()["data"];
// Get current EPD content directly as array
std::array<String, NUM_SCREENS> epdContent = getCurrentEpdContent();
std::array<String, NUM_SCREENS> epdContent = EPDManager::getInstance().getCurrentContent();
// Add EPD content arrays
JsonArray data = doc["data"].to<JsonArray>();
JsonArray data = root["data"].to<JsonArray>();
// Copy array elements directly
for(const auto& content : epdContent) {
@ -325,7 +326,7 @@ void eventSourceUpdate() {
}
String buffer;
serializeJson(doc, buffer);
serializeJson(root, buffer);
events.send(buffer.c_str(), "status");
}
@ -341,7 +342,7 @@ void onApiStatus(AsyncWebServerRequest *request)
JsonDocument root = getStatusObject();
// Get current EPD content directly as array
std::array<String, NUM_SCREENS> epdContent = getCurrentEpdContent();
std::array<String, NUM_SCREENS> epdContent = EPDManager::getInstance().getCurrentContent();
// Add EPD content arrays
JsonArray data = root["data"].to<JsonArray>();
@ -383,11 +384,9 @@ void onApiActionTimerRestart(AsyncWebServerRequest *request)
*/
void onApiFullRefresh(AsyncWebServerRequest *request)
{
forceFullRefresh();
std::array<String, NUM_SCREENS> newEpdContent = getCurrentEpdContent();
setEpdContent(newEpdContent, true);
EPDManager::getInstance().forceFullRefresh();
std::array<String, NUM_SCREENS> newEpdContent = EPDManager::getInstance().getCurrentContent();
EPDManager::getInstance().setContent(newEpdContent, true);
request->send(HTTP_OK);
}
@ -434,7 +433,7 @@ void onApiShowText(AsyncWebServerRequest *request)
textEpdContent[i] = t[i];
}
setEpdContent(textEpdContent);
EPDManager::getInstance().setContent(textEpdContent);
}
ScreenHandler::setCurrentScreen(SCREEN_CUSTOM);
request->send(HTTP_OK);
@ -452,7 +451,7 @@ void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json)
i++;
}
setEpdContent(epdContent);
EPDManager::getInstance().setContent(epdContent);
ScreenHandler::setCurrentScreen(SCREEN_CUSTOM);
request->send(HTTP_OK);
@ -480,13 +479,13 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json)
if (inverted) {
preferences.putUInt("fgColor", GxEPD_WHITE);
preferences.putUInt("bgColor", GxEPD_BLACK);
setFgColor(GxEPD_WHITE);
setBgColor(GxEPD_BLACK);
EPDManager::getInstance().setForegroundColor(GxEPD_WHITE);
EPDManager::getInstance().setBackgroundColor(GxEPD_BLACK);
} else {
preferences.putUInt("fgColor", GxEPD_BLACK);
preferences.putUInt("bgColor", GxEPD_WHITE);
setFgColor(GxEPD_BLACK);
setBgColor(GxEPD_WHITE);
EPDManager::getInstance().setForegroundColor(GxEPD_BLACK);
EPDManager::getInstance().setBackgroundColor(GxEPD_WHITE);
}
Serial.printf("Setting invertedColor to %d\r\n", inverted);
settingsChanged = true;
@ -619,26 +618,28 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json)
}
// Handle DND settings
if (settings.containsKey("dnd")) {
if (settings["dnd"].is<JsonObject>()) {
JsonObject dndObj = settings["dnd"];
if (dndObj.containsKey("timeBasedEnabled")) {
setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as<bool>());
auto& ledHandler = getLedHandler();
if (dndObj["timeBasedEnabled"].is<bool>()) {
ledHandler.setDNDTimeBasedEnabled(dndObj["timeBasedEnabled"].as<bool>());
}
if (dndObj.containsKey("startHour") && dndObj.containsKey("startMinute") &&
dndObj.containsKey("endHour") && dndObj.containsKey("endMinute")) {
setDNDTimeRange(
if (dndObj["startHour"].is<uint8_t>() && dndObj["startMinute"].is<uint8_t>() &&
dndObj["endHour"].is<uint8_t>() && dndObj["endMinute"].is<uint8_t>()) {
ledHandler.setDNDTimeRange(
dndObj["startHour"].as<uint8_t>(),
dndObj["startMinute"].as<uint8_t>(),
dndObj["endHour"].as<uint8_t>(),
dndObj["endMinute"].as<uint8_t>()
);
dndObj["endMinute"].as<uint8_t>());
}
}
request->send(HTTP_OK);
if (settingsChanged)
{
queueLedEffect(LED_FLASH_SUCCESS);
auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_FLASH_SUCCESS);
}
}
@ -659,7 +660,8 @@ void onApiRestart(AsyncWebServerRequest *request)
void onApiIdentify(AsyncWebServerRequest *request)
{
queueLedEffect(LED_FLASH_IDENTIFY);
auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_FLASH_IDENTIFY);
request->send(HTTP_OK);
}
@ -682,7 +684,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
JsonDocument root;
root["numScreens"] = NUM_SCREENS;
root["invertedColor"] = preferences.getBool("invertedColor", getFgColor() == GxEPD_WHITE);
root["invertedColor"] = preferences.getBool("invertedColor", EPDManager::getInstance().getForegroundColor() == GxEPD_WHITE);
root["timerSeconds"] = getTimerSeconds();
root["timerRunning"] = isTimerActive();
root["minSecPriceUpd"] = preferences.getUInt(
@ -699,6 +701,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
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)
root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB);
root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY);
@ -797,12 +802,13 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["ceDisableSSL"] = preferences.getBool("ceDisableSSL", DEFAULT_CUSTOM_ENDPOINT_DISABLE_SSL);
// Add DND settings
root["dnd"]["enabled"] = dndEnabled;
root["dnd"]["timeBasedEnabled"] = dndTimeBasedEnabled;
root["dnd"]["startHour"] = dndTimeRange.startHour;
root["dnd"]["startMinute"] = dndTimeRange.startMinute;
root["dnd"]["endHour"] = dndTimeRange.endHour;
root["dnd"]["endMinute"] = dndTimeRange.endMinute;
auto& ledHandler = getLedHandler();
root["dnd"]["enabled"] = ledHandler.isDNDEnabled();
root["dnd"]["timeBasedEnabled"] = ledHandler.isDNDTimeBasedEnabled();
root["dnd"]["startHour"] = ledHandler.getDNDStartHour();
root["dnd"]["startMinute"] = ledHandler.getDNDStartMinute();
root["dnd"]["endHour"] = ledHandler.getDNDEndHour();
root["dnd"]["endMinute"] = ledHandler.getDNDEndMinute();
AsyncResponseStream *response =
request->beginResponseStream(JSON_CONTENT);
@ -819,7 +825,7 @@ bool processEpdColorSettings(AsyncWebServerRequest *request)
const AsyncWebParameter *fgColor = request->getParam("fgColor", true);
uint32_t color = strtol(fgColor->value().c_str(), NULL, 16);
preferences.putUInt("fgColor", color);
setFgColor(color);
EPDManager::getInstance().setForegroundColor(color);
// Serial.print(F("Setting foreground color to "));
// Serial.println(fgColor->value().c_str());
settingsChanged = true;
@ -830,7 +836,7 @@ bool processEpdColorSettings(AsyncWebServerRequest *request)
uint32_t color = strtol(bgColor->value().c_str(), NULL, 16);
preferences.putUInt("bgColor", color);
setBgColor(color);
EPDManager::getInstance().setBackgroundColor(color);
// Serial.print(F("Setting background color to "));
// Serial.println(bgColor->value().c_str());
settingsChanged = true;
@ -909,7 +915,7 @@ void onApiStopDataSources(AsyncWebServerRequest *request)
request->beginResponseStream(JSON_CONTENT);
stopPriceNotify();
stopBlockNotify();
BlockNotify::getInstance().stop();
request->send(response);
}
@ -920,16 +926,15 @@ void onApiRestartDataSources(AsyncWebServerRequest *request)
request->beginResponseStream(JSON_CONTENT);
restartPriceNotify();
restartBlockNotify();
// setupPriceNotify();
// setupBlockNotify();
BlockNotify::getInstance().restart();
request->send(response);
}
void onApiLightsOff(AsyncWebServerRequest *request)
{
setLights(0, 0, 0);
auto& ledHandler = getLedHandler();
ledHandler.setLights(0, 0, 0);
request->send(HTTP_OK);
}
@ -944,13 +949,15 @@ void onApiLightsSetColor(AsyncWebServerRequest *request)
if (rgbColor.compareTo("off") == 0)
{
setLights(0, 0, 0);
auto& ledHandler = getLedHandler();
ledHandler.setLights(0, 0, 0);
}
else
{
uint r, g, b;
sscanf(rgbColor.c_str(), "%02x%02x%02x", &r, &g, &b);
setLights(r, g, b);
auto& ledHandler = getLedHandler();
ledHandler.setLights(r, g, b);
}
JsonDocument doc;
@ -968,6 +975,9 @@ void onApiLightsSetColor(AsyncWebServerRequest *request)
void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json)
{
auto& ledHandler = getLedHandler();
auto& pixels = ledHandler.getPixels();
JsonArray lights = json.as<JsonArray>();
if (lights.size() != pixels.numPixels())
@ -1016,7 +1026,7 @@ void onApiLightsSetJson(AsyncWebServerRequest *request, JsonVariant &json)
}
pixels.show();
saveLedState();
ledHandler.saveLedState();
request->send(HTTP_OK);
}
@ -1080,19 +1090,21 @@ void onApiShowCurrency(AsyncWebServerRequest *request)
#ifdef HAS_FRONTLIGHT
void onApiFrontlightOn(AsyncWebServerRequest *request)
{
frontlightFadeInAll();
auto& ledHandler = getLedHandler();
ledHandler.frontlightFadeInAll();
request->send(HTTP_OK);
}
void onApiFrontlightStatus(AsyncWebServerRequest *request)
{
auto& ledHandler = getLedHandler();
AsyncResponseStream *response =
request->beginResponseStream(JSON_CONTENT);
JsonDocument root;
std::vector<uint16_t> statuses = frontlightGetStatus();
std::vector<uint16_t> statuses = ledHandler.frontlightGetStatus();
uint16_t arr[NUM_SCREENS];
std::copy(statuses.begin(), statuses.end(), arr);
@ -1105,7 +1117,8 @@ void onApiFrontlightStatus(AsyncWebServerRequest *request)
void onApiFrontlightFlash(AsyncWebServerRequest *request)
{
frontlightFlash(preferences.getUInt("flEffectDelay"));
auto& ledHandler = getLedHandler();
ledHandler.frontlightFlash(preferences.getUInt("flEffectDelay"));
request->send(HTTP_OK);
}
@ -1114,7 +1127,8 @@ void onApiFrontlightSetBrightness(AsyncWebServerRequest *request)
{
if (request->hasParam("b"))
{
frontlightSetBrightness(request->getParam("b")->value().toInt());
auto& ledHandler = getLedHandler();
ledHandler.frontlightSetBrightness(request->getParam("b")->value().toInt());
request->send(HTTP_OK);
}
else
@ -1125,21 +1139,51 @@ void onApiFrontlightSetBrightness(AsyncWebServerRequest *request)
void onApiFrontlightOff(AsyncWebServerRequest *request)
{
frontlightFadeOutAll();
auto& ledHandler = getLedHandler();
ledHandler.frontlightFadeOutAll();
request->send(HTTP_OK);
}
#endif
void onApiDNDTimeBasedEnable(AsyncWebServerRequest *request) {
auto& ledHandler = getLedHandler();
ledHandler.setDNDTimeBasedEnabled(true);
request->send(200);
}
void onApiDNDTimeBasedDisable(AsyncWebServerRequest *request) {
auto& ledHandler = getLedHandler();
ledHandler.setDNDTimeBasedEnabled(false);
request->send(200);
}
void onApiDNDSetTimeRange(AsyncWebServerRequest *request) {
if (request->hasParam("startHour") && request->hasParam("startMinute") &&
request->hasParam("endHour") && request->hasParam("endMinute")) {
auto& ledHandler = getLedHandler();
uint8_t startHour = request->getParam("startHour")->value().toInt();
uint8_t startMinute = request->getParam("startMinute")->value().toInt();
uint8_t endHour = request->getParam("endHour")->value().toInt();
uint8_t endMinute = request->getParam("endMinute")->value().toInt();
ledHandler.setDNDTimeRange(startHour, startMinute, endHour, endMinute);
request->send(200);
} else {
request->send(400);
}
}
void onApiDNDStatus(AsyncWebServerRequest *request) {
auto& ledHandler = getLedHandler();
JsonDocument doc;
doc["enabled"] = dndEnabled;
doc["timeBasedEnabled"] = dndTimeBasedEnabled;
doc["startTime"] = String(dndTimeRange.startHour) + ":" +
(dndTimeRange.startMinute < 10 ? "0" : "") + String(dndTimeRange.startMinute);
doc["endTime"] = String(dndTimeRange.endHour) + ":" +
(dndTimeRange.endMinute < 10 ? "0" : "") + String(dndTimeRange.endMinute);
doc["active"] = isDNDActive();
doc["enabled"] = ledHandler.isDNDEnabled();
doc["timeBasedEnabled"] = ledHandler.isDNDTimeBasedEnabled();
doc["startTime"] = String(ledHandler.getDNDStartHour()) + ":" +
(ledHandler.getDNDStartMinute() < 10 ? "0" : "") + String(ledHandler.getDNDStartMinute());
doc["endTime"] = String(ledHandler.getDNDEndHour()) + ":" +
(ledHandler.getDNDEndMinute() < 10 ? "0" : "") + String(ledHandler.getDNDEndMinute());
doc["active"] = ledHandler.isDNDActive();
String response;
serializeJson(doc, response);
@ -1147,11 +1191,71 @@ void onApiDNDStatus(AsyncWebServerRequest *request) {
}
void onApiDNDEnable(AsyncWebServerRequest *request) {
setDNDEnabled(true);
auto& ledHandler = getLedHandler();
ledHandler.setDNDEnabled(true);
request->send(200);
}
void onApiDNDDisable(AsyncWebServerRequest *request) {
setDNDEnabled(false);
auto& ledHandler = getLedHandler();
ledHandler.setDNDEnabled(false);
request->send(200);
}
void onApiLightsGet(AsyncWebServerRequest *request)
{
auto& ledHandler = getLedHandler();
auto& pixels = ledHandler.getPixels();
JsonDocument doc;
JsonArray lights = doc.createNestedArray("lights");
for (uint i = 0; i < pixels.numPixels(); i++)
{
uint32_t pixColor = pixels.getPixelColor(pixels.numPixels() - i - 1);
JsonObject light = lights.createNestedObject();
light["r"] = (uint8_t)(pixColor >> 16);
light["g"] = (uint8_t)(pixColor >> 8);
light["b"] = (uint8_t)pixColor;
}
String output;
serializeJson(doc, output);
request->send(200, "application/json", output);
}
void onApiLightsPost(AsyncWebServerRequest *request, uint8_t *data, size_t len,
size_t index, size_t total)
{
auto& ledHandler = getLedHandler();
auto& pixels = ledHandler.getPixels();
JsonDocument doc;
DeserializationError error = deserializeJson(doc, data);
if (error)
{
request->send(400);
return;
}
JsonArray lights = doc["lights"];
if (lights.size() != pixels.numPixels())
{
request->send(400);
return;
}
for (uint i = 0; i < pixels.numPixels(); i++)
{
JsonObject light = lights[i];
uint8_t red = light["r"];
uint8_t green = light["g"];
uint8_t blue = light["b"];
pixels.setPixelColor((pixels.numPixels() - i - 1),
pixels.Color(red, green, blue));
}
pixels.show();
request->send(200);
}

View file

@ -18,6 +18,8 @@
#define WEBSERVER_H
#include "ESPAsyncWebServer.h"
#include "lib/config.hpp"
#include "lib/led_handler.hpp"
#include "lib/block_notify.hpp"
uint wifiLostConnection;
uint priceNotifyLostConnection = 0;
@ -48,7 +50,8 @@ void handleBlockNotifyDisconnection() {
if ((getUptime() - blockNotifyLostConnection) > 300) { // 5 minutes timeout
Serial.println(F("Block notification connection lost for 5 minutes, restarting handler..."));
restartBlockNotify();
auto& blockNotify = BlockNotify::getInstance();
blockNotify.restart();
blockNotifyLostConnection = 0;
}
}
@ -58,13 +61,14 @@ void handleFrontlight() {
if (hasLightLevel() && preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0) {
uint lightLevel = getLightLevel();
uint luxThreshold = preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE);
auto& ledHandler = getLedHandler();
if (lightLevel <= 1 && preferences.getBool("flOffWhenDark", DEFAULT_FL_OFF_WHEN_DARK)) {
if (frontlightIsOn()) frontlightFadeOutAll();
} else if (lightLevel < luxThreshold && !frontlightIsOn()) {
frontlightFadeInAll();
} else if (frontlightIsOn() && lightLevel > luxThreshold) {
frontlightFadeOutAll();
if (ledHandler.frontlightIsOn()) ledHandler.frontlightFadeOutAll();
} else if (lightLevel < luxThreshold && !ledHandler.frontlightIsOn()) {
ledHandler.frontlightFadeInAll();
} else if (ledHandler.frontlightIsOn() && lightLevel > luxThreshold) {
ledHandler.frontlightFadeOutAll();
}
}
#endif
@ -90,19 +94,18 @@ void checkWiFiConnection() {
void checkMissedBlocks() {
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 != getBlockHeight()) {
if (currentBlock != blockNotify.getBlockHeight()) {
Serial.println(F("Detected stuck block height... restarting block handler."));
restartBlockNotify();
blockNotify.restart();
}
setLastBlockUpdate(getUptime());
blockNotify.setLastBlockUpdate(getUptime());
}
}
void monitorDataConnections() {
// Price notification monitoring
if (getPriceNotifyInit() && !preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE) && !isPriceNotifyConnected()) {
handlePriceNotifyDisconnection();
@ -111,9 +114,10 @@ void monitorDataConnections() {
}
// Block notification monitoring
if (getBlockNotifyInit() && !isBlockNotifyConnected()) {
auto& blockNotify = BlockNotify::getInstance();
if (blockNotify.isInitialized() && !blockNotify.isConnected()) {
handleBlockNotifyDisconnection();
} else if (blockNotifyLostConnection > 0 && isBlockNotifyConnected()) {
} else if (blockNotifyLostConnection > 0 && blockNotify.isConnected()) {
blockNotifyLostConnection = 0;
}
@ -125,7 +129,7 @@ void monitorDataConnections() {
}
// Check for missed blocks
if ((getLastBlockUpdate() - getUptime()) > 45 * 60) {
if ((blockNotify.getLastBlockUpdate() - getUptime()) > 45 * 60) {
checkMissedBlocks();
}
}
@ -137,7 +141,6 @@ extern "C" void app_main() {
bool thirdPartySource = getDataSource() == THIRD_PARTY_SOURCE;
while (true) {
if (eventSourceTaskHandle != NULL) {
xTaskNotifyGive(eventSourceTaskHandle);