Compare commits

...

85 commits

Author SHA1 Message Date
Djuri Baars
8a818c66a0 Update readme
All checks were successful
BTClock CI / build (push) Successful in 10m20s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 20s
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 27s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Successful in 25s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Successful in 19s
BTClock CI / release (push) Successful in 35s
2024-12-21 01:15:59 +01:00
Djuri Baars
9889e983ec Update WebUI 2024-12-21 00:51:26 +01:00
Djuri Baars
03dbb8add6 Small improvements for epd 2024-12-21 00:50:57 +01:00
Djuri Baars
753838b122 Switch to new ArduinoJson methods 2024-12-21 00:15:08 +01:00
Djuri Baars
46c0f3a22b Improve webserver functionality 2024-12-21 00:01:27 +01:00
Djuri Baars
fb70d435a9 Get mining pool logos by download 2024-12-20 23:02:54 +01:00
Djuri Baars
7bcb24bab0 Mining pool bugfixes, improvements and icon optimizations
All checks were successful
BTClock CI / build (push) Successful in 17m35s
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 27s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 20s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Successful in 25s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Successful in 19s
BTClock CI / release (push) Successful in 36s
2024-12-20 14:11:26 +01:00
Djuri Baars
e8a7b221cb Update WebUI for new mining pools
All checks were successful
BTClock CI / build (push) Successful in 14m22s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 27s
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 20s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Successful in 25s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Successful in 20s
BTClock CI / release (push) Successful in 34s
2024-12-20 04:07:46 +01:00
Djuri Baars
aeee5238b3 Improve mining pool interface, added GoBrrr pool, Public Pool and Satoshi Radio pool 2024-12-20 04:00:09 +01:00
Djuri Baars
f613c7e9a1 Update WebUI
All checks were successful
BTClock CI / build (push) Successful in 20m17s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 22s
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 20s
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 20s
BTClock CI / release (push) Successful in 16s
2024-12-20 01:33:10 +01:00
c7ea2f3e4d Merge pull request '[Feature] Adds mining pool screens' (#5) from kdmukai/btclock_v3:mining_stats_v2 into main
Reviewed-on: #5
2024-12-20 00:11:01 +00:00
Djuri Baars
814cd234a9 Big refactor of mining pool support, optimization of existing icons 2024-12-20 01:08:03 +01:00
f9aa593f0b Removed pool name consts; reduce Preferences hits 2024-12-19 13:54:28 -06:00
01ef6daf9f Update screen id consts 2024-12-19 13:53:16 -06:00
8f9307d1e4 Remove whitespace 2024-12-18 21:34:52 -06:00
e758659a4a Remove platformio edit 2024-12-18 21:31:05 -06:00
be224d1f91 Restoring .gitmodule link to main webui repo 2024-12-18 21:17:19 -06:00
72e5ee6580 README update 2024-12-18 21:17:19 -06:00
c3af0b4d36 Docs update; info on mining pool stats config 2024-12-18 21:17:19 -06:00
fabc6c1d28 bugfix for long preferences key 2024-12-18 21:17:19 -06:00
e175b5f2f5 syncing to my forked webui submodule 2024-12-18 21:17:19 -06:00
2bc5984f6f Add mining pool daily/expected earnings 2024-12-18 21:17:19 -06:00
1bd465b33a rebuilt with larger partitions 2024-12-18 21:17:19 -06:00
Djuri Baars
ae2e6656df Fix LittleFS sha256 generation 2024-12-18 22:52:50 +01:00
Djuri Baars
c989169ff4 Fix workflow and auto updater webui filenames 2024-12-18 22:20:40 +01:00
Djuri Baars
1a4bc9b711 Merge branch 'main' of https://git.btclock.dev/btclock/btclock_v3
Some checks failed
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Has been cancelled
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Has been cancelled
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Has been cancelled
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Has been cancelled
BTClock CI / release (push) Has been cancelled
BTClock CI / build (push) Has been cancelled
2024-12-18 21:40:53 +01:00
Djuri Baars
0dcde59fb4 Fix workflow 2024-12-18 21:40:18 +01:00
Djuri Baars
de8fe2e26e Fix preaction script 2024-12-18 20:41:42 +01:00
Djuri Baars
af4c466659 Switch to leaner MCP23017 library, create new aligned partition tables 2024-12-18 19:47:03 +01:00
Djuri Baars
83d293c58e Add bitaxe logo to WebUI
All checks were successful
BTClock CI / build (push) Successful in 18m16s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 26s
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 24s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Successful in 19s
BTClock CI / release (push) Successful in 10s
2024-12-18 01:32:11 +01:00
Djuri Baars
da25c7de90 Merge remote-tracking branch 'keith/bitaxe_logo' 2024-12-18 01:27:05 +01:00
Djuri Baars
4a52fc0bf2 Add vertical screen description option 2024-12-18 00:50:20 +01:00
db0ec01c86 Add bitaxe logo 2024-12-17 08:53:00 -06:00
Djuri Baars
34b77ea105 Update WebUI 2024-12-12 23:12:59 +01:00
Djuri Baars
dbf2c53083 Adapted tests for Mow Units
All checks were successful
BTClock CI / build (push) Successful in 25m14s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 20s
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 32s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Successful in 19s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Successful in 29s
BTClock CI / release (push) Successful in 29s
2024-12-10 15:18:04 +01:00
Djuri Baars
2a116d97ed Add frontlight off when dark setting
Some checks failed
BTClock CI / build (push) Failing after 1m39s
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
2024-12-10 15:13:17 +01:00
3b6f1db3c5 Merge pull request 'Mow Units: No rounding' (#3) from kdmukai/btclock_v3:mow_units_no_rounding into main
Reviewed-on: #3
2024-12-10 13:26:23 +00:00
9ada991ab1 Update utils.cpp 2024-12-09 16:05:55 -06:00
132aa835cd Mow Units no rounding! 2024-12-09 15:58:39 -06:00
Djuri Baars
d6604d28d6 Update some initial values
All checks were successful
BTClock CI / build (push) Successful in 27m16s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 20s
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 30s
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 28s
BTClock CI / release (push) Successful in 26s
2024-12-05 18:28:46 +01:00
Djuri Baars
33c06c86a1 Prepare sats per dollar for 1B amounts 2024-12-05 18:22:59 +01:00
Djuri Baars
f0f591a16f Make better use of the screens in compact suffix mode 2024-12-05 05:21:14 +01:00
Djuri Baars
41b5fcf1c1 Bugfix for suffix compact mode
All checks were successful
BTClock CI / build (push) Successful in 22m18s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 20s
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 30s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Successful in 19s
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Successful in 27s
BTClock CI / release (push) Successful in 25s
2024-12-05 04:32:32 +01:00
Djuri Baars
981895d315 Convert partition table to hex, update WebUI 2024-11-29 01:22:07 +01:00
Djuri Baars
031b506fed Fix partition tables for bigger flash sizes 2024-11-28 19:21:15 +01:00
Djuri Baars
4cda081d05 Add suffix compact mode, added extra zap notify settings, WebUI cleanup
All checks were successful
BTClock CI / build (push) Successful in 15m59s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 21s
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 28s
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 25s
BTClock CI / release (push) Successful in 11s
2024-11-28 18:22:07 +01:00
Djuri Baars
2951055f68 Add requirements.txt for CI
All checks were successful
BTClock CI / build (push) Successful in 20m55s
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 21s
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 28s
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 26s
BTClock CI / release (push) Successful in 12s
2024-11-27 11:41:58 +01:00
Djuri Baars
d37307cccf Add Mow mode notation and setting
Some checks failed
BTClock CI / build (push) Failing after 30s
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
2024-11-27 11:33:12 +01:00
Djuri Baars
239297c26d Add own badges to README 2024-11-26 21:48:28 +01:00
Djuri Baars
3b47c81cfe Webserver dependency update and testing new CI flow
All checks were successful
/ build (push) Successful in 15m45s
/ merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Successful in 28s
/ merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Successful in 25s
/ merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Successful in 26s
/ merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Successful in 25s
/ release (push) Successful in 23s
2024-11-26 20:50:32 +01:00
Djuri Baars
9f3351f85b Add Forgejo CI action
Some checks failed
/ merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Has been cancelled
/ merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Has been cancelled
/ merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Has been cancelled
/ merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Has been cancelled
/ release (push) Has been cancelled
/ build (push) Has been cancelled
Remove report action

Fix release step

Add platformio and node_modules cache

Try with less targets

Disable more

Restore all variants

Remove verbose and feature branch run
2024-11-26 20:26:01 +01:00
Djuri Baars
1ccd5f18fb Update WebUI
Some checks failed
BTClock CI / build (push) Has been cancelled
BTClock CI / merge (map[name:btclock_rev_b version:esp32s3], 213epd) (push) Has been cancelled
BTClock CI / merge (map[name:btclock_v8 version:esp32s3], 213epd) (push) Has been cancelled
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 213epd) (push) Has been cancelled
BTClock CI / merge (map[name:lolin_s3_mini version:esp32s3], 29epd) (push) Has been cancelled
BTClock CI / release (push) Has been cancelled
2024-11-26 00:03:07 +01:00
Djuri Baars
82dd70a38d Use CA bundle instead of single certificates, make auto update URL configurable 2024-11-25 23:54:02 +01:00
Djuri Baars
a614bd15db Restore Nostr Zap subscription after loss of connection 2024-10-23 00:43:53 +02:00
Djuri Baars
b0ec0685a1 Bugfix for too short WiFi-setup passwords 2024-10-03 00:26:14 +02:00
Djuri Baars
85579e98cf Bugfixes for stack size and market cap with small chars, write tests for the small char mcap 2024-09-28 00:03:18 +02:00
Djuri Baars
e8a9e253f7 Fix matrix expansion 2024-09-21 18:40:44 +02:00
Djuri Baars
8b72f2f6b3 Fix merged binary for v8 2024-09-21 18:25:13 +02:00
Djuri Baars
ff0d8f5a0a Make BTClock v8 board working again 2024-09-21 15:58:07 +02:00
Djuri Baars
41bf2480ce Dependency updates and fix URL rewrites 2024-09-18 02:40:24 +02:00
Djuri Baars
ff50acf913 Fix WebUI currency converter, reorganize some code 2024-09-18 02:00:10 +02:00
Djuri Baars
5dd47c2275 Fixes for Rev. B black PCB 2024-09-16 21:50:28 +02:00
Djuri Baars
630943ec54 Bring back snprintf for ledstate 2024-09-12 02:22:29 +02:00
Djuri Baars
18bac7dcc7 Remove old settings from sdkconfig and add optimizations 2024-09-12 02:18:44 +02:00
Djuri Baars
023ff29131 Add single-click auto-update functionality 2024-09-11 20:27:40 +02:00
Djuri Baars
d00c216126 GitHub Workflow addition 2024-09-11 18:56:06 +02:00
Djuri Baars
283469dc4c Fix github action 2024-09-11 18:07:34 +02:00
Djuri Baars
5d5b09f56c Prepare for automatic OTA updates 2024-09-11 17:40:44 +02:00
Djuri Baars
1f2110fc5a Make zap notify more lightning like, verify SSL certificates, remove price fetch code 2024-09-11 03:23:41 +02:00
Djuri Baars
5425ea7fbf Improve setup-WiFi password generation 2024-09-10 11:12:14 +02:00
Djuri Baars
7a1ce54248 Fix WebUI credentials not being saved 2024-09-10 10:21:02 +02:00
Djuri Baars
f42cd250fe Add some tests for multi-currency 2024-09-09 16:52:23 +02:00
Djuri Baars
849e5ce439 Finish V2 MsgPack WebSocket API implementation 2024-09-09 15:13:41 +02:00
Djuri Baars
c276d32807 Implement multi-currency support with MsgPack 2024-09-05 14:00:15 +02:00
Djuri Baars
a4ff5a2f75 Add WebUI authentication 2024-09-03 01:36:44 +02:00
Djuri Baars
00ac808731 Fix GitHub action 2024-09-02 23:28:09 +02:00
Djuri Baars
6cf464c3e3 Dependency upgrade and cleanup 2024-09-02 23:04:23 +02:00
Djuri Baars
a31a42511f Fix disabled screen skipping 2024-09-02 22:44:23 +02:00
Djuri Baars
478c951ffb Improved multi-currency support, improved WebUI checks 2024-08-31 23:11:48 +02:00
Djuri Baars
9c67f769d3 WebUI improvements for small screens 2024-08-31 18:06:42 +02:00
Djuri Baars
65496fbb29 WebUI improvements 2024-08-31 17:22:30 +02:00
Djuri Baars
2777637355 Improve Nostr Zap functionality, BitAxe integration bugfix 2024-08-31 15:45:19 +02:00
Djuri Baars
e39a0ccc14 Add zap notify functionality 2024-08-24 16:27:55 +03:00
Djuri Baars
99e622eeef Lock dependency 2024-08-19 01:53:16 +02:00
Djuri Baars
0a08c5f9ea Begin support for other currencies 2024-08-15 00:37:50 +02:00
87 changed files with 6629 additions and 2241 deletions

View file

@ -0,0 +1,190 @@
name: "BTClock CI"
on:
push:
tags:
- "*"
workflow_dispatch:
jobs:
build:
runs-on: docker
container:
image: ghcr.io/catthehacker/ubuntu:js-22.04
permissions:
contents: write
checks: write
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: lts/*
cache: yarn
cache-dependency-path: "**/yarn.lock"
- uses: actions/cache@v4
with:
path: |
~/.cache/pip
~/.platformio/.cache
~/data/node_modules
.pio
data/node_modules
key: ${{ runner.os }}-pio
- uses: actions/setup-python@v5
with:
python-version: "3.9"
cache: "pip"
- name: Get current date
id: dateAndTime
shell: bash
run: echo "dateAndTime=$(date +'%Y-%m-%d-%H:%M')" >> $GITHUB_OUTPUT
- name: Install PlatformIO Core
shell: bash
run: pip install --upgrade platformio
- name: Run unit tests
shell: bash
run: mkdir -p junit-reports && pio test -e native_test_only --junit-output-path junit-reports/
- name: Build BTClock firmware
shell: bash
run: pio run
- name: Build BTClock filesystem
shell: bash
run: pio run --target buildfs
- name: Copy bootloader to output folder
run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin .pio
- name: Upload artifacts
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
with:
include-hidden-files: true
retention-days: 1
name: prepared-outputs
path: .pio/**/*.bin
merge:
runs-on: docker
container:
image: ghcr.io/catthehacker/ubuntu:js-22.04
permissions:
contents: write
checks: write
needs: build
continue-on-error: true
strategy:
matrix:
chip:
- name: lolin_s3_mini
version: esp32s3
- name: btclock_rev_b
version: esp32s3
- name: btclock_v8
version: esp32s3
epd_variant: [213epd, 29epd]
exclude:
- chip: { name: btclock_rev_b, version: esp32s3 }
epd_variant: 29epd
- chip: { name: btclock_v8, version: esp32s3 }
epd_variant: 29epd
steps:
- uses: https://code.forgejo.org/forgejo/download-artifact@v4
with:
name: prepared-outputs
path: .pio
- name: Install esptools.py
run: pip install --upgrade esptool
- name: Create merged firmware binary
shell: bash
run: |
mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }}
if [ "${{ matrix.chip.name }}" == "btclock_v8" ]; then
esptool.py --chip ${{ matrix.chip.version }} merge_bin \
-o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \
--flash_mode dio \
--flash_freq 80m \
--flash_size 16MB \
0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin \
0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \
0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \
0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \
0xDF0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs_16MB.bin
elif [ "${{ matrix.chip.name }}" == "btclock_rev_b" ]; then
esptool.py --chip ${{ matrix.chip.version }} merge_bin \
-o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \
--flash_mode dio \
--flash_freq 80m \
--flash_size 8MB \
0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin \
0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \
0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \
0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \
0x6F0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs_8MB.bin;
else
esptool.py --chip ${{ matrix.chip.version }} merge_bin \
-o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \
--flash_mode dio \
0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin \
0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \
0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \
0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \
0x380000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs_4MB.bin
# Adjust the offset for littlefs or other files as needed for the original case
fi
- name: Create checksum for firmware
shell: bash
run: shasum -a 256 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_firmware.bin.sha256
- name: Create checksum for merged binary
shell: bash
run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin.sha256
- name: Create checksum for littlefs partition
shell: bash
run: |
fs_file=$(find .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }} -name "littlefs*.bin")
echo $fs_file
fs_name=$(basename "$fs_file")
shasum -a 256 "$fs_file" | awk '{print $1}' > "${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${fs_name}.sha256"
cat "${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${fs_name}.sha256"
- name: Copy all artifacts to output folder
run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }}
- name: Create OTA binary file
run: mv ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_firmware.bin
- name: Upload artifacts
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
with:
name: build-${{ matrix.chip.name }}-${{ matrix.epd_variant }}
path: |
${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin
${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.sha256
release:
runs-on: docker
permissions:
contents: write
checks: write
needs: merge
steps:
- name: Download matrix outputs
uses: https://code.forgejo.org/forgejo/download-artifact@v4
with:
pattern: build-*
merge-multiple: false
path: temp
- name: Copy files
run: |
mkdir -p release
find temp -type f \( -name "*.bin" -o -name "*.sha256" \) -exec cp -f {} release/ \;
- name: Create release
uses: https://code.forgejo.org/actions/forgejo-release@v2.4.0
with:
url: "https://git.btclock.dev"
repo: "${{ github.repository }}"
direction: upload
tag: "${{ github.ref_name }}"
sha: "${{ github.sha }}"
release-dir: release
token: ${{ secrets.TOKEN }}
override: ${{ github.ref_type != 'tag' && github.ref_name != 'main' }}
prerelease: ${{ github.ref_type != 'tag' && github.ref_name != 'main' }}
release-notes-assistant: false

View file

@ -1,9 +1,9 @@
name: BTClock CI
on:
on:
push:
tags:
- '*'
- "*"
jobs:
build:
@ -22,6 +22,7 @@ jobs:
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
include-hidden-files: true
retention-days: 1
name: prepared-outputs
path: .pio/**/*.bin
@ -39,10 +40,14 @@ jobs:
version: esp32s3
- name: btclock_rev_b
version: esp32s3
- name: btclock_v8
version: esp32s3
epd_variant: [213epd, 29epd]
exclude:
- chip: btclock_rev_b
epd_variant: 29epd
- chip: {name: btclock_rev_b, version: esp32s3}
epd_variant: 29epd
- chip: {name: btclock_v8, version: esp32s3}
epd_variant: 29epd
steps:
- uses: actions/download-artifact@v4
with:
@ -50,12 +55,44 @@ jobs:
path: .pio
- name: Install esptools.py
run: pip install --upgrade esptool
# - name: Create merged firmware binary
# run: mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && esptool.py --chip ${{ matrix.chip.version }} merge_bin -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin --flash_mode dio 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin 0xe000 .pio/boot_app0.bin 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin 0x369000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin
- name: Create merged firmware binary
run: mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && esptool.py --chip ${{ matrix.chip.version }} merge_bin -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin --flash_mode dio 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin 0xe000 .pio/boot_app0.bin 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin 0x369000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin
run: |
if [ "${{ matrix.chip.name }}" == "btclock_v8" ]; then
mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \
esptool.py --chip ${{ matrix.chip.version }} merge_bin \
-o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \
--flash_mode dio \
--flash_freq 80m \
--flash_size 16MB \
0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin \
0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \
0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \
0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \
0x810000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin;
else
# Original command for other cases
mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \
esptool.py --chip ${{ matrix.chip.version }} merge_bin \
-o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \
--flash_mode dio \
0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin \
0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin \
0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \
0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \
0x369000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin
# Adjust the offset for littlefs or other files as needed for the original case
fi
- name: Create checksum for firmware
run: shasum -a 256 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_firmware.bin.sha256
- name: Create checksum for merged binary
run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.sha256
run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin.sha256
- name: Create checksum for littlefs partition
run: shasum -a 256 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin.sha256
- name: Copy all artifacts to output folder
run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }}
@ -84,10 +121,10 @@ jobs:
merge-multiple: false
- name: Write commit hash to file
run: echo $GITHUB_SHA > commit.txt
- name: Write build date to file
run: echo "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" > date.txt
- name: Create release
uses: ncipollo/release-action@v1
with:
@ -110,8 +147,8 @@ jobs:
with:
source-directory: .
target-directory: firmware_v3/
destination-github-username: 'btclock'
destination-repository-name: 'web-flasher'
destination-github-username: "btclock"
destination-repository-name: "web-flasher"
target-branch: main
user-name: ${{github.actor}}
user-email: ${{github.actor}}@users.noreply.github.com
user-email: ${{github.actor}}@users.noreply.github.com

3
.gitignore vendored
View file

@ -10,4 +10,5 @@ data/.yarn
data/node_modules
node_modules
.DS_Store
*.bin
*.bin
ci/cache

2
.gitmodules vendored
View file

@ -1,3 +1,3 @@
[submodule "data"]
path = data
url = https://github.com/btclock/webui.git
url = https://git.btclock.dev/btclock/webui.git

View file

@ -1,6 +1,8 @@
# BTClock v3
[![BTClock CI](https://github.com/btclock/btclock_v3/actions/workflows/tagging.yml/badge.svg)](https://github.com/btclock/btclock_v3/actions/workflows/tagging.yml)
[![Latest release](https://git.btclock.dev/btclock/btclock_v3/badges/release.svg)](https://git.btclock.dev/btclock/btclock_v3/releases/latest)
[![BTClock CI](https://git.btclock.dev/btclock/btclock_v3/badges/workflows/push.yaml/badge.svg)](https://git.btclock.dev/btclock/btclock_v3/actions?workflow=push.yaml&actor=0&status=0)
Software for the BTClock project.
@ -12,12 +14,46 @@ Biggest differences with v2 are:
- Added market capitalization screen
- LED flash on new block (and focus to block height screen on new block)
New features:
- BitAxe integration
- Nostr Zap notifier
- Multiple mining pool stats integrations
"Steal focus on new block" means that when a new block is mined, the display will switch to the block height screen if it's not on it already.
Most [information](https://github.com/btclock/btclock_v2/wiki) about BTClock v2 is still valid for this version.
See the [docs](https://git.btclock.dev/btclock/docs) repo for more information and building instructions.
**NOTE**: The software assumes that the hardware is run in a controlled private network. The Web UI and the OTA update mechanism are not password protected and accessible to anyone in the network. Also, since the device only fetches numbers through WebSockets it will skip server certificate verification to save resources.
**NOTE**: The software assumes that the hardware is run in a controlled private network. ~~The Web UI and the OTA update mechanism are not password protected and accessible to anyone in the network. Also, since the device only fetches numbers through WebSockets it will skip server certificate verification to save resources.~~ Since 3.2.0 the WebUI is password protectable and all certificates are verified. OTA update mechanism is not password-protected.
## Building
Use PlatformIO to build it yourself. Make sure you fetch the [WebUI](https://github.com/btclock/webui) submodule.
Use PlatformIO to build it yourself. Make sure you fetch the [WebUI](https://git.btclock.dev/btclock/webui) submodule.
## Mining pool stats
Enable mining pool stats by accessing your btclock's web UI (point a web browser at the device's IP address).
Under Settings -> Extra Features: toggle Enable Mining Pool Stats.
New options will appear. Select your mining pool and enter your pool username (Ocean) or api key (Braiins).
The Mining Pool Earnings screen displays:
* Braiins: Today's mining reward thus far
* Ocean: Your estimated earnings if the pool were to find a block right now
For solo mining pools, there are no earning estimations. Your username is the onchain withdrawal address, without the worker name.
### Braiins Pool integration
Create an API key based on the steps [here](https://academy.braiins.com/en/braiins-pool/monitoring/#api-configuration).
The key's permissions should be:
* Web Access: no
* API Access: yes
* Access Permissions: Read-only
Copy the token that is created for the new key. Enter this as your "Mining Pool username or api key" in the btclock web UI.
### Ocean integration
Your "Mining Pool username" is just the onchain withdrawal address that you specify when pointing your miners at Ocean.

View file

@ -10,7 +10,7 @@
"-DBOARD_HAS_PSRAM",
"-DARDUINO_BTCLOCK",
"-DARDUINO_ESP32S3_DEV",
"-DIS_BTCLOCK_S3",
"-DIS_BTCLOCK_V8",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
@ -20,8 +20,8 @@
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "opi",
"espidf": {
"sdkconfig_path": "boards"
"esp-idf": {
"sdkconfig_path": "boards/sdkconfig.btclock_v8"
},
"hwids": [
[

1598
boards/sdkconfig.btclock_v8 Normal file

File diff suppressed because it is too large Load diff

15
ci/Dockerfile Normal file
View file

@ -0,0 +1,15 @@
# Use the official Python 3.9 image as the base
FROM python:3.9-slim
# Set the working directory
WORKDIR /workspace
RUN apt-get update && apt-get install -y git
# Install PlatformIO
RUN pip install platformio
WORKDIR /usr/src
CMD ["platformio", "run"]

2
data

@ -1 +1 @@
Subproject commit be5647e1a518036aedec2e51543c19e04bbfa191
Subproject commit 924be8fc2eb02fe384a20b53da1a9fa3d8db8a05

View file

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

View file

@ -24,7 +24,7 @@ std::array<std::string, NUM_SCREENS> parseBitaxeHashRate(std::string text)
}
ret[NUM_SCREENS - 1] = "GH/S";
ret[0] = "BIT/AXE";
ret[0] = "mdi:bitaxe";
return ret;
}
@ -37,7 +37,7 @@ std::array<std::string, NUM_SCREENS> parseBitaxeBestDiff(std::string text)
if (text.length() < NUM_SCREENS)
{
text.insert(text.begin(), NUM_SCREENS - text.length(), ' ');
ret[0] = "BIT/AXE";
ret[0] = "mdi:bitaxe";
ret[1] = "mdi:rocket";
firstIndex = 2;
}
@ -49,3 +49,4 @@ std::array<std::string, NUM_SCREENS> parseBitaxeBestDiff(std::string text)
return ret;
}

View file

@ -4,39 +4,140 @@
#include <emscripten/bind.h>
#endif
std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat)
char getCurrencySymbol(char input)
{
switch (input)
{
case CURRENCY_EUR:
return '[';
break;
case CURRENCY_GBP:
return ']';
break;
case CURRENCY_JPY:
return '^';
break;
case CURRENCY_AUD:
case CURRENCY_CAD:
case CURRENCY_USD:
return '$';
break;
default:
return input;
}
}
std::string getCurrencyCode(char input)
{
switch (input)
{
case CURRENCY_EUR:
return CURRENCY_CODE_EUR;
break;
case CURRENCY_GBP:
return CURRENCY_CODE_GBP;
break;
case CURRENCY_JPY:
return CURRENCY_CODE_JPY;
break;
case CURRENCY_AUD:
return CURRENCY_CODE_AUD;
break;
case CURRENCY_CAD:
return CURRENCY_CODE_CAD;
break;
default:
return CURRENCY_CODE_USD;
}
}
char getCurrencyChar(const std::string& input)
{
if (input == "EUR")
return CURRENCY_EUR;
else if (input == "GBP")
return CURRENCY_GBP;
else if (input == "JPY")
return CURRENCY_JPY;
else if (input == "AUD")
return CURRENCY_AUD;
else if (input == "CAD")
return CURRENCY_CAD;
else
return CURRENCY_USD; // Assuming USD is the default for unknown inputs
}
std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat, bool mowMode, bool shareDot)
{
std::array<std::string, NUM_SCREENS> ret;
std::string priceString;
if (std::to_string(price).length() >= NUM_SCREENS || useSuffixFormat) {
priceString = currencySymbol + formatNumberWithSuffix(price, NUM_SCREENS-2);
} else {
priceString = currencySymbol + std::to_string(price);
if (std::to_string(price).length() >= NUM_SCREENS || useSuffixFormat)
{
int numScreens = shareDot || mowMode ? NUM_SCREENS - 1 : NUM_SCREENS - 2;
priceString = getCurrencySymbol(currencySymbol) + formatNumberWithSuffix(price, numScreens, mowMode);
}
else
{
priceString = getCurrencySymbol(currencySymbol) + std::to_string(price);
}
std::uint32_t firstIndex = 0;
if (priceString.length() < (NUM_SCREENS))
if ((shareDot && priceString.length() <= (NUM_SCREENS)) || priceString.length() < (NUM_SCREENS))
{
priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' ');
if (currencySymbol == '[')
if (mowMode)
{
ret[0] = "BTC/EUR";
ret[0] = "MOW/UNITS";
}
else
{
ret[0] = "BTC/USD";
ret[0] = "BTC/" + getCurrencyCode(currencySymbol);
}
firstIndex = 1;
}
size_t dotPosition = priceString.find('.');
for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++)
if (shareDot && dotPosition != std::string::npos && dotPosition > 0)
{
ret[i] = priceString[i];
std::vector<std::string> tempArray;
if (dotPosition != std::string::npos && dotPosition > 0)
{
for (size_t i = 0; i < priceString.length(); ++i)
{
if (i == dotPosition - 1)
{
tempArray.push_back(std::string(1, priceString[i]) + ".");
++i; // Skip the dot in the next iteration
}
else
{
tempArray.push_back(std::string(1, priceString[i]));
}
}
// Copy from tempArray to ret
for (std::uint32_t i = firstIndex; i < NUM_SCREENS && i - firstIndex < tempArray.size(); ++i)
{
ret[i] = tempArray[i - firstIndex];
}
}
}
else
{
for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++)
{
ret[i] = std::string(1, priceString[i]);
}
}
return ret;
}
std::array<std::string, NUM_SCREENS> parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol)
std::array<std::string, NUM_SCREENS> parseSatsPerCurrency(std::uint32_t price,char currencySymbol, bool withSatsSymbol)
{
std::array<std::string, NUM_SCREENS> ret;
std::string priceString = std::to_string(int(round(1 / float(price) * 10e7)));
@ -45,17 +146,30 @@ std::array<std::string, NUM_SCREENS> parseSatsPerCurrency(std::uint32_t price, c
if (priceString.length() < (NUM_SCREENS))
{
priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' ');
if (currencySymbol == '[')
// Check if price is greater than 1 billion
if (price >= 100000000)
{
ret[0] = "SATS/EUR";
double satsPerCurrency = (1.0 / static_cast<double>(price)) * 1e8; // Calculate satoshis
std::ostringstream oss;
oss << std::fixed << std::setprecision(3) << satsPerCurrency; // Format with 3 decimal places
priceString = oss.str();
}
else
{
ret[0] = "MSCW/TIME";
priceString = std::to_string(static_cast<int>(round(1.0 / static_cast<double>(price) * 1e8))); // Default formatting
}
// Pad the string with spaces if necessary
if (priceString.length() < NUM_SCREENS)
{
priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' ');
}
if (currencySymbol != CURRENCY_USD || price >= 100000000) // no time anymore when earlier than 1
ret[0] = "SATS/" + getCurrencyCode(currencySymbol);
else
ret[0] = "MSCW/TIME";
firstIndex = 1;
for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++)
@ -63,7 +177,8 @@ std::array<std::string, NUM_SCREENS> parseSatsPerCurrency(std::uint32_t price, c
ret[i] = priceString[i];
}
if (withSatsSymbol) {
if (withSatsSymbol)
{
ret[insertSatSymbol] = "STS";
}
}
@ -91,7 +206,8 @@ std::array<std::string, NUM_SCREENS> parseBlockHeight(std::uint32_t blockHeight)
return ret;
}
std::array<std::string, NUM_SCREENS> parseBlockFees(std::uint16_t blockFees) {
std::array<std::string, NUM_SCREENS> parseBlockFees(std::uint16_t blockFees)
{
std::array<std::string, NUM_SCREENS> ret;
std::string blockFeesString = std::to_string(blockFees);
std::uint32_t firstIndex = 0;
@ -103,12 +219,12 @@ std::array<std::string, NUM_SCREENS> parseBlockFees(std::uint16_t blockFees) {
firstIndex = 1;
}
for (std::uint8_t i = firstIndex; i < NUM_SCREENS-1; i++)
for (std::uint8_t i = firstIndex; i < NUM_SCREENS - 1; i++)
{
ret[i] = blockFeesString[i];
}
ret[NUM_SCREENS-1] = "sat/vB";
ret[NUM_SCREENS - 1] = "sat/vB";
return ret;
}
@ -119,7 +235,8 @@ std::array<std::string, NUM_SCREENS> parseHalvingCountdown(std::uint32_t blockHe
const std::uint32_t nextHalvingBlock = 210000 - (blockHeight % 210000);
const std::uint32_t minutesToHalving = nextHalvingBlock * 10;
if (asBlocks) {
if (asBlocks)
{
std::string blockNrString = std::to_string(nextHalvingBlock);
std::uint32_t firstIndex = 0;
@ -134,9 +251,9 @@ std::array<std::string, NUM_SCREENS> parseHalvingCountdown(std::uint32_t blockHe
{
ret[i] = blockNrString[i];
}
} else {
}
else
{
const int years = floor(minutesToHalving / 525600);
const int days = floor((minutesToHalving - (years * 525600)) / (24 * 60));
@ -159,22 +276,16 @@ std::array<std::string, NUM_SCREENS> parseMarketCap(std::uint32_t blockHeight, s
std::array<std::string, NUM_SCREENS> ret;
std::uint32_t firstIndex = 0;
double supply = getSupplyAtBlock(blockHeight);
int64_t marketCap = static_cast<std::int64_t>(supply * double(price));
if (currencySymbol == '[')
{
ret[0] = "EUR/MCAP";
}
else
{
ret[0] = "USD/MCAP";
}
uint64_t marketCap = static_cast<std::uint64_t>(supply * double(price));
ret[0] = getCurrencyCode(currencySymbol) + "/MCAP";
if (bigChars)
{
firstIndex = 1;
// Serial.print("Market cap: ");
// Serial.println(marketCap);
std::string priceString = currencySymbol + formatNumberWithSuffix(marketCap, (NUM_SCREENS-2));
std::string priceString = currencySymbol + formatNumberWithSuffix(marketCap, (NUM_SCREENS - 2));
priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' ');
for (std::uint32_t i = firstIndex; i < NUM_SCREENS; i++)
@ -201,7 +312,7 @@ std::array<std::string, NUM_SCREENS> parseMarketCap(std::uint32_t blockHeight, s
ret[i] = "";
}
ret[NUM_SCREENS - groups - 1] = " $ ";
ret[NUM_SCREENS - groups - 1] = std::string(" ") + currencySymbol + " ";
for (std::uint32_t i = 0; i < groups; i++)
{
ret[(NUM_SCREENS - groups + i)] = stringValue.substr(i * 3, 3).c_str();
@ -212,50 +323,61 @@ std::array<std::string, NUM_SCREENS> parseMarketCap(std::uint32_t blockHeight, s
}
#ifdef __EMSCRIPTEN__
emscripten::val arrayToStringArray(const std::array<std::string, NUM_SCREENS>& arr) {
emscripten::val arrayToStringArray(const std::array<std::string, NUM_SCREENS> &arr)
{
emscripten::val jsArray = emscripten::val::array();
for (const auto& str : arr) {
for (const auto &str : arr)
{
jsArray.call<void>("push", str);
}
return jsArray;
}
emscripten::val vectorToStringArray(const std::vector<std::string>& vec) {
emscripten::val vectorToStringArray(const std::vector<std::string> &vec)
{
emscripten::val jsArray = emscripten::val::array();
for (size_t i = 0; i < vec.size(); ++i) {
for (size_t i = 0; i < vec.size(); ++i)
{
jsArray.set(i, vec[i]);
}
return jsArray;
}
emscripten::val parseBlockHeightArray(std::uint32_t blockHeight) {
emscripten::val parseBlockHeightArray(std::uint32_t blockHeight)
{
return arrayToStringArray(parseBlockHeight(blockHeight));
}
emscripten::val parsePriceDataArray(std::uint32_t price, const std::string& currencySymbol, bool useSuffixFormat = false) {
return arrayToStringArray(parsePriceData(price, currencySymbol[0], useSuffixFormat));
emscripten::val parsePriceDataArray(std::uint32_t price, const std::string &currencySymbol, bool useSuffixFormat = false, bool mowMode = false, bool shareDot = false)
{
return arrayToStringArray(parsePriceData(price, currencySymbol[0], useSuffixFormat, mowMode, shareDot));
}
emscripten::val parseHalvingCountdownArray(std::uint32_t blockHeight, bool asBlocks) {
emscripten::val parseHalvingCountdownArray(std::uint32_t blockHeight, bool asBlocks)
{
return arrayToStringArray(parseHalvingCountdown(blockHeight, asBlocks));
}
emscripten::val parseMarketCapArray(std::uint32_t blockHeight, std::uint32_t price, const std::string& currencySymbol, bool bigChars) {
emscripten::val parseMarketCapArray(std::uint32_t blockHeight, std::uint32_t price, const std::string &currencySymbol, bool bigChars)
{
return arrayToStringArray(parseMarketCap(blockHeight, price, currencySymbol[0], bigChars));
}
emscripten::val parseBlockFeesArray(std::uint16_t blockFees) {
emscripten::val parseBlockFeesArray(std::uint16_t blockFees)
{
return arrayToStringArray(parseBlockFees(blockFees));
}
emscripten::val parseSatsPerCurrencyArray(std::uint32_t price, const std::string& currencySymbol, bool withSatsSymbol) {
emscripten::val parseSatsPerCurrencyArray(std::uint32_t price, const std::string &currencySymbol, bool withSatsSymbol)
{
return arrayToStringArray(parseSatsPerCurrency(price, currencySymbol[0], withSatsSymbol));
}
EMSCRIPTEN_BINDINGS(my_module) {
// emscripten::register_vector<std::string>("StringList");
EMSCRIPTEN_BINDINGS(my_module)
{
// emscripten::register_vector<std::string>("StringList");
emscripten::function("parseBlockHeight", &parseBlockHeightArray);
emscripten::function("parseBlockHeight", &parseBlockHeightArray);
emscripten::function("parseHalvingCountdown", &parseHalvingCountdownArray);
emscripten::function("parseMarketCap", &parseMarketCapArray);
emscripten::function("parseBlockFees", &parseBlockFeesArray);

View file

@ -2,12 +2,31 @@
#include <string>
#include <cmath>
#include <cstdint>
#include <vector>
#include "utils.hpp"
std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, char currencySymbol, bool useSuffixFormat = false);
const char CURRENCY_USD = '$';
const char CURRENCY_EUR = '[';
const char CURRENCY_GBP = ']';
const char CURRENCY_JPY = '^';
const char CURRENCY_AUD = '_';
const char CURRENCY_CAD = '`';
const std::string CURRENCY_CODE_USD = "USD";
const std::string CURRENCY_CODE_EUR = "EUR";
const std::string CURRENCY_CODE_GBP = "GBP";
const std::string CURRENCY_CODE_JPY = "JPY";
const std::string CURRENCY_CODE_AUD = "AUD";
const std::string CURRENCY_CODE_CAD = "CAD";
std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, char currency, bool useSuffixFormat = false, bool mowMode = false, bool shareDot = false);
std::array<std::string, NUM_SCREENS> parseSatsPerCurrency(std::uint32_t price, char currencySymbol, bool withSatsSymbol);
std::array<std::string, NUM_SCREENS> parseBlockHeight(std::uint32_t blockHeight);
std::array<std::string, NUM_SCREENS> parseHalvingCountdown(std::uint32_t blockHeight, bool asBlocks);
std::array<std::string, NUM_SCREENS> parseMarketCap(std::uint32_t blockHeight, std::uint32_t price, char currencySymbol, bool bigChars);
std::array<std::string, NUM_SCREENS> parseBlockFees(std::uint16_t blockFees);
char getCurrencySymbol(char input);
std::string getCurrencyCode(char input);
char getCurrencyChar(const std::string& input);

View file

@ -0,0 +1,24 @@
#include "nostrdisplay_handler.hpp"
std::array<std::string, NUM_SCREENS> parseZapNotify(std::uint16_t amount, bool withSatsSymbol)
{
std::string text = std::to_string(amount);
std::size_t textLength = text.length();
std::size_t startIndex = NUM_SCREENS - textLength;
std::array<std::string, NUM_SCREENS> textEpdContent = {"ZAP", "mdi-lnbolt", "", "", "", "", ""};
// Insert the sats symbol just before the digits
if (startIndex > 0 && withSatsSymbol)
{
textEpdContent[startIndex - 1] = "STS";
}
// Place the digits
for (std::size_t i = 0; i < textLength; i++)
{
textEpdContent[startIndex + i] = text.substr(i, 1);
}
return textEpdContent;
}

View file

@ -0,0 +1,5 @@
#include <array>
#include <string>
#include "utils.hpp"
std::array<std::string, NUM_SCREENS> parseZapNotify(std::uint16_t amount, bool withSatsSymbol);

View file

@ -28,7 +28,12 @@ double getSupplyAtBlock(std::uint32_t blockNr)
return totalBitcoinInCirculation;
}
std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters)
std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters)
{
return formatNumberWithSuffix(num, numCharacters, false);
}
std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters, bool mowMode)
{
static char result[20]; // Adjust size as needed
const long long quadrillion = 1000000000000000LL;
@ -56,30 +61,185 @@ std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters)
numDouble /= billion;
suffix = 'B';
}
else if (num >= million || numDigits > 6)
else if (num >= million || numDigits > 6 || (mowMode && num >= thousand))
{
numDouble /= million;
suffix = 'M';
}
else if (num >= thousand || numDigits > 3)
else if (!mowMode && (num >= thousand || numDigits > 3))
{
numDouble /= thousand;
suffix = 'K';
}
else
else if (!mowMode)
{
sprintf(result, "%llu", (unsigned long long)num);
snprintf(result, sizeof(result), "%llu", (unsigned long long)num);
return result;
}
else // mowMode is true and num < 1000
{
numDouble /= million;
suffix = 'M';
}
// Add suffix
int len = snprintf(result, sizeof(result), "%.0f%c", numDouble, suffix);
int len;
// If there's room, add decimal places
// Mow Mode always uses string truncation to avoid rounding
std::string mowAsString = std::to_string(numDouble);
if (mowMode) {
// Default to one decimal place
len = snprintf(result, sizeof(result), "%s%c", mowAsString.substr(0, mowAsString.find(".") + 2).c_str(), suffix);
}
else
{
len = snprintf(result, sizeof(result), "%.0f%c", numDouble, suffix);
}
// If there's room, add more decimal places
if (len < numCharacters)
{
snprintf(result, sizeof(result), "%.*f%c", numCharacters - len - 1, numDouble, suffix);
int restLen = mowMode ? numCharacters - len : numCharacters - len - 1;
if (mowMode) {
snprintf(result, sizeof(result), "%s%c", mowAsString.substr(0, mowAsString.find(".") + 2 + restLen).c_str(), suffix);
}
else
{
snprintf(result, sizeof(result), "%.*f%c", restLen, numDouble, suffix);
}
}
return result;
}
}
/**
* Get sat amount from a bolt11 invoice
*
* Based on https://github.com/lnbits/nostr-zap-lamp/blob/main/nostrZapLamp/nostrZapLamp.ino
*/
int64_t getAmountInSatoshis(std::string bolt11) {
int64_t number = -1;
char multiplier = ' ';
for (unsigned int i = 0; i < bolt11.length(); ++i) {
if (isdigit(bolt11[i])) {
number = 0;
while (isdigit(bolt11[i])) {
number = number * 10 + (bolt11[i] - '0');
++i;
}
for (unsigned int j = i; j < bolt11.length(); ++j) {
if (isalpha(bolt11[j])) {
multiplier = bolt11[j];
break;
}
}
break;
}
}
if (number == -1 || multiplier == ' ') {
return -1;
}
int64_t satoshis = number;
switch (multiplier) {
case 'm':
satoshis *= 100000; // 0.001 * 100,000,000
break;
case 'u':
satoshis *= 100; // 0.000001 * 100,000,000
break;
case 'n':
satoshis /= 10; // 0.000000001 * 100,000,000
break;
case 'p':
satoshis /= 10000; // 0.000000000001 * 100,000,000
break;
default:
return -1;
}
return satoshis;
}
void parseHashrateString(const std::string& hashrate, std::string& label, std::string& output, unsigned int maxCharacters) {
// Handle empty string or "0" cases
if (hashrate.empty() || hashrate == "0") {
label = "H/S";
output = "0";
return;
}
size_t suffixLength = 0;
if (hashrate.length() > 21) {
label = "ZH/S";
suffixLength = 21;
} else if (hashrate.length() > 18) {
label = "EH/S";
suffixLength = 18;
} else if (hashrate.length() > 15) {
label = "PH/S";
suffixLength = 15;
} else if (hashrate.length() > 12) {
label = "TH/S";
suffixLength = 12;
} else if (hashrate.length() > 9) {
label = "GH/S";
suffixLength = 9;
} else if (hashrate.length() > 6) {
label = "MH/S";
suffixLength = 6;
} else if (hashrate.length() > 3) {
label = "KH/S";
suffixLength = 3;
} else {
label = "H/S";
suffixLength = 0;
}
double value = std::stod(hashrate) / std::pow(10, suffixLength);
// Calculate integer part length
int integerPartLength = std::to_string(static_cast<int>(value)).length();
// Calculate remaining space for decimals
int remainingSpace = maxCharacters - integerPartLength;
char buffer[32];
if (remainingSpace <= 0)
{
// No space for decimals, just round to integer
snprintf(buffer, sizeof(buffer), "%.0f", value);
}
else
{
// Space for decimal point and some decimals
snprintf(buffer, sizeof(buffer), "%.*f", remainingSpace - 1, value);
}
// Remove trailing zeros and decimal point if necessary
output = buffer;
if (output.find('.') != std::string::npos)
{
output = output.substr(0, output.find_last_not_of('0') + 1);
if (output.back() == '.')
{
output.pop_back();
}
}
}
int getHashrateMultiplier(char unit) {
if (unit == '0')
return 0;
static const std::unordered_map<char, int> multipliers = {
{'Z', 21}, {'E', 18}, {'P', 15}, {'T', 12},
{'G', 9}, {'M', 6}, {'K', 3}
};
return multipliers.at(unit);
}

View file

@ -5,9 +5,15 @@
#include <cstdint>
#include <sstream>
#include <iomanip>
#include <unordered_map>
int modulo(int x,int N);
double getSupplyAtBlock(std::uint32_t blockNr);
std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters = 4);
std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters = 4);
std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters, bool mowMode);
int64_t getAmountInSatoshis(std::string bolt11);
void parseHashrateString(const std::string& hashrate, std::string& label, std::string& output, unsigned int maxCharacters);
int getHashrateMultiplier(char unit);

20
maintainers.yaml Normal file
View file

@ -0,0 +1,20 @@
identifier: BTClock
maintainers:
- npub1k5f85zx0xdskyayqpfpc0zq6n7vwqjuuxugkayk72fgynp34cs3qfcvqg2
relays:
- wss://relay.noderunners.network/
- wss://nostr.sathoarder.com/
- wss://offchain.pub/
- wss://nostr3.daedaluslabs.io/
- wss://nostr4.daedaluslabs.io/
- wss://nostr.dbtc.link/
- wss://purplepag.es/
- wss://nos.lol/
- wss://nostr1.daedaluslabs.io/
- wss://nostr.noderunners.network/
- wss://nostr.lnbitcoin.cz/
- wss://relay.primal.net/
- wss://relay.damus.io
- wss://nostr-relay.derekross.me/
- wss://nostr2.azzamo.net/
- wss://nostr2.daedaluslabs.io/

View file

@ -1,7 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 36K, 20K,
otadata, data, ota, 56K, 8K,
app0, app, ota_0, 64K, 1700K,
app1, app, ota_1, , 1700K,
spiffs, data, spiffs, , 400K,
coredump, data, coredump,, 64K,
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x1b8000,
app1, app, ota_1, , 0x1b8000,
spiffs, data, spiffs, , 0x66C00,
coredump, data, coredump,, 0x10000,

1 # Name Type SubType Offset Size Flags
2 nvs data nvs 36K 0x9000 20K 0x5000
3 otadata data ota 56K 0xe000 8K 0x2000
4 app0 app ota_0 64K 0x10000 1700K 0x1b8000
5 app1 app ota_1 1700K 0x1b8000
6 spiffs data spiffs 400K 0x66C00
7 coredump data coredump 64K 0x10000

View file

@ -1,7 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 36K, 20K,
otadata, data, ota, 56K, 8K,
app0, app, ota_0, 64K, 4096K,
app1, app, ota_1, , 4096K,
spiffs, data, spiffs, , 3072K,
coredump, data, coredump,, 64K,
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x6F0000,
app1, app, ota_1, , 0x6F0000,
spiffs, data, spiffs, , 0x200000,
coredump, data, coredump,, 0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 36K 0x9000 20K 0x5000
3 otadata data ota 56K 0xe000 8K 0x2000
4 app0 app ota_0 64K 0x10000 4096K 0x6F0000
5 app1 app ota_1 4096K 0x6F0000
6 spiffs data spiffs 3072K 0x200000
7 coredump data coredump 64K 0x10000

View file

@ -1,7 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 36K, 20K,
otadata, data, ota, 56K, 8K,
app0, app, ota_0, 64K, 1700K,
app1, app, ota_1, , 1700K,
spiffs, data, spiffs, , 400K,
coredump, data, coredump,, 64K,
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x370000,
app1, app, ota_1, , 0x370000,
spiffs, data, spiffs, , 0xCD000,
coredump, data, coredump,, 0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 36K 0x9000 20K 0x5000
3 otadata data ota 56K 0xe000 8K 0x2000
4 app0 app ota_0 64K 0x10000 1700K 0x370000
5 app1 app ota_1 1700K 0x370000
6 spiffs data spiffs 400K 0xCD000
7 coredump data coredump 64K 0x10000

View file

@ -7,151 +7,190 @@
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
data_dir = data/build_gz
default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd
default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd, btclock_v8_213epd
[env]
[btclock_base]
platform = espressif32 @ ^6.6.0
framework = arduino, espidf
platform = espressif32 @ ^6.9.0
framework = arduino, espidf
monitor_speed = 115200
monitor_filters = esp32_exception_decoder, colorize
board_build.filesystem = littlefs
extra_scripts = post:scripts/extra_script.py
build_flags =
!python scripts/git_rev.py
-DLAST_BUILD_TIME=$UNIX_TIME
-DARDUINO_USB_CDC_ON_BOOT
-DCORE_DEBUG_LEVEL=0
-fexceptions
extra_scripts = pre:scripts/pre_script.py, post:scripts/extra_script.py
platform_packages =
earlephilhower/tool-mklittlefs-rp2040-earlephilhower
board_build.embed_files =
x509_crt_bundle
build_flags =
!python scripts/git_rev.py
-DLAST_BUILD_TIME=$UNIX_TIME
-DARDUINO_USB_CDC_ON_BOOT
-DCORE_DEBUG_LEVEL=0
-fexceptions
build_unflags =
-Werror=all
-fno-exceptions
-Werror=all
-fno-exceptions
lib_deps =
https://github.com/joltwallet/esp_littlefs.git
bblanchon/ArduinoJson@^7.1.0
esphome/Improv@^1.2.3
mathieucarbou/ESP Async WebServer@2.10.8
adafruit/Adafruit BusIO@^1.16.1
adafruit/Adafruit MCP23017 Arduino Library@^2.3.2
adafruit/Adafruit NeoPixel@^1.12.3
https://github.com/dsbaars/universal_pin
https://github.com/dsbaars/GxEPD2#universal_pin
https://github.com/tzapu/WiFiManager.git#v2.0.17
rblb/Nostrduino@^1.2.8
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/dsbaars/universal_pin#feature/mcp23017_rt
https://github.com/dsbaars/GxEPD2#universal_pin
https://github.com/tzapu/WiFiManager.git#v2.0.17
rblb/Nostrduino@1.2.8
[env:lolin_s3_mini]
extends = btclock_base
board = lolin_s3_mini
board_build.partitions = partition.csv
build_flags =
${btclock_base.build_flags}
-D MCP_INT_PIN=8
-D NEOPIXEL_PIN=34
-D NEOPIXEL_COUNT=4
-D NUM_SCREENS=7
-D I2C_SDA_PIN=35
-D I2C_SCK_PIN=36
-DARDUINO_USB_CDC_ON_BOOT=1
-D IS_HW_REV_A
build_flags =
${btclock_base.build_flags}
-D MCP_INT_PIN=8
-D NEOPIXEL_PIN=34
-D NEOPIXEL_COUNT=4
-D NUM_SCREENS=7
-D I2C_SDA_PIN=35
-D I2C_SCK_PIN=36
-DARDUINO_USB_CDC_ON_BOOT=1
-D IS_HW_REV_A
build_unflags =
${btclock_base.build_unflags}
${btclock_base.build_unflags}
platform_packages =
platformio/tool-mklittlefs@^1.203.210628
earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216
[env:btclock_rev_b]
extends = btclock_base
board = btclock_rev_b
board_build.partitions = partition.csv
build_flags =
${btclock_base.build_flags}
-D MCP_INT_PIN=8
-D NEOPIXEL_PIN=15
-D NEOPIXEL_COUNT=4
-D NUM_SCREENS=7
-D I2C_SDA_PIN=35
-D I2C_SCK_PIN=36
-D HAS_FRONTLIGHT
-D PCA_OE_PIN=45
-D PCA_I2C_ADDR=0x42
-D IS_HW_REV_B
board_build.partitions = partition_8mb.csv
build_flags =
${btclock_base.build_flags}
-D MCP_INT_PIN=8
-D NEOPIXEL_PIN=15
-D NEOPIXEL_COUNT=4
-D NUM_SCREENS=7
-D I2C_SDA_PIN=35
-D I2C_SCK_PIN=36
-D HAS_FRONTLIGHT
-D PCA_OE_PIN=45
-D PCA_I2C_ADDR=0x42
-D IS_HW_REV_B
lib_deps =
${btclock_base.lib_deps}
robtillaart/PCA9685@^0.7.1
claws/BH1750@^1.3.0
${btclock_base.lib_deps}
robtillaart/PCA9685@^0.7.1
claws/BH1750@^1.3.0
build_unflags =
${btclock_base.build_unflags}
${btclock_base.build_unflags}
platform_packages =
platformio/tool-mklittlefs@^1.203.210628
earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216
[env:lolin_s3_mini_213epd]
extends = env:lolin_s3_mini
test_framework = unity
build_flags =
${env:lolin_s3_mini.build_flags}
-D USE_QR
-D VERSION_EPD_2_13
-D HW_REV=\"REV_A_EPD_2_13\"
build_flags =
${env:lolin_s3_mini.build_flags}
-D USE_QR
-D VERSION_EPD_2_13
-D HW_REV=\"REV_A_EPD_2_13\"
platform_packages =
platformio/tool-mklittlefs@^1.203.210628
earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216
[env:btclock_rev_b_213epd]
extends = env:btclock_rev_b
test_framework = unity
build_flags =
${env:btclock_rev_b.build_flags}
-D USE_QR
-D VERSION_EPD_2_13
-D HW_REV=\"REV_B_EPD_2_13\"
build_flags =
${env:btclock_rev_b.build_flags}
-D USE_QR
-D VERSION_EPD_2_13
-D HW_REV=\"REV_B_EPD_2_13\"
platform_packages =
platformio/tool-mklittlefs@^1.203.210628
earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216
[env:lolin_s3_mini_29epd]
extends = env:lolin_s3_mini
test_framework = unity
build_flags =
${env:lolin_s3_mini.build_flags}
-D USE_QR
-D VERSION_EPD_2_9
-D HW_REV=\"REV_A_EPD_2_9\"
build_flags =
${env:lolin_s3_mini.build_flags}
-D USE_QR
-D VERSION_EPD_2_9
-D HW_REV=\"REV_A_EPD_2_9\"
platform_packages =
platformio/tool-mklittlefs@^1.203.210628
earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216
[env:btclock_rev_b_29epd]
extends = env:btclock_rev_b
test_framework = unity
build_flags =
${env:btclock_rev_b.build_flags}
-D USE_QR
-D VERSION_EPD_2_9
-D HW_REV=\"REV_B_EPD_2_9\"
build_flags =
${env:btclock_rev_b.build_flags}
-D USE_QR
-D VERSION_EPD_2_9
-D HW_REV=\"REV_B_EPD_2_9\"
platform_packages =
platformio/tool-mklittlefs@^1.203.210628
earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216
[env:btclock_s3]
[env:btclock_v8]
extends = btclock_base
board = btclock
board = btclock_v8
board_build.partitions = partition_16mb.csv
board_build.flash_mode = qio
test_framework = unity
build_flags =
${btclock_base.build_flags}
-D MCP_INT_PIN=4
-D NEOPIXEL_PIN=5
-D NEOPIXEL_COUNT=4
-D NUM_SCREENS=8
-D SPI_SDA_PIN=11
-D SPI_SCK_PIN=12
-D I2C_SDA_PIN=1
-D I2C_SCK_PIN=2
-D MCP_RESET_PIN=21
-D MCP1_A0_PIN=6
-D MCP1_A1_PIN=7
-D MCP1_A2_PIN=8
-D MCP2_A0_PIN=9
-D MCP2_A1_PIN=10
-D MCP2_A2_PIN=14
build_flags =
${btclock_base.build_flags}
-D MCP_INT_PIN=4
-D NEOPIXEL_PIN=5
-D NEOPIXEL_COUNT=4
-D NUM_SCREENS=8
-D SPI_SDA_PIN=11
-D SPI_SCK_PIN=12
-D I2C_SDA_PIN=1
-D I2C_SCK_PIN=2
-D MCP_RESET_PIN=21
-D MCP1_A0_PIN=6
-D MCP1_A1_PIN=7
-D MCP1_A2_PIN=8
-D MCP2_A0_PIN=9
-D MCP2_A1_PIN=10
-D MCP2_A2_PIN=14
build_unflags =
${btclock_base.build_unflags}
${btclock_base.build_unflags}
platform_packages =
platformio/tool-mklittlefs@^1.203.210628
earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216
[env:btclock_v8_213epd]
extends = env:btclock_v8
test_framework = unity
build_flags =
${env:btclock_v8.build_flags}
-D USE_QR
-D VERSION_EPD_2_13
-D HW_REV=\"REV_V8_EPD_2_13\"
platform_packages =
platformio/tool-mklittlefs@^1.203.210628
earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216
[env:native_test_only]
platform = native
test_framework = unity
build_flags =
${btclock_base.build_flags}
-D MCP_INT_PIN=8
-D NEOPIXEL_PIN=34
-D NEOPIXEL_COUNT=4
-D NUM_SCREENS=7
build_flags =
${btclock_base.build_flags}
-D MCP_INT_PIN=8
-D NEOPIXEL_PIN=34
-D NEOPIXEL_COUNT=4
-D NUM_SCREENS=7
-D UNITY_TEST
-std=gnu++17
platform_packages =
platformio/tool-mklittlefs@^1.203.210628
earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216

1
requirements.txt Normal file
View file

@ -0,0 +1 @@
platformio

View file

@ -1,10 +1,13 @@
Import("env")
import os
import gzip
from shutil import copyfileobj, rmtree
from shutil import copyfileobj, rmtree, copyfile, copytree
from pathlib import Path
import subprocess
revision = (
subprocess.check_output(["git", "rev-parse", "HEAD"])
.strip()
@ -26,7 +29,7 @@ def process_directory(input_dir, output_dir):
Path(output_root).mkdir(parents=True, exist_ok=True)
for file in files:
# if file.endswith(('.html', '.css', '.js')):
# if not file.endswith(('.bin')):
input_file_path = os.path.join(root, file)
output_file_path = os.path.join(output_root, file + '.gz')
gzip_file(input_file_path, output_file_path)
@ -38,10 +41,85 @@ def process_directory(input_dir, output_dir):
# Build web interface before building FS
def before_buildfs(source, target, env):
env.Execute("cd data && yarn && yarn postinstall && yarn build")
input_directory = 'data/dist'
output_directory = 'data/build_gz'
# copytree("assets", "data/dist/assets")
process_directory(input_directory, output_directory)
def get_fs_partition_size(env):
import csv
# Get partition table path - first try custom, then default
board_config = env.BoardConfig()
partition_table = board_config.get("build.partitions", "default.csv")
# Handle default partition table path
if partition_table == "default.csv" or partition_table == "huge_app.csv":
partition_table = os.path.join(env.PioPlatform().get_package_dir("framework-arduinoespressif32"),
"tools", "partitions", partition_table)
# Parse CSV to find spiffs/littlefs partition
with open(partition_table, 'r') as f:
for row in csv.reader(f):
if len(row) < 5:
continue
# Remove comments and whitespace
row = [cell.strip().split('#')[0] for cell in row]
# Check if this is a spiffs or littlefs partition
if row[0].startswith(('spiffs', 'littlefs')):
# Size is in hex format
return int(row[4], 16)
return 0
def get_littlefs_used_size(binary_path):
mklittlefs_path = os.path.join(env.PioPlatform().get_package_dir("tool-mklittlefs-rp2040-earlephilhower"), "mklittlefs")
try:
result = subprocess.run([mklittlefs_path, '-l', binary_path], capture_output=True, text=True)
if result.returncode == 0:
# Parse the output to sum up file sizes
total_size = 0
for line in result.stdout.splitlines():
if line.strip() and not line.startswith('<dir>') and not line.startswith('Creation'):
# Each line format: size filename
size = line.split()[0]
total_size += int(size)
return total_size
except Exception as e:
print(f"Error getting filesystem size: {e}")
return 0
def after_littlefs(source, target, env):
binary_path = str(target[0])
partition_size = get_fs_partition_size(env)
used_size = get_littlefs_used_size(binary_path)
percentage = (used_size / partition_size) * 100
bar_width = 50
filled = int(bar_width * percentage / 100)
bar = '=' * filled + '-' * (bar_width - filled)
print(f"\nLittleFS Actual Usage: [{bar}] {percentage:.1f}% ({used_size}/{partition_size} bytes)")
flash_size = env.BoardConfig().get("upload.flash_size", "4MB")
fs_image_name = f"littlefs_{flash_size}"
env.Replace(ESP32_FS_IMAGE_NAME=fs_image_name)
env.Replace(ESP8266_FS_IMAGE_NAME=fs_image_name)
os.environ["PUBLIC_BASE_URL"] = ""
env.AddPreAction("$BUILD_DIR/littlefs.bin", before_buildfs)
fs_name = env.get("ESP32_FS_IMAGE_NAME", "littlefs.bin")
# Or alternatively:
# fs_name = env.get("FSTOOLNAME", "littlefs.bin")
# Use the variable in the pre-action
env.AddPreAction(f"$BUILD_DIR/{fs_name}.bin", before_buildfs)
env.AddPostAction(f"$BUILD_DIR/{fs_name}.bin", after_littlefs)
# LittleFS Actual Usage: [==============================--------------------] 60.4% (254165/420864 bytes)
# LittleFS Actual Usage: [==============================--------------------] 60.2% (253476/420864 bytes)
# 372736 used

7
scripts/pre_script.py Normal file
View file

@ -0,0 +1,7 @@
Import("env")
flash_size = env.BoardConfig().get("upload.flash_size", "4MB")
fs_image_name = f"littlefs_{flash_size}"
env.Replace(ESP32_FS_IMAGE_NAME=fs_image_name)
env.Replace(ESP8266_FS_IMAGE_NAME=fs_image_name)

View file

@ -7,8 +7,8 @@ CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y
#CONFIG_FREERTOS_USE_TRACE_FACILITY=y
#CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
#CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=n
CONFIG_ESP_TLS_INSECURE=y
CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y
#CONFIG_ESP_TLS_INSECURE=y
#CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y
CONFIG_HEAP_CORRUPTION_DETECTION=CONFIG_HEAP_POISONING_LIGHT
CONFIG_HEAP_POISONING_LIGHT=y
@ -16,18 +16,14 @@ CONFIG_HEAP_POISONING_LIGHT=y
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
CONFIG_BOOTLOADER_LOG_LEVEL=0
CONFIG_LOG_BOOTLOADER_LEVEL_NONE=y
CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
CONFIG_LOG_DEFAULT_LEVEL_NONE=y
CONFIG_LOG_DEFAULT_LEVEL=0
CONFIG_LOG_MAXIMUM_LEVEL=0
CONFIG_LOG_BOOTLOADER_LEVEL_NONE=y
CONFIG_LOG_BOOTLOADER_LEVEL=0
CONFIG_CXX_EXCEPTIONS=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y
#CONFIG_BOOTLOADER_WDT_ENABLE=n
#CONFIG_TASK_WDT=n
#CONFIG_ESP_TASK_WDT=n
#Required for BTClock
#CONFIG_SPIRAM_MODE_OCT=y
@ -42,12 +38,11 @@ CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=12
CONFIG_ESP32_WIFI_RX_BA_WIN=6
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_RTC_CLK_CAL_CYCLES=576
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120
CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n
CONFIG_SPIRAM_CACHE_WORKAROUND=y
CONFIG_COMPILER_OPTIMIZATION_PERF=y
CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
#CONFIG_NEWLIB_NANO_FORMAT=y

View file

@ -1526,7 +1526,78 @@ const uint8_t Antonio_SemiBold40pt7bBitmaps[] PROGMEM = {
0x3F, 0xE0, 0x3F, 0xE0, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0x80, 0xFF, 0xFF,
0xFE, 0x00, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF,
0xC0, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0x80,
0x00};
0x00,
// euro
0x00, 0x07, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0x00,
0x07, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xFF, 0x00,
0xFF, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFE, 0x07, 0xFE, 0x07, 0xFC, 0x0F,
0xF8, 0x0F, 0xF8, 0x1F, 0xF0, 0x0F, 0xF8, 0x3F, 0xE0, 0x1F, 0xF0, 0xFF,
0x80, 0x3F, 0xE1, 0xFF, 0x00, 0x7F, 0xC3, 0xFE, 0x00, 0xFF, 0x87, 0xFC,
0x00, 0xFF, 0x0F, 0xF8, 0x01, 0xFE, 0x1F, 0xF0, 0x03, 0xFC, 0x3F, 0xE0,
0x07, 0xF8, 0x7F, 0xC0, 0x0F, 0xF0, 0xFF, 0x80, 0x1F, 0xE1, 0xFF, 0x00,
0x3F, 0xC3, 0xFE, 0x00, 0x7F, 0x87, 0xFC, 0x00, 0xFF, 0x0F, 0xF8, 0x01,
0xFE, 0x1F, 0xF0, 0x03, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0x80,
0x0F, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFC, 0x00,
0x07, 0xFC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00,
0x3F, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0x00, 0x1F,
0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x0F,
0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x03, 0xFC, 0x3F, 0xE0, 0x07, 0xF8, 0x7F,
0xC0, 0x0F, 0xF0, 0xFF, 0x80, 0x1F, 0xE1, 0xFF, 0x00, 0x3F, 0xC3, 0xFE,
0x00, 0x7F, 0x87, 0xFC, 0x00, 0xFF, 0x0F, 0xF8, 0x01, 0xFE, 0x1F, 0xF0,
0x03, 0xFC, 0x3F, 0xE0, 0x07, 0xF8, 0x7F, 0xC0, 0x0F, 0xF0, 0xFF, 0x80,
0x1F, 0xE1, 0xFF, 0x00, 0x7F, 0xC3, 0xFE, 0x00, 0xFF, 0x87, 0xFC, 0x01,
0xFF, 0x0F, 0xF8, 0x03, 0xFE, 0x0F, 0xF8, 0x07, 0xFC, 0x1F, 0xF0, 0x0F,
0xF0, 0x3F, 0xE0, 0x3F, 0xE0, 0x3F, 0xE0, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF,
0x80, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xF0,
0x00, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF8, 0x00,
0x00, 0x3F, 0x80, 0x00,
// pound
0x00, 0x0F, 0xFC, 0x00, 0x01, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFC, 0x00,
0x7F, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xC0, 0x7F,
0xFF, 0xFF, 0x83, 0xFF, 0x03, 0xFE, 0x0F, 0xF8, 0x07, 0xF8, 0x3F, 0xC0,
0x1F, 0xE1, 0xFF, 0x00, 0x3F, 0xC7, 0xFC, 0x00, 0xFF, 0x1F, 0xE0, 0x03,
0xFC, 0x7F, 0x80, 0x0F, 0xF1, 0xFE, 0x00, 0x3F, 0xC7, 0xF8, 0x00, 0xFF,
0x1F, 0xF0, 0x03, 0xFC, 0x7F, 0xC0, 0x0F, 0xF1, 0xFF, 0x00, 0x3F, 0xC3,
0xFC, 0x00, 0xFF, 0x0F, 0xF0, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0xFF,
0x80, 0x00, 0x01, 0xFE, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x1F, 0xF0,
0x00, 0x00, 0x7F, 0xC0, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x03, 0xFC, 0x00,
0x00, 0x0F, 0xF8, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x3F, 0xFF, 0xFF, 0xF8,
0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xFE, 0x3F,
0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFE, 0x00, 0x00, 0x0F,
0xF8, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x01, 0xFE,
0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x7F, 0xC0,
0x00, 0x01, 0xFF, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x1F, 0xF0, 0x00,
0x00, 0x7F, 0xC0, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00,
0x1F, 0xF0, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x0F,
0xF8, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x0F, 0xF8,
0x00, 0x00, 0x7F, 0xC0, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x1F, 0xF0, 0x00,
0x01, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF,
0x7F, 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xDF,
0xFF, 0xFF, 0xFF,
// yen
0xFF, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x07, 0xFB, 0xFE, 0x00, 0x1F, 0xF3,
0xFC, 0x00, 0x3F, 0xE7, 0xFC, 0x00, 0x7F, 0xCF, 0xF8, 0x00, 0xFF, 0x0F,
0xF0, 0x03, 0xFE, 0x1F, 0xE0, 0x07, 0xFC, 0x3F, 0xE0, 0x0F, 0xF0, 0x7F,
0xC0, 0x1F, 0xE0, 0x7F, 0x80, 0x7F, 0xC0, 0xFF, 0x80, 0xFF, 0x81, 0xFF,
0x01, 0xFE, 0x01, 0xFE, 0x03, 0xFC, 0x03, 0xFC, 0x07, 0xF8, 0x07, 0xFC,
0x1F, 0xF0, 0x0F, 0xF8, 0x3F, 0xC0, 0x0F, 0xF0, 0x7F, 0x80, 0x1F, 0xE0,
0xFF, 0x00, 0x3F, 0xE3, 0xFC, 0x00, 0x3F, 0xC7, 0xF8, 0x00, 0x7F, 0x8F,
0xF0, 0x00, 0xFF, 0x9F, 0xE0, 0x00, 0xFF, 0x7F, 0x80, 0x01, 0xFE, 0xFF,
0x00, 0x03, 0xFD, 0xFE, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xF0,
0x00, 0x0F, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x1F, 0xFF, 0x00,
0x00, 0x3F, 0xFE, 0x00, 0x00, 0x7F, 0xFC, 0x00, 0x00, 0xFF, 0xF8, 0x00,
0x00, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0x80, 0x07,
0xFF, 0xFF, 0xFE, 0x0F, 0xFF, 0xFF, 0xFC, 0x1F, 0xFF, 0xFF, 0xF8, 0x3F,
0xFF, 0xFF, 0xF0, 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x7F, 0xC0, 0x00, 0x00,
0xFF, 0x80, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x07,
0xFC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x7F, 0xFF,
0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF,
0xFF, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x07, 0xFC, 0x00, 0x00, 0x0F, 0xF8,
0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x7F, 0xC0,
0x00, 0x00, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x03, 0xFE, 0x00,
0x00, 0x07, 0xFC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x00,
0x00, 0x3F, 0xE0, 0x00, 0x00, 0x7F, 0xC0, 0x00
};
const GFXglyph Antonio_SemiBold40pt7bGlyphs[] PROGMEM = {
{0, 1, 1, 17, 0, 0}, // 0x20 ' '
@ -1588,10 +1659,10 @@ const GFXglyph Antonio_SemiBold40pt7bGlyphs[] PROGMEM = {
{10963, 28, 67, 32, 2, -66}, // 0x58 'X'
{11198, 31, 67, 32, 1, -66}, // 0x59 'Y'
{11458, 23, 67, 27, 3, -66}, // 0x5A 'Z'
{11651, 17, 70, 26, 6, -66}, // 0x5B '['
{11800, 24, 67, 30, 3, -66}, // 0x5C '\'
{12001, 16, 70, 26, 4, -66}, // 0x5D ']'
{12141, 29, 35, 37, 4, -66}, // 0x5E '^'
{18021, 31, 69, 37, 2, -67}, // 0x5B '[' --> euro { 18290, 31, 69, 37, 2, -67 } was {11651, 17, 70, 26, 6, -66}
{11800, 24, 67, 30, 3, -66}, // 0x5C '\'
{18557, 30, 68, 36, 3, -67 }, // 0x5D ']' --> pound { 0, 30, 68, 36, 3, -67 } was {12001, 16, 70, 26, 4, -66}
{18812, 31, 67, 32, 1, -66 }, // 0x5E '^' --> yen { 0, 31, 67, 32, 1, -66 } was {12141, 29, 35, 37, 4, -66
{12268, 25, 7, 29, 2, 2}, // 0x5F '_'
{12290, 12, 15, 16, 2, -77}, // 0x60 '`'
{12313, 27, 59, 36, 4, -57}, // 0x61 'a'
@ -1623,10 +1694,13 @@ const GFXglyph Antonio_SemiBold40pt7bGlyphs[] PROGMEM = {
{17509, 20, 75, 27, 4, -67}, // 0x7B '{'
{17697, 9, 75, 21, 6, -70}, // 0x7C '|'
{17782, 19, 75, 27, 4, -67}, // 0x7D '}'
{17961, 34, 14, 43, 4, -44}, {18021, 31, 69, 37, 2, -67}}; // 0x7E '~'
{17961, 34, 14, 43, 4, -44}}; // 0x7E '~'
//, {18021, 31, 69, 37, 2, -67}
const GFXfont Antonio_SemiBold40pt7b PROGMEM = {
(uint8_t *)Antonio_SemiBold40pt7bBitmaps,
(GFXglyph *)Antonio_SemiBold40pt7bGlyphs, 0x20, 0x7E, 101};
(GFXglyph *)Antonio_SemiBold40pt7bGlyphs, 0x20, 0x7E, 100};
// Approx. 18961 bytes

View file

@ -5029,7 +5029,231 @@ const uint8_t Antonio_SemiBold90pt7bBitmaps[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00,
0x00, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF,
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00,
0x00};
0x00,
// Pound sign (start 60325)
0x00, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F,
0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xE0,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03,
0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00,
0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0x00, 0x1F, 0xFF, 0xFF, 0x80, 0x0F,
0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xE0, 0x00,
0x0F, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xE0,
0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xC0,
0x00, 0x01, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF,
0xF0, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x01, 0xFF, 0xFF,
0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x0F,
0xFF, 0xFC, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0xFF,
0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x1F, 0xFF, 0xF8, 0x00, 0x00,
0x07, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0,
0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xF8, 0x0F, 0xFF, 0xFC, 0x00,
0x00, 0x01, 0xFF, 0xFF, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF,
0xE0, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFC, 0x07, 0xFF, 0xFE,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x1F,
0xFF, 0xF0, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x03, 0xFF,
0xFF, 0x80, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x7F, 0xFF, 0xF0, 0x00, 0x00,
0x0F, 0xFF, 0xF8, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x01,
0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xE0, 0x3F, 0xFF, 0xF8, 0x00,
0x00, 0x07, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF,
0x80, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFE,
0x00, 0x00, 0x03, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F,
0xFF, 0xC0, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF,
0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x1F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC,
0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFE, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x7F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFF,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF,
0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF,
0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF,
0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F,
0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80,
0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF,
0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF,
0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00,
0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0,
// yen-sign (start 61607)
0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0,
0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF9, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE3,
0xFF, 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x1F,
0xFF, 0xFE, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x1F,
0xFF, 0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x3F, 0xFF, 0xFC, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0xC0, 0x7F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0xFF,
0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xE0, 0x00,
0x00, 0x1F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF,
0xF8, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x07, 0xFF,
0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x07, 0xFF, 0xFF, 0x00, 0x00,
0x03, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFE,
0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x3F, 0xFF,
0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00,
0x7F, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xC0,
0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF,
0xE0, 0x00, 0x07, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x0F,
0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xF8, 0x00,
0x07, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x07, 0xFF, 0xFF,
0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x01, 0xFF,
0xFF, 0x80, 0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00,
0x1F, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xF8,
0x00, 0x1F, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x3F, 0xFF,
0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x00,
0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFF, 0xFF, 0xC0,
0x03, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFE,
0x00, 0x00, 0x03, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x07,
0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x00,
0x7F, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xC0,
0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F,
0xFF, 0xF8, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x0F,
0xFF, 0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF0, 0x1F, 0xFF, 0xF8, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x01, 0xFF,
0xFF, 0xC0, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x81, 0xFF,
0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x07, 0xFF, 0xFF, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x0F, 0xFF,
0xFE, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFE, 0x3F, 0xFF,
0xF0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x7F, 0xFF, 0xE0, 0x00, 0x00,
0x00, 0x3F, 0xFF, 0xF8, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF,
0xF3, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF7, 0xFF, 0xFE,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFC, 0x00, 0x00, 0x00,
0x00, 0xFF, 0xFF, 0xDF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF,
0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00,
0x07, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF,
0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00,
0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x3F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF,
0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x01,
0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF,
0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xC0,
0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00,
0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0xFC, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x07, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC,
0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xE0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x3F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0,
0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00,
0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F,
0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF,
0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xF8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xC0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00
};
const GFXglyph Antonio_SemiBold90pt7bGlyphs[] PROGMEM = {
{0, 1, 1, 38, 0, 0}, // 0x20 ' '
@ -5090,11 +5314,16 @@ const GFXglyph Antonio_SemiBold90pt7bGlyphs[] PROGMEM = {
{53398, 108, 151, 116, 4, 106}, // 0x57 'W'
{55437, 64, 151, 72, 4, 106}, // 0x58 'X'
{56645, 71, 151, 73, 1, 106}, // 0x59 'Y'
{57986, 52, 151, 61, 6, 106},
{58968, 70, 155, 84, 5, 104}}; // 0x5A 'Z'
{57986, 52, 151, 61, 6, 106}, // 0x5A 'Z'
{58968, 70, 155, 84, 5, 104}, // Euro-sign as [
{0, 0, 0, 0, 0, 0}, // Skip backslash
{60325, 67, 153, 82, 7, 104 }, // Pound-sign as ]
{61607, 71, 151, 73, 1, 106 }, // Yen-sign as ^
};
const GFXfont Antonio_SemiBold90pt7b PROGMEM = {
(uint8_t *)Antonio_SemiBold90pt7bBitmaps,
(GFXglyph *)Antonio_SemiBold90pt7bGlyphs, 0x20, 0x5B, 228};
(GFXglyph *)Antonio_SemiBold90pt7bGlyphs, 0x20, 0x5E, 231};
// Approx. 60745 bytes

View file

@ -1,7 +1,7 @@
#pragma once
#include "antonio-semibold20.h"
#include "antonio-semibold30.h"
//#include "antonio-semibold30.h"
#include "antonio-semibold40.h"
#include "antonio-semibold90.h"
#include "sats-symbol.h"

View file

@ -1,250 +1,122 @@
#include "icons.h"
// 'pickaxe', 122x250px
const unsigned char epd_icons_pickaxe [] PROGMEM = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x71, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x20, 0xe1, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x40, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xf0, 0x00, 0x00, 0x7f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xf8, 0xc0, 0x00, 0x7f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x7f, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0x00, 0x07, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x7f, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
// 'lightning-bolt', 122x122px
const unsigned char epd_icons_lightning_bolt [] PROGMEM = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
@ -253,7 +125,7 @@ const unsigned char epd_icons_pickaxe [] PROGMEM = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0
};
// 'rocket-launch', 122x250px
// 'rocket-launch', 122x122px
const unsigned char epd_icons_rocket_launch [] PROGMEM = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
@ -263,216 +135,213 @@ const unsigned char epd_icons_rocket_launch [] PROGMEM = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x0f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x03, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x03, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x01, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x01, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x7f, 0xff, 0x80, 0x00, 0x01, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xc0, 0x00, 0x01, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x00, 0x03, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x03, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x03, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x07, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xf9, 0xfc, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xe1, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0x83, 0xf0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xfe, 0x03, 0xf0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xf8, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xc0, 0x00, 0x3f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xe0, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xc0,
0xff, 0xff, 0xff, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xc0,
0xff, 0xff, 0xfc, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x7f, 0xff, 0xc0,
0xff, 0xff, 0xf0, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x00, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xc0, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0,
0xff, 0xfe, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xc0,
0xff, 0xf8, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0,
0xff, 0xe0, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0,
0xff, 0xf0, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0,
0xff, 0xfe, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x80, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xe0, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xfc, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xc7, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xe7, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xc3, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0x83, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0x01, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xfe, 0x03, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xfc, 0x07, 0xff, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xf8, 0x0f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xf0, 0x1f, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xe0, 0x3f, 0x9f, 0xe0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xc0, 0x7f, 0x0f, 0xf0, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x80, 0xfe, 0x07, 0xf8, 0x00, 0x00, 0x0f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x01, 0xfc, 0x03, 0xfc, 0x00, 0x00, 0x3f, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfe, 0x03, 0xf8, 0x01, 0xfe, 0x00, 0x00, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfc, 0x07, 0xf0, 0x00, 0xff, 0x00, 0x03, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf8, 0x0f, 0xe0, 0x00, 0x7f, 0x80, 0x0f, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf0, 0x1f, 0xc0, 0x00, 0x7f, 0xe0, 0x7f, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xe0, 0x3f, 0x80, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0x7f, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0xfe, 0x00, 0x03, 0xfb, 0xff, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc1, 0xfc, 0x00, 0x07, 0xf0, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc3, 0xf8, 0x00, 0x0f, 0xe0, 0x3f, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc7, 0xf0, 0x00, 0x1f, 0xc0, 0x7f, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xcf, 0xe0, 0x00, 0x3f, 0x80, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xdf, 0xc0, 0x00, 0x7f, 0x01, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x80, 0x00, 0xfe, 0x03, 0xff, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x00, 0x01, 0xfc, 0x07, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfe, 0x00, 0x03, 0xf8, 0x0f, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfc, 0x00, 0x07, 0xf0, 0x1f, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf8, 0x00, 0x0f, 0xe0, 0x3f, 0xff, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf0, 0x00, 0x1f, 0xc0, 0x7f, 0xff, 0xfc, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xe0, 0x00, 0x3f, 0x80, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0x00, 0x7f, 0x01, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0x00, 0xfe, 0x03, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0x01, 0xfc, 0x07, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0x03, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0x07, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0x0f, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0x1f, 0xc0, 0x7f, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xe0, 0x3f, 0x80, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xfe, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xf1, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xc1, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0x03, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xfc, 0x03, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xf0, 0x07, 0xc0, 0x00, 0x00, 0x07, 0xfe, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x80, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfe, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf8, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xe0, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x80, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x80, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xe0, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf8, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xc0, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xfc, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xf0, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xe0, 0x7e, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xc0, 0xff, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x81, 0xff, 0x80, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x03, 0xff, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfe, 0x07, 0xe7, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfc, 0x0f, 0xc3, 0xf0, 0x00, 0x00, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf8, 0x1f, 0x81, 0xf8, 0x00, 0x03, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf0, 0x3f, 0x00, 0xfc, 0x00, 0x0f, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xe0, 0x7e, 0x00, 0x7e, 0x00, 0x3f, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0xfc, 0x00, 0x3f, 0x80, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x81, 0xf8, 0x00, 0x7f, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x03, 0xf0, 0x00, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x07, 0xe0, 0x01, 0xf3, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x0f, 0xc0, 0x03, 0xe0, 0xff, 0x80, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x1f, 0x80, 0x07, 0xc0, 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x3f, 0x00, 0x0f, 0x81, 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x7e, 0x00, 0x1f, 0x03, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfc, 0x00, 0x3e, 0x07, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf8, 0x00, 0x7c, 0x0f, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf0, 0x00, 0xf8, 0x1f, 0xff, 0xc0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xe0, 0x01, 0xf0, 0x3f, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0x03, 0xe0, 0x7f, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x80, 0x07, 0xc0, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x00, 0x0f, 0x81, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x00, 0x1f, 0x03, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x00, 0x3e, 0x07, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x00, 0x7c, 0x0f, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x00, 0xf8, 0x1f, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x01, 0xf0, 0x3f, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0
};
// 'pickaxe', 122x122px
const unsigned char epd_icons_pickaxe [] PROGMEM = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x31, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xf8, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xfd, 0x80, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfe, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
@ -507,9 +376,167 @@ const unsigned char epd_icons_rocket_launch [] PROGMEM = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0
};
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8032)
const int epd_icons_allArray_LEN = 2;
const unsigned char* epd_icons_allArray[2] = {
epd_icons_pickaxe,
epd_icons_rocket_launch
const unsigned char epd_icons_bitaxe_logo [] PROGMEM = {
// 'bitaxe_dark copy', 88x220px
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3e, 0xff, 0xff, 0xfd, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe,
0x00, 0xff, 0xff, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xff, 0x00,
0x07, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff,
0xf8, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xff, 0xff,
0xf0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x0f, 0xff, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff,
0xff, 0xf0, 0x00, 0x07, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x03, 0xff,
0xff, 0xfc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff,
0xff, 0xff, 0xe0, 0x07, 0xf8, 0x7f, 0xff, 0xfd, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xfe,
0x3f, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0x1f, 0xff, 0xff, 0xf8, 0x07,
0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xcf, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0x80, 0x0f,
0xff, 0xe7, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xf3, 0xff, 0xff, 0xf0,
0x01, 0xff, 0xff, 0xff, 0xc0, 0x1f, 0xff, 0xf3, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xff, 0xf0,
0x1f, 0xff, 0xf9, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xfd, 0xff, 0xff,
0xe0, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xfd, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff,
0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff,
0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f,
0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xfc, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xfe,
0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 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, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xfe, 0x7f,
0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xfc, 0x7f, 0xff, 0xff,
0xff, 0xfc, 0x00, 0x00, 0x7f, 0x3f, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x7e,
0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x0f, 0xff,
0xff, 0xff, 0xf0, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00,
0x3c, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03,
0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf8,
0x1f, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xf0, 0x03, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x07,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80,
0x0f, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xfc, 0xff, 0xff,
0xff, 0xff, 0xfc, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00,
0x00, 0x3f, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f,
0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00,
0x01, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xc0,
0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xf8, 0x00,
0x00, 0x0f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff,
0xe0, 0x0f, 0xff, 0xf1, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xf9, 0xfc,
0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x3f, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff,
0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xfd, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xfe, 0x3f,
0xff, 0xf8, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1f, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xfc,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff,
0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff,
0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xf7, 0xff, 0xff, 0xfc, 0x3f, 0xff,
0xff, 0xff, 0xe0, 0x07, 0xff, 0xf3, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff,
0xf3, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xf3, 0xff, 0xff, 0xf8, 0x01,
0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xf9, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0x80, 0x1f,
0xff, 0xf9, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xff, 0xc0, 0x1f, 0xff, 0xfc, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xfe, 0x3f, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xff, 0xf8,
0x3f, 0xff, 0x7e, 0x1f, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xfc, 0xff, 0x03, 0xff,
0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff,
0xff, 0x80, 0x01, 0xff, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x03, 0xff, 0xe0,
0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0xff,
0xff, 0xff, 0xe0, 0x1f, 0xff, 0xf0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xff,
0xf8, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff,
0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff,
0xff, 0xfc, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x7f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
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, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff,
0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff,
0xff, 0x83, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff,
0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xf8, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xf8, 0x7f,
0xff, 0xf9, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xf0, 0xff, 0xf8, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xe0, 0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0xff, 0xc0, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x80, 0x1f, 0xf0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x80, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xff, 0xc0, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xe0, 0x3f,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x1f, 0xff, 0xf9, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff,
0xff, 0xf0, 0x1f, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xfd,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 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, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff,
0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff,
0xef, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xfc, 0x00,
0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00,
0x00, 0x07, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xf8,
0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x60, 0x00,
0x00, 0x00, 0x03, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff,
0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f,
0xff, 0xe0, 0x07, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xe0, 0x07, 0xff, 0xff,
0x80, 0x1f, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xc0, 0x07, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xf8,
0x0f, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x07, 0xff, 0x80, 0x0f, 0xff,
0xff, 0x00, 0x3f, 0xff, 0xff, 0xf8, 0x03, 0xff, 0x80, 0x1f, 0xff, 0xff, 0x80, 0x3f, 0xff, 0xff,
0xf0, 0x01, 0xff, 0xc0, 0x1f, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xf0, 0x1f,
0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xf8, 0x7f, 0xff,
0xff, 0xe0, 0x03, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xfe,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff,
0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f,
0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xe0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7f, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xe0, 0x3f, 0xfe, 0x00, 0x7f, 0xff, 0x80, 0x03, 0xff,
0xff, 0xff, 0xf8, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xfe,
0x00, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xfe, 0x00, 0xff, 0xff, 0xe0, 0x1f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff,
0xfe, 0x01, 0xff, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xfc,
0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff,
0xff, 0xfe, 0x03, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff,
0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff,
0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff,
0xbf, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xe0,
0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0x9f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xe7, 0xff, 0xff, 0xe7, 0xfd, 0xff, 0xff, 0xff, 0xff,
0xfe, 0x00, 0x1f, 0xff, 0xff, 0xfc, 0x73, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x7f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff
};
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 8032)
const int epd_icons_allArray_LEN = 4;
const unsigned char* epd_icons_allArray[epd_icons_allArray_LEN] = {
epd_icons_pickaxe,
epd_icons_rocket_launch,
epd_icons_lightning_bolt,
epd_icons_bitaxe_logo
};

View file

@ -2,54 +2,47 @@
char *wsServer;
esp_websocket_client_handle_t blockNotifyClient = NULL;
uint currentBlockHeight = 840000;
uint currentBlockHeight = 873400;
uint blockMedianFee = 1;
bool blockNotifyInit = false;
unsigned long int lastBlockUpdate;
// const char *mempoolWsCert = R"(-----BEGIN CERTIFICATE-----
// MIIHfTCCBmWgAwIBAgIRANFX3mhqRYDt1NFuENoSyaAwDQYJKoZIhvcNAQELBQAw
// gZUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
// BgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDE9MDsGA1UE
// AxM0U2VjdGlnbyBSU0EgT3JnYW5pemF0aW9uIFZhbGlkYXRpb24gU2VjdXJlIFNl
// cnZlciBDQTAeFw0yMzA3MjQwMDAwMDBaFw0yNDA4MjIyMzU5NTlaMFcxCzAJBgNV
// BAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEgMB4GA1UEChMXTUVNUE9PTCBTUEFDRSBD
// Ty4sIExURC4xFjAUBgNVBAMTDW1lbXBvb2wuc3BhY2UwggEiMA0GCSqGSIb3DQEB
// AQUAA4IBDwAwggEKAoIBAQCqmiPRWgo58d25R0biQjAksXMq5ciH7z7ZQo2w2AbB
// rHxpnlIry74b9S4wRY5UJeYmd6ZwA76NdSioDvxTJc29bLplY+Ftmfc4ET0zYb2k
// Fi86z7GOWb6Ezor/qez9uMM9cxd021Bvcs0/2OrL6Sgp66u9keDZv9NyvFPpXfuR
// tdV2r4HF57VJqZn105PN4k80kNWgDbae8aw+BuUNvQYKEe71yfB7Bh6zSh9pCSfM
// I6pIJdQzoada2uY1dQMoJeIq8qKNKqAPKGsH5McemUT5ZIKU/tjk3nfX0pz/sQa4
// CN7tLH6UeUlctei92GFd6Xtn7RbKLhDUbc4Sq02Cc9iXAgMBAAGjggQDMIID/zAf
// BgNVHSMEGDAWgBQX2dYlJ2f5McJJQ9kwNkSMbKlP6zAdBgNVHQ4EFgQUXkxoddJ6
// rKobsbmDdtuCK1ywXuIwDgYDVR0PAQH/BAQDAgWgMAwGA1UdEwEB/wQCMAAwHQYD
// VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMEoGA1UdIARDMEEwNQYMKwYBBAGy
// MQECAQMEMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgG
// BmeBDAECAjBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLnNlY3RpZ28uY29t
// L1NlY3RpZ29SU0FPcmdhbml6YXRpb25WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0Eu
// Y3JsMIGKBggrBgEFBQcBAQR+MHwwVQYIKwYBBQUHMAKGSWh0dHA6Ly9jcnQuc2Vj
// dGlnby5jb20vU2VjdGlnb1JTQU9yZ2FuaXphdGlvblZhbGlkYXRpb25TZWN1cmVT
// ZXJ2ZXJDQS5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29t
// MIIBgAYKKwYBBAHWeQIEAgSCAXAEggFsAWoAdwB2/4g/Crb7lVHCYcz1h7o0tKTN
// uyncaEIKn+ZnTFo6dAAAAYmc9m/gAAAEAwBIMEYCIQD8XOozx411S/bnZambGjTB
// yTcr2fCmggUfQLSmqksD5gIhAIjiEMg0o1VSuQW31gWzfzL6idCkIZeSKN104cdp
// xa4SAHcA2ra/az+1tiKfm8K7XGvocJFxbLtRhIU0vaQ9MEjX+6sAAAGJnPZwPwAA
// BAMASDBGAiEA2sPTZTzvxewzQ8vk36+BWAKuJS7AvJ5W3clvfwCa8OUCIQC74ekT
// Ged2fqQE4sVy74aS6HRA2ihC9VLtNrASJx1YjQB2AO7N0GTV2xrOxVy3nbTNE6Iy
// h0Z8vOzew1FIWUZxH7WbAAABiZz2cA8AAAQDAEcwRQIgEklH7wYCFuuJIFUHX5PY
// /vZ3bDoxOp+061PT3caa+rICIQC0abgfGlBKiHxp47JZxnW3wcVqWdiYX4ViLm9H
// xfx4ljCBxgYDVR0RBIG+MIG7gg1tZW1wb29sLnNwYWNlghMqLmZtdC5tZW1wb29s
// LnNwYWNlghMqLmZyYS5tZW1wb29sLnNwYWNlgg8qLm1lbXBvb2wuc3BhY2WCEyou
// dGs3Lm1lbXBvb2wuc3BhY2WCEyoudmExLm1lbXBvb2wuc3BhY2WCDGJpc3EubWFy
// a2V0c4IKYmlzcS5uaW5qYYIObGlxdWlkLm5ldHdvcmuCDGxpcXVpZC5wbGFjZYIN
// bWVtcG9vbC5uaW5qYTANBgkqhkiG9w0BAQsFAAOCAQEAFvOSRnlHDfq9C8acjZEG
// 5XIqjNYigyWyjOvx83of6Z3PBKkAZB5D/UHBPp+jBDJiEb/QXC7Z7Y7kpuvnoVib
// b4jDc0RjGEsxL+3F7cSw26m3wILJhhHooGZRmFY4GOAeCZtYCOTzJsiZvFpDoQjU
// hTBxtaps05z0Ly9/eYvkXnjnBNROZJVR+KYHlq4TIoGNc4q4KvpfHv2I/vhS2M1e
// bECNNPEyRxHGKdXXO3huocE7aVKpy+JDR6cWwDu6hpdc1j/SCDqdTDFQ7McHOrqA
// fpPh4FcfePMh7Mqxtg2pSs5pXPtiP0ZjLgxd7HbAXct8Y+/jGk+k3sx3SeYXVimr
// ew==
// -----END CERTIFICATE-----)";
const char *mempoolWsCert = R"EOF(
-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
jjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----
)EOF";
void setupBlockNotify()
{
@ -103,13 +96,18 @@ void setupBlockNotify()
esp_websocket_client_config_t config = {
// .uri = "wss://mempool.space/api/v1/ws",
// .task_stack = (6*1024),
// .cert_pem = mempoolWsCert,
.user_agent = USER_AGENT,
.task_stack = (6*1024),
.user_agent = USER_AGENT
};
if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) {
config.cert_pem = mempoolWsCert;
}
config.uri = mempoolUri.c_str();
Serial.printf("Connecting to %s\r\n", preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE));
blockNotifyClient = esp_websocket_client_init(&config);
esp_websocket_register_events(blockNotifyClient, WEBSOCKET_EVENT_ANY,
onWebsocketBlockEvent, blockNotifyClient);
@ -298,42 +296,27 @@ void restartBlockNotify()
}
int getBlockFetch()
{
try {
WiFiClientSecure client;
client.setInsecure();
int getBlockFetch() {
try {
String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "https" : "http";
String url = protocol + "://" + mempoolInstance + "/api/blocks/tip/height";
String mempoolInstance =
preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
HTTPClient* http = HttpHelper::begin(url);
Serial.println("Fetching block height from " + url);
int httpCode = http->GET();
// Get current block height through regular API
HTTPClient http;
const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "https" : "http";
if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE))
http.begin(client, protocol + "://" + mempoolInstance + "/api/blocks/tip/height");
else
http.begin(protocol + "://" + mempoolInstance + "/api/blocks/tip/height");
Serial.println("Fetching block height from " + protocol + "://" + mempoolInstance + "/api/blocks/tip/height");
int httpCode = http.GET();
if (httpCode > 0 && httpCode == HTTP_CODE_OK)
{
String blockHeightStr = http.getString();
return blockHeightStr.toInt();
} else {
Serial.println("HTTP code" + String(httpCode));
return 0;
if (httpCode > 0 && httpCode == HTTP_CODE_OK) {
String blockHeightStr = http->getString();
HttpHelper::end(http);
return blockHeightStr.toInt();
}
HttpHelper::end(http);
Serial.println("HTTP code" + String(httpCode));
} catch (...) {
Serial.println(F("An exception occurred while trying to get the latest block"));
}
}
catch (...) {
Serial.println(F("An exception occured while trying to get the latest block"));
}
return 2203; // B-T-C
return 2203; // B-T-C
}
uint getLastBlockUpdate()

View file

@ -11,6 +11,7 @@
#include "lib/led_handler.hpp"
#include "lib/screen_handler.hpp"
#include "lib/timers.hpp"
#include "lib/shared.hpp"
// using namespace websockets;

View file

@ -4,39 +4,52 @@ TaskHandle_t buttonTaskHandle = NULL;
const TickType_t debounceDelay = pdMS_TO_TICKS(50);
TickType_t lastDebounceTime = 0;
#ifdef IS_BTCLOCK_V8
#define BTN_1 256
#define BTN_2 512
#define BTN_3 1024
#define BTN_4 2048
#else
#define BTN_1 2048
#define BTN_2 1024
#define BTN_3 512
#define BTN_4 256
#endif
void buttonTask(void *parameter) {
while (1) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
std::lock_guard<std::mutex> lock(mcpMutex);
TickType_t currentTime = xTaskGetTickCount();
if ((currentTime - lastDebounceTime) >= debounceDelay) {
lastDebounceTime = currentTime;
if (!digitalRead(MCP_INT_PIN)) {
uint pin = mcp1.getLastInterruptPin();
uint pin = mcp1.getInterruptFlagRegister();
switch (pin) {
case 3:
case BTN_1:
toggleTimerActive();
break;
case 2:
case BTN_2:
nextScreen();
break;
case 1:
case BTN_3:
previousScreen();
break;
case 0:
case BTN_4:
showSystemStatusScreen();
break;
}
}
mcp1.clearInterrupts();
mcp1.getInterruptCaptureRegister();
} else {
}
// Very ugly, but for some reason this is necessary
while (!digitalRead(MCP_INT_PIN)) {
mcp1.clearInterrupts();
mcp1.getInterruptCaptureRegister();
}
}
}

View file

@ -4,6 +4,7 @@
#include "lib/screen_handler.hpp"
#include "lib/shared.hpp"
#include "lib/timers.hpp"
extern TaskHandle_t buttonTaskHandle;

View file

@ -2,10 +2,12 @@
#define MAX_ATTEMPTS_WIFI_CONNECTION 20
// zlib_turbo zt;
Preferences preferences;
Adafruit_MCP23X17 mcp1;
#ifdef IS_BTCLOCK_S3
Adafruit_MCP23X17 mcp2;
MCP23017 mcp1(0x20);
#ifdef IS_BTCLOCK_V8
MCP23017 mcp2(0x21);
#endif
#ifdef HAS_FRONTLIGHT
@ -18,8 +20,9 @@ std::vector<ScreenMapping> screenMappings;
std::mutex mcpMutex;
uint lastTimeSync;
void addScreenMapping(int value, const char* name) {
screenMappings.push_back({value, name});
void addScreenMapping(int value, const char *name)
{
screenMappings.push_back({value, name});
}
void setup()
@ -34,7 +37,7 @@ void setup()
}
{
std::lock_guard<std::mutex> lockMcp(mcpMutex);
if (mcp1.digitalRead(3) == LOW)
if (mcp1.read1(3) == LOW)
{
preferences.putBool("wifiConfigured", false);
preferences.remove("txPower");
@ -45,7 +48,7 @@ void setup()
}
{
if (mcp1.digitalRead(0) == LOW)
if (mcp1.read1(0) == LOW)
{
// Then loop forever to prevent anything else from writing to the screen
while (true)
@ -53,36 +56,50 @@ void setup()
delay(1000);
}
}
else if (mcp1.digitalRead(1) == LOW)
else if (mcp1.read1(1) == LOW)
{
preferences.clear();
queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS);
nvs_flash_erase();
delay(1000);
ESP.restart();
}
}
tryImprovSetup();
setupWifi();
// loadIcons();
setupWebserver();
// setupWifi();
syncTime();
finishSetup();
setupTasks();
setupTimers();
if (preferences.getBool("useNostr", DEFAULT_USE_NOSTR))
if (preferences.getBool("useNostr", DEFAULT_USE_NOSTR) || preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED))
{
setupNostrNotify();
setupNostrNotify(preferences.getBool("useNostr", DEFAULT_USE_NOSTR), preferences.getBool("nostrZapNotify", DEFAULT_ZAP_NOTIFY_ENABLED));
setupNostrTask();
}
else
if (!preferences.getBool("useNostr", DEFAULT_USE_NOSTR))
{
xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL,
tskIDLE_PRIORITY, NULL);
}
if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED))
{
setupBitaxeFetchTask();
}
if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED))
{
setupMiningPoolStatsFetchTask();
}
setupButtonTask();
setupOTA();
@ -97,9 +114,10 @@ void setup()
#endif
forceFullRefresh();
}
void tryImprovSetup()
void setupWifi()
{
WiFi.onEvent(WiFiEvent);
WiFi.setAutoConnect(true);
@ -123,7 +141,7 @@ void tryImprovSetup()
bool buttonPress = false;
{
std::lock_guard<std::mutex> lockMcp(mcpMutex);
buttonPress = (mcp1.digitalRead(2) == LOW);
buttonPress = (mcp1.read1(2) == LOW);
}
{
@ -134,10 +152,10 @@ void tryImprovSetup()
String softAP_SSID =
String("BTClock" + String(mac[5], 16) + String(mac[1], 16));
WiFi.setHostname(softAP_SSID.c_str());
String softAP_password =
String softAP_password = replaceAmbiguousChars(
base64::encode(String(mac[2], 16) + String(mac[4], 16) +
String(mac[5], 16) + String(mac[1], 16))
.substring(2, 10);
String(mac[5], 16) + String(mac[1], 16) + String(mac[3], 16))
.substring(2, 10));
wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT));
wm.setWiFiAutoReconnect(false);
@ -146,10 +164,10 @@ void tryImprovSetup()
wm.setAPCallback([&](WiFiManager *wifiManager)
{
// Serial.printf("Entered config mode:ip=%s, ssid='%s', pass='%s'\n",
// WiFi.softAPIP().toString().c_str(),
// wifiManager->getConfigPortalSSID().c_str(),
// softAP_password.c_str());
Serial.printf("Entered config mode:ip=%s, ssid='%s', pass='%s'\n",
WiFi.softAPIP().toString().c_str(),
wifiManager->getConfigPortalSSID().c_str(),
softAP_password.c_str());
// delay(6000);
setFgColor(GxEPD_BLACK);
setBgColor(GxEPD_WHITE);
@ -223,6 +241,9 @@ void tryImprovSetup()
// 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));
}
@ -254,7 +275,6 @@ void syncTime()
lastTimeSync = esp_timer_get_time() / 1000000;
}
void setupPreferences()
{
preferences.begin("btclock", false);
@ -262,48 +282,95 @@ void setupPreferences()
setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR));
setBgColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR));
setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT));
setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE));
setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD);
if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE))
setCurrentCurrency(preferences.getUChar("lastCurrency", CURRENCY_USD));
else
setCurrentCurrency(CURRENCY_USD);
if (!preferences.isKey("flDisable")) {
preferences.putBool("flDisable", isWhiteVersion() ? false : true);
}
if (!preferences.isKey("gitReleaseUrl")) {
preferences.putString("gitReleaseUrl", DEFAULT_GIT_RELEASE_URL);
}
if (!preferences.isKey("fgColor")) {
preferences.putUInt("fgColor", isWhiteVersion() ? GxEPD_BLACK : GxEPD_WHITE);
preferences.putUInt("bgColor", isWhiteVersion() ? GxEPD_WHITE : GxEPD_BLACK);
}
addScreenMapping(SCREEN_BLOCK_HEIGHT, "Block Height");
addScreenMapping(SCREEN_MSCW_TIME, "Sats per dollar");
addScreenMapping(SCREEN_BTC_TICKER, "Ticker");
addScreenMapping(SCREEN_TIME, "Time");
addScreenMapping(SCREEN_HALVING_COUNTDOWN, "Halving countdown");
addScreenMapping(SCREEN_MARKET_CAP, "Market Cap");
addScreenMapping(SCREEN_BLOCK_FEE_RATE, "Block Fee Rate");
addScreenMapping(SCREEN_SATS_PER_CURRENCY, "Sats per dollar");
addScreenMapping(SCREEN_BTC_TICKER, "Ticker");
addScreenMapping(SCREEN_MARKET_CAP, "Market Cap");
// addScreenMapping(SCREEN_SATS_PER_CURRENCY_USD, "Sats per USD");
// addScreenMapping(SCREEN_BTC_TICKER_USD, "Ticker USD");
// addScreenMapping(SCREEN_MARKET_CAP_USD, "Market Cap USD");
// addScreenMapping(SCREEN_SATS_PER_CURRENCY_EUR, "Sats per EUR");
// addScreenMapping(SCREEN_BTC_TICKER_EUR, "Ticker EUR");
// addScreenMapping(SCREEN_MARKET_CAP_EUR, "Market Cap EUR");
// screenNameMap[SCREEN_BLOCK_HEIGHT] = "Block Height";
// screenNameMap[SCREEN_BLOCK_FEE_RATE] = "Block Fee Rate";
// screenNameMap[SCREEN_MSCW_TIME] = "Sats per dollar";
// screenNameMap[SCREEN_SATS_PER_CURRENCY] = "Sats per dollar";
// screenNameMap[SCREEN_BTC_TICKER] = "Ticker";
// screenNameMap[SCREEN_TIME] = "Time";
// screenNameMap[SCREEN_HALVING_COUNTDOWN] = "Halving countdown";
// screenNameMap[SCREEN_MARKET_CAP] = "Market Cap";
if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED)) {
addScreenMapping(SCREEN_BITAXE_HASHRATE, "BitAxe Hashrate");
addScreenMapping(SCREEN_BITAXE_BESTDIFF, "BitAxe Best Difficulty");
// addCurrencyMappings(getActiveCurrencies());
if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED))
{
addScreenMapping(SCREEN_BITAXE_HASHRATE, "BitAxe Hashrate");
addScreenMapping(SCREEN_BITAXE_BESTDIFF, "BitAxe Best Difficulty");
}
if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED))
{
addScreenMapping(SCREEN_MINING_POOL_STATS_HASHRATE, "Mining Pool Hashrate");
if (getMiningPool()->supportsDailyEarnings()) {
addScreenMapping(SCREEN_MINING_POOL_STATS_EARNINGS, "Mining Pool Earnings");
}
}
}
String replaceAmbiguousChars(String input)
{
const char *ambiguous = "1IlO0";
const char *replacements = "LKQM8";
for (int i = 0; i < strlen(ambiguous); i++)
{
input.replace(ambiguous[i], replacements[i]);
}
return input;
}
void setupWebsocketClients(void *pvParameters)
{
setupBlockNotify();
if (preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE))
if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE))
{
setupPriceFetchTask();
V2Notify::setupV2Notify();
}
else
{
setupBlockNotify();
setupPriceNotify();
}
if (preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED))
{
setupBitaxeFetchTask();
}
vTaskDelete(NULL);
}
@ -331,12 +398,12 @@ std::vector<ScreenMapping> getScreenNameMap() { return screenMappings; }
void setupMcp()
{
#ifdef IS_BTCLOCK_S3
#ifdef IS_BTCLOCK_V8
const int mcp1AddrPins[] = {MCP1_A0_PIN, MCP1_A1_PIN, MCP1_A2_PIN};
const int mcp1AddrValues[] = {LOW, LOW, LOW};
const int mcp2AddrPins[] = {MCP2_A0_PIN, MCP2_A1_PIN, MCP2_A2_PIN};
const int mcp2AddrValues[] = {LOW, LOW, HIGH};
const int mcp2AddrValues[] = {HIGH, LOW, LOW};
pinMode(MCP_RESET_PIN, OUTPUT);
digitalWrite(MCP_RESET_PIN, HIGH);
@ -363,20 +430,28 @@ void setupHardware()
Serial.println(F("An Error has occurred while mounting LittleFS"));
}
if (HW_REV == "REV_B_EPD_2_13" && !isWhiteVersion()) {
Serial.println(F("Black Rev B"));
}
if (!LittleFS.open("/index.html.gz", "r"))
{
Serial.println(F("Error loading WebUI"));
}
// if (!LittleFS.exists("/qr.txt"))
// {
// 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();
@ -391,9 +466,9 @@ void setupHardware()
Wire.begin(I2C_SDA_PIN, I2C_SCK_PIN, 400000);
if (!mcp1.begin_I2C(0x20))
if (!mcp1.begin())
{
Serial.println(F("Error MCP23017"));
Serial.println(F("Error MCP23017 1"));
// while (1)
// ;
@ -401,30 +476,33 @@ void setupHardware()
else
{
pinMode(MCP_INT_PIN, INPUT_PULLUP);
mcp1.setupInterrupts(false, false, LOW);
// mcp1.setupInterrupts(false, false, LOW);
mcp1.enableControlRegister(MCP23x17_IOCR_ODR);
mcp1.mirrorInterrupts(true);
for (int i = 0; i < 4; i++)
{
mcp1.pinMode(i, INPUT_PULLUP);
mcp1.setupInterruptPin(i, LOW);
mcp1.pinMode1(i, INPUT_PULLUP);
mcp1.enableInterrupt(i, LOW);
}
#ifndef IS_BTCLOCK_S3
#ifndef IS_BTCLOCK_V8
for (int i = 8; i <= 14; i++)
{
mcp1.pinMode(i, OUTPUT);
mcp1.pinMode1(i, OUTPUT);
}
#endif
}
#ifdef IS_HW_REV_B
pinMode(39, INPUT_PULLUP);
pinMode(39, INPUT_PULLDOWN);
#endif
#ifdef IS_BTCLOCK_S3
if (!mcp2.begin_I2C(0x21))
#ifdef IS_BTCLOCK_V8
if (!mcp2.begin())
{
Serial.println(F("Error MCP23017"));
Serial.println(F("Error MCP23017 2"));
// while (1)
// ;
@ -441,222 +519,16 @@ void setupHardware()
{
Serial.println(F("Found BH1750"));
hasLuxSensor = true;
bh1750.begin(BH1750::CONTINUOUS_LOW_RES_MODE, 0x5C);
} else {
bh1750.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x5C);
}
else
{
Serial.println(F("BH1750 Not found"));
hasLuxSensor = false;
}
#endif
}
void improvGetAvailableWifiNetworks()
{
int networkNum = WiFi.scanNetworks();
for (int id = 0; id < networkNum; ++id)
{
std::vector<uint8_t> data = improv::build_rpc_response(
improv::GET_WIFI_NETWORKS,
{WiFi.SSID(id), String(WiFi.RSSI(id)),
(WiFi.encryptionType(id) == WIFI_AUTH_OPEN ? "NO" : "YES")},
false);
improv_send_response(data);
}
// final response
std::vector<uint8_t> data = improv::build_rpc_response(
improv::GET_WIFI_NETWORKS, std::vector<std::string>{}, false);
improv_send_response(data);
}
bool improv_connectWifi(std::string ssid, std::string password)
{
uint8_t count = 0;
WiFi.begin(ssid.c_str(), password.c_str());
while (WiFi.status() != WL_CONNECTED)
{
blinkDelay(500, 2);
if (count > MAX_ATTEMPTS_WIFI_CONNECTION)
{
WiFi.disconnect();
return false;
}
count++;
}
return true;
}
void onImprovErrorCallback(improv::Error err)
{
blinkDelayColor(100, 1, 255, 0, 0);
// pixels.setPixelColor(0, pixels.Color(255, 0, 0));
// pixels.setPixelColor(1, pixels.Color(255, 0, 0));
// pixels.setPixelColor(2, pixels.Color(255, 0, 0));
// pixels.setPixelColor(3, pixels.Color(255, 0, 0));
// pixels.show();
// vTaskDelay(pdMS_TO_TICKS(100));
// pixels.clear();
// pixels.show();
// vTaskDelay(pdMS_TO_TICKS(100));
}
std::vector<std::string> getLocalUrl()
{
return {// URL where user can finish onboarding or use device
// Recommended to use website hosted by device
String("http://" + WiFi.localIP().toString()).c_str()};
}
bool onImprovCommandCallback(improv::ImprovCommand cmd)
{
switch (cmd.command)
{
case improv::Command::GET_CURRENT_STATE:
{
if ((WiFi.status() == WL_CONNECTED))
{
improv_set_state(improv::State::STATE_PROVISIONED);
std::vector<uint8_t> data = improv::build_rpc_response(
improv::GET_CURRENT_STATE, getLocalUrl(), false);
improv_send_response(data);
}
else
{
improv_set_state(improv::State::STATE_AUTHORIZED);
}
break;
}
case improv::Command::WIFI_SETTINGS:
{
if (cmd.ssid.length() == 0)
{
improv_set_error(improv::Error::ERROR_INVALID_RPC);
break;
}
improv_set_state(improv::STATE_PROVISIONING);
queueLedEffect(LED_EFFECT_WIFI_CONNECTING);
if (improv_connectWifi(cmd.ssid, cmd.password))
{
queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS);
// std::array<String, NUM_SCREENS> epdContent = {"S", "U", "C", "C",
// "E", "S", "S"}; setEpdContent(epdContent);
preferences.putBool("wifiConfigured", true);
improv_set_state(improv::STATE_PROVISIONED);
std::vector<uint8_t> data = improv::build_rpc_response(
improv::WIFI_SETTINGS, getLocalUrl(), false);
improv_send_response(data);
delay(2500);
ESP.restart();
setupWebserver();
}
else
{
queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR);
improv_set_state(improv::STATE_STOPPED);
improv_set_error(improv::Error::ERROR_UNABLE_TO_CONNECT);
}
break;
}
case improv::Command::GET_DEVICE_INFO:
{
std::vector<std::string> infos = {// Firmware name
"BTClock",
// Firmware version
"1.0.0",
// Hardware chip/variant
"ESP32S3",
// Device name
"BTClock"};
std::vector<uint8_t> data =
improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
improv_send_response(data);
break;
}
case improv::Command::GET_WIFI_NETWORKS:
{
improvGetAvailableWifiNetworks();
// std::array<String, NUM_SCREENS> epdContent = {"W", "E", "B", "W", "I",
// "F", "I"}; setEpdContent(epdContent);
break;
}
default:
{
improv_set_error(improv::ERROR_UNKNOWN_RPC);
return false;
}
}
return true;
}
void improv_set_state(improv::State state)
{
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(11);
data[6] = improv::IMPROV_SERIAL_VERSION;
data[7] = improv::TYPE_CURRENT_STATE;
data[8] = 1;
data[9] = state;
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data[10] = checksum;
Serial.write(data.data(), data.size());
}
void improv_send_response(std::vector<uint8_t> &response)
{
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(9);
data[6] = improv::IMPROV_SERIAL_VERSION;
data[7] = improv::TYPE_RPC_RESPONSE;
data[8] = response.size();
data.insert(data.end(), response.begin(), response.end());
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data.push_back(checksum);
Serial.write(data.data(), data.size());
}
void improv_set_error(improv::Error error)
{
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(11);
data[6] = improv::IMPROV_SERIAL_VERSION;
data[7] = improv::TYPE_ERROR_STATE;
data[8] = 1;
data[9] = error;
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data[10] = checksum;
Serial.write(data.data(), data.size());
}
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
{
static bool first_connect = true;
@ -804,6 +676,7 @@ String getHwRev()
bool isWhiteVersion()
{
#ifdef IS_HW_REV_B
pinMode(39, INPUT_PULLDOWN);
return digitalRead(39);
#else
return false;
@ -823,11 +696,72 @@ String getFsRev()
return ret;
}
int findScreenIndexByValue(int value) {
for (int i = 0; i < screenMappings.size(); i++) {
if (screenMappings[i].value == value) {
return i;
}
int findScreenIndexByValue(int value)
{
for (int i = 0; i < screenMappings.size(); i++)
{
if (screenMappings[i].value == value)
{
return i;
}
}
return -1; // Return -1 if value is not found
}
std::vector<std::string> getAvailableCurrencies()
{
return {CURRENCY_CODE_USD, CURRENCY_CODE_EUR, CURRENCY_CODE_GBP, CURRENCY_CODE_JPY, CURRENCY_CODE_AUD, CURRENCY_CODE_CAD};
}
std::vector<std::string> getActiveCurrencies()
{
std::vector<std::string> result;
// Convert Arduino String to std::string
std::string stdString = preferences.getString("actCurrencies", DEFAULT_ACTIVE_CURRENCIES).c_str();
// Use a stringstream to split the string
std::stringstream ss(stdString);
std::string item;
// Split the string by comma and add each part to the vector
while (std::getline(ss, item, ','))
{
result.push_back(item);
}
return result;
}
bool isActiveCurrency(std::string &currency)
{
std::vector<std::string> ac = getActiveCurrencies();
if (std::find(ac.begin(), ac.end(), currency) != ac.end())
{
return true;
}
return false;
}
const char* getFirmwareFilename() {
if (HW_REV == "REV_B_EPD_2_13") {
return "btclock_rev_b_213epd_firmware.bin";
} else if (HW_REV == "REV_A_EPD_2_13") {
return "lolin_s3_mini_213epd_firmware.bin";
} else if (HW_REV == "REV_A_EPD_2_9") {
return "lolin_s3_mini_29epd_firmware.bin";
} else {
return "";
}
}
const char* getWebUiFilename() {
if (HW_REV == "REV_B_EPD_2_13") {
return "littlefs_8MB.bin";
} else if (HW_REV == "REV_A_EPD_2_13") {
return "littlefs_4MB.bin";
} else if (HW_REV == "REV_A_EPD_2_9") {
return "littlefs_4MB.bin";
} else {
return "littlefs_4MB.bin";
}
return -1; // Return -1 if value is not found
}

View file

@ -1,23 +1,26 @@
#pragma once
#include <Adafruit_MCP23X17.h>
#include <MCP23017.h>
#include <Arduino.h>
#include <Preferences.h>
#include <WiFiClientSecure.h>
#include <WiFiManager.h>
#include <base64.h>
#include <esp_task_wdt.h>
#include <nvs_flash.h>
#include <map>
#include "lib/block_notify.hpp"
#include "lib/button_handler.hpp"
#include "lib/epd.hpp"
#include "lib/improv.hpp"
// #include "lib/improv.hpp"
#include "lib/led_handler.hpp"
#include "lib/ota.hpp"
#include "lib/nostr_notify.hpp"
#include "lib/bitaxe_fetch.hpp"
#include "lib/mining_pool_stats_fetch.hpp"
#include "lib/v2_notify.hpp"
#include "lib/price_notify.hpp"
#include "lib/screen_handler.hpp"
@ -30,7 +33,6 @@
#define NTP_SERVER "pool.ntp.org"
#define DEFAULT_TIME_OFFSET_SECONDS 3600
#define USER_AGENT "BTClock/3.0"
#ifndef MCP_DEV_ADDR
#define MCP_DEV_ADDR 0x20
#endif
@ -42,7 +44,7 @@ uint getLastTimeSync();
void setupPreferences();
void setupWebsocketClients(void *pvParameters);
void setupHardware();
void tryImprovSetup();
void setupWifi();
void setupTimers();
void finishSetup();
void setupMcp();
@ -57,13 +59,18 @@ String getMyHostname();
std::vector<ScreenMapping> getScreenNameMap();
std::vector<std::string> getLocalUrl();
bool improv_connectWifi(std::string ssid, std::string password);
void improvGetAvailableWifiNetworks();
bool onImprovCommandCallback(improv::ImprovCommand cmd);
void onImprovErrorCallback(improv::Error err);
void improv_set_state(improv::State state);
void improv_send_response(std::vector<uint8_t> &response);
void improv_set_error(improv::Error error);
// bool improv_connectWifi(std::string ssid, std::string password);
// void improvGetAvailableWifiNetworks();
// bool onImprovCommandCallback(improv::ImprovCommand cmd);
// void onImprovErrorCallback(improv::Error err);
// void improv_set_state(improv::State state);
// void improv_send_response(std::vector<uint8_t> &response);
// void improv_set_error(improv::Error error);
//void addCurrencyMappings(const std::vector<std::string>& currencies);
std::vector<std::string> getActiveCurrencies();
std::vector<std::string> getAvailableCurrencies();
bool isActiveCurrency(std::string &currency);
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
String getHwRev();
@ -71,4 +78,11 @@ bool isWhiteVersion();
String getFsRev();
void addScreenMapping(int value, const char* name);
int findScreenIndexByValue(int value);
// void addScreenMapping(int value, const String& name);
// void addScreenMapping(int value, const std::string& name);
int findScreenIndexByValue(int value);
String replaceAmbiguousChars(String input);
const char* getFirmwareFilename();
const char* getWebUiFilename();
// void loadIcons();

View file

@ -15,7 +15,14 @@
#define DEFAULT_USE_BLOCK_COUNTDOWN true
#define DEFAULT_SUFFIX_PRICE false
#define DEFAULT_DISABLE_LEDS false
#define DEFAULT_DISABLE_FL false
#define DEFAULT_OWN_DATA_SOURCE true
#define DEFAULT_STAGING_SOURCE false
#define DEFAULT_MOW_MODE false
#define DEFAULT_SUFFIX_SHARE_DOT false
#define DEFAULT_V2_SOURCE_CURRENCY CURRENCY_USD
#define DEFAULT_TIME_OFFSET_SECONDS 3600
@ -24,7 +31,7 @@
#define DEFAULT_USE_NOSTR false
#define DEFAULT_NOSTR_NPUB "642317135fd4c4205323b9dea8af3270657e62d51dc31a657c0ec8aab31c6288"
#define DEFAULT_NOSTR_RELAY "wss://nostr.dbtc.link"
#define DEFAULT_NOSTR_RELAY "wss://relay.primal.net"
#define DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE 30
#define DEFAULT_MINUTES_FULL_REFRESH 60
@ -38,6 +45,8 @@
#define DEFAULT_FL_EFFECT_DELAY 15
#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
@ -49,3 +58,22 @@
#define DEFAULT_BITAXE_ENABLED false
#define DEFAULT_BITAXE_HOSTNAME "bitaxe1"
#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_ZAP_NOTIFY_ENABLED false
#define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422"
#define DEFAULT_LED_FLASH_ON_ZAP true
#define DEFAULT_FL_FLASH_ON_ZAP true
#define DEFAULT_HTTP_AUTH_ENABLED false
#define DEFAULT_HTTP_AUTH_USERNAME "btclock"
#define DEFAULT_HTTP_AUTH_PASSWORD "satoshi"
#define DEFAULT_ACTIVE_CURRENCIES "USD,EUR,JPY"
#define DEFAULT_GIT_RELEASE_URL "https://git.btclock.dev/api/v1/repos/btclock/btclock_v3/releases/latest"
#define DEFAULT_VERTICAL_DESC true
#define DEFAULT_MINING_POOL_LOGOS_URL "https://git.btclock.dev/btclock/mining-pool-logos/raw/branch/main"

View file

@ -30,7 +30,7 @@ MCP23X17_Pin EPD_RESET_MPD[NUM_SCREENS] = {
};
Native_Pin EPD_DC = Native_Pin(14);
#elif IS_BTCLOCK_S3
#elif IS_BTCLOCK_V8
Native_Pin EPD_DC = Native_Pin(38);
MCP23X17_Pin EPD_BUSY[NUM_SCREENS] = {
@ -104,8 +104,8 @@ GxEPD2_BW<EPD_CLASS, EPD_CLASS::HEIGHT> displays[NUM_SCREENS] = {
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_S3
EPD_CLASS(&EPD_CS[7], &EPD_DC, &EPD_RESET_MPD[6], &EPD_BUSY[7]),
#ifdef IS_BTCLOCK_V8
EPD_CLASS(&EPD_CS[7], &EPD_DC, &EPD_RESET_MPD[7], &EPD_BUSY[7]),
#endif
};
@ -132,6 +132,15 @@ 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()
{
for (uint i = 0; i < NUM_SCREENS; i++)
@ -170,7 +179,7 @@ void setupDisplays()
updateQueue = xQueueCreate(UPDATE_QUEUE_SIZE, sizeof(UpdateDisplayTaskItem));
xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", 2048, NULL, 11, NULL);
xTaskCreate(prepareDisplayUpdateTask, "PrepareUpd", EPD_TASK_STACK_SIZE*2, NULL, 11, NULL);
for (uint i = 0; i < NUM_SCREENS; i++)
{
@ -180,22 +189,25 @@ void setupDisplays()
int *taskParam = new int;
*taskParam = i;
xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), 2048, taskParam,
xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), EPD_TASK_STACK_SIZE, taskParam,
11, &tasks[i]); // create task
}
// Hold lower button to enable "storage mode" (prevents burn-in of ePaper displays)
if (mcp1.digitalRead(0) == LOW)
if (mcp1.read1(0) == LOW)
{
setFgColor(GxEPD_BLACK);
setBgColor(GxEPD_WHITE);
epdContent = {" ", " ", " ", " ", " ", " ", " "};
epdContent.fill("");
}
else
{
#ifdef IS_BTCLOCK_V8
epdContent = {"B", "T", "C", "L", "O", "C", "K", "v8"};
#else
epdContent = {"B", "T", "C", "L", "O", "C", "K"};
#endif
}
setEpdContent(epdContent);
@ -252,7 +264,7 @@ void prepareDisplayUpdateTask(void *pvParameters)
bool updatePartial = true;
if (strstr(epdContent[epdIndex].c_str(), "/") != NULL)
if (epdContent[epdIndex].length() > 1 && strstr(epdContent[epdIndex].c_str(), "/") != NULL)
{
String top = epdContent[epdIndex].substring(
0, epdContent[epdIndex].indexOf("/"));
@ -266,7 +278,10 @@ void prepareDisplayUpdateTask(void *pvParameters)
}
else if (epdContent[epdIndex].startsWith(F("mdi")))
{
renderIcon(epdIndex, epdContent[epdIndex], updatePartial);
bool updated = renderIcon(epdIndex, epdContent[epdIndex], updatePartial);
if (!updated) {
continue;
}
}
else if (epdContent[epdIndex].length() > 5)
{
@ -274,7 +289,10 @@ void prepareDisplayUpdateTask(void *pvParameters)
}
else
{
if (epdContent[epdIndex].length() > 1 && epdContent[epdIndex].indexOf(".") == -1)
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"))
{
@ -361,7 +379,11 @@ extern "C" void updateDisplay(void *pvParameters) noexcept
void splitText(const uint dispNum, const String &top, const String &bottom,
bool partial)
{
displays[dispNum].setRotation(2);
if(preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) {
displays[dispNum].setRotation(1);
} else {
displays[dispNum].setRotation(2);
}
displays[dispNum].setFont(&FONT_SMALL);
displays[dispNum].setTextColor(getFgColor());
@ -398,80 +420,83 @@ void splitText(const uint dispNum, const String &top, const String &bottom,
displays[dispNum].print(bottom);
}
void showDigit(const uint dispNum, char chr, bool partial,
const GFXfont *font)
{
String str(chr);
// 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());
}
if (chr == '.')
{
str = "!";
void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font) {
String str(chr);
if (chr == '.') {
str = "!";
}
setupDisplay(dispNum, font);
int16_t tbx, tby;
uint16_t tbw, tbh;
displays[dispNum].getTextBounds(str, 0, 0, &tbx, &tby, &tbw, &tbh);
uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx;
uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby;
displays[dispNum].setCursor(x, y);
displays[dispNum].print(str);
if (chr == '.') {
displays[dispNum].fillRect(x, y, displays[dispNum].width(),
round(displays[dispNum].height() * 0.9), getBgColor());
}
}
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;
}
}
displays[dispNum].setRotation(2);
displays[dispNum].setFont(font);
displays[dispNum].setTextColor(getFgColor());
int16_t tbx, tby;
uint16_t tbw, tbh;
displays[dispNum].getTextBounds(str, 0, 0, &tbx, &tby, &tbw, &tbh);
// 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;
// if (str.equals("."))
// {
// // int16_t yAdvance = font->yAdvance;
// // uint8_t charIndex = 46 - font->first;
// // GFXglyph *glyph = (&font->glyph)[charIndex];
// int16_t tbx2, tby2;
// uint16_t tbw2, tbh2;
// displays[dispNum].getTextBounds(".!", 0, 0, &tbx2, &tby2, &tbw2, &tbh2);
// y = ((displays[dispNum].height() - tbh2) / 2) - tby2;
// // Serial.print("yAdvance");
// // Serial.println(yAdvance);
// // if (glyph != nullptr) {
// // Serial.print("height");
// // Serial.println(glyph->height);
// // Serial.print("yOffset");
// // Serial.println(glyph->yOffset);
// // }
// // y = 250-99+18+19;
// }
displays[dispNum].fillScreen(getBgColor());
displays[dispNum].setCursor(x, y);
displays[dispNum].print(str);
if (chr == '.')
{
displays[dispNum].fillRect(x, y, displays[dispNum].width(), round(displays[dispNum].height() * 0.9), getBgColor());
}
// displays[dispNum].setCursor(10, 3);
// displays[dispNum].setFont(&FONT_SMALL);
// displays[dispNum].setTextColor(getFgColor());
// displays[dispNum].println("Y = " + y);
return maxDescent;
}
void showChars(const uint dispNum, const String &chars, bool partial,
const GFXfont *font)
{
displays[dispNum].setRotation(2);
displays[dispNum].setFont(font);
displays[dispNum].setTextColor(getFgColor());
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:
uint16_t x = ((displays[dispNum].width() - tbw) / 2) - tbx;
uint16_t y = ((displays[dispNum].height() - tbh) / 2) - tby;
displays[dispNum].fillScreen(getBgColor());
displays[dispNum].setCursor(x, y);
displays[dispNum].print(chars);
for (int i = 0; i < chars.length(); i++) {
char c = chars[i];
if (c == '.' || c == ',') {
// For the dot, calculate its specific descent
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 {
// For other characters, use the original y-position
displays[dispNum].setCursor(x, y);
displays[dispNum].print(c);
}
// Move x-position for the next character
x += font->glyph[c - font->first].xAdvance;
}
}
int getBgColor() { return bgColor; }
@ -517,21 +542,55 @@ void renderText(const uint dispNum, const String &text, bool partial)
}
}
void renderIcon(const uint dispNum, const String &text, bool partial)
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());
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("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());
displays[dispNum].drawInvertedBitmap(0,0, epd_icons_allArray[iconIndex], 122, 250, getFgColor());
}
@ -578,15 +637,12 @@ void waitUntilNoneBusy()
while (EPD_BUSY[i].digitalRead())
{
count++;
vTaskDelay(10);
if (count == 200)
{
// displays[i].init(0, false);
vTaskDelay(100);
}
else if (count > 205)
{
Serial.printf("Busy timeout %d", i);
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;
}
}

View file

@ -4,6 +4,7 @@
#include <Fonts/FreeSansBold9pt7b.h>
#include <GxEPD2_BW.h>
#include <mcp23x17_pin.hpp>
#include <mutex>
#include <native_pin.hpp>
@ -13,6 +14,7 @@
#include "lib/config.hpp"
#include "lib/shared.hpp"
#include "icons/icons.h"
#include "mining_pool_stats_fetch.hpp"
#ifdef USE_QR
#include "qrcodegen.h"
@ -43,7 +45,7 @@ int getFgColor();
void setBgColor(int color);
void setFgColor(int color);
void renderIcon(const uint dispNum, const String &text, bool partial);
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);

View file

@ -1,143 +0,0 @@
#include "improv.h"
namespace improv {
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data,
bool check_checksum) {
return parse_improv_data(data.data(), data.size(), check_checksum);
}
ImprovCommand parse_improv_data(const uint8_t *data, size_t length,
bool check_checksum) {
ImprovCommand improv_command;
Command command = (Command)data[0];
uint8_t data_length = data[1];
if (data_length != length - 2 - check_checksum) {
improv_command.command = UNKNOWN;
return improv_command;
}
if (check_checksum) {
uint8_t checksum = data[length - 1];
uint32_t calculated_checksum = 0;
for (uint8_t i = 0; i < length - 1; i++) {
calculated_checksum += data[i];
}
if ((uint8_t)calculated_checksum != checksum) {
improv_command.command = BAD_CHECKSUM;
return improv_command;
}
}
if (command == WIFI_SETTINGS) {
uint8_t ssid_length = data[2];
uint8_t ssid_start = 3;
size_t ssid_end = ssid_start + ssid_length;
uint8_t pass_length = data[ssid_end];
size_t pass_start = ssid_end + 1;
size_t pass_end = pass_start + pass_length;
std::string ssid(data + ssid_start, data + ssid_end);
std::string password(data + pass_start, data + pass_end);
return {.command = command, .ssid = ssid, .password = password};
}
improv_command.command = command;
return improv_command;
}
bool parse_improv_serial_byte(size_t position, uint8_t byte,
const uint8_t *buffer,
std::function<bool(ImprovCommand)> &&callback,
std::function<void(Error)> &&on_error) {
if (position == 0) return byte == 'I';
if (position == 1) return byte == 'M';
if (position == 2) return byte == 'P';
if (position == 3) return byte == 'R';
if (position == 4) return byte == 'O';
if (position == 5) return byte == 'V';
if (position == 6) return byte == IMPROV_SERIAL_VERSION;
if (position <= 8) return true;
uint8_t type = buffer[7];
uint8_t data_len = buffer[8];
if (position <= 8 + data_len) return true;
if (position == 8 + data_len + 1) {
uint8_t checksum = 0x00;
for (size_t i = 0; i < position; i++) checksum += buffer[i];
if (checksum != byte) {
on_error(ERROR_INVALID_RPC);
return false;
}
if (type == TYPE_RPC) {
auto command = parse_improv_data(&buffer[9], data_len, false);
return callback(command);
}
}
return false;
}
std::vector<uint8_t> build_rpc_response(Command command,
const std::vector<std::string> &datum,
bool add_checksum) {
std::vector<uint8_t> out;
uint32_t length = 0;
out.push_back(command);
for (const auto &str : datum) {
uint8_t len = str.length();
length += len + 1;
out.push_back(len);
out.insert(out.end(), str.begin(), str.end());
}
out.insert(out.begin() + 1, length);
if (add_checksum) {
uint32_t calculated_checksum = 0;
for (uint8_t byte : out) {
calculated_checksum += byte;
}
out.push_back(calculated_checksum);
}
return out;
}
#ifdef ARDUINO
std::vector<uint8_t> build_rpc_response(Command command,
const std::vector<String> &datum,
bool add_checksum) {
std::vector<uint8_t> out;
uint32_t length = 0;
out.push_back(command);
for (const auto &str : datum) {
uint8_t len = str.length();
length += len;
out.push_back(len);
out.insert(out.end(), str.begin(), str.end());
}
out.insert(out.begin() + 1, length);
if (add_checksum) {
uint32_t calculated_checksum = 0;
for (uint8_t byte : out) {
calculated_checksum += byte;
}
out.push_back(calculated_checksum);
}
return out;
}
#endif // ARDUINO
} // namespace improv

View file

@ -1,86 +0,0 @@
#pragma once
#ifdef ARDUINO
#include <Arduino.h>
#endif // ARDUINO
#include <cstdint>
#include <functional>
#include <string>
#include <vector>
namespace improv {
static const char *const SERVICE_UUID = "00467768-6228-2272-4663-277478268000";
static const char *const STATUS_UUID = "00467768-6228-2272-4663-277478268001";
static const char *const ERROR_UUID = "00467768-6228-2272-4663-277478268002";
static const char *const RPC_COMMAND_UUID =
"00467768-6228-2272-4663-277478268003";
static const char *const RPC_RESULT_UUID =
"00467768-6228-2272-4663-277478268004";
static const char *const CAPABILITIES_UUID =
"00467768-6228-2272-4663-277478268005";
enum Error : uint8_t {
ERROR_NONE = 0x00,
ERROR_INVALID_RPC = 0x01,
ERROR_UNKNOWN_RPC = 0x02,
ERROR_UNABLE_TO_CONNECT = 0x03,
ERROR_NOT_AUTHORIZED = 0x04,
ERROR_UNKNOWN = 0xFF,
};
enum State : uint8_t {
STATE_STOPPED = 0x00,
STATE_AWAITING_AUTHORIZATION = 0x01,
STATE_AUTHORIZED = 0x02,
STATE_PROVISIONING = 0x03,
STATE_PROVISIONED = 0x04,
};
enum Command : uint8_t {
UNKNOWN = 0x00,
WIFI_SETTINGS = 0x01,
IDENTIFY = 0x02,
GET_CURRENT_STATE = 0x02,
GET_DEVICE_INFO = 0x03,
GET_WIFI_NETWORKS = 0x04,
BAD_CHECKSUM = 0xFF,
};
static const uint8_t CAPABILITY_IDENTIFY = 0x01;
static const uint8_t IMPROV_SERIAL_VERSION = 1;
enum ImprovSerialType : uint8_t {
TYPE_CURRENT_STATE = 0x01,
TYPE_ERROR_STATE = 0x02,
TYPE_RPC = 0x03,
TYPE_RPC_RESPONSE = 0x04
};
struct ImprovCommand {
Command command;
std::string ssid;
std::string password;
};
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data,
bool check_checksum = true);
ImprovCommand parse_improv_data(const uint8_t *data, size_t length,
bool check_checksum = true);
bool parse_improv_serial_byte(size_t position, uint8_t byte,
const uint8_t *buffer,
std::function<bool(ImprovCommand)> &&callback,
std::function<void(Error)> &&on_error);
std::vector<uint8_t> build_rpc_response(Command command,
const std::vector<std::string> &datum,
bool add_checksum = true);
#ifdef ARDUINO
std::vector<uint8_t> build_rpc_response(Command command,
const std::vector<String> &datum,
bool add_checksum = true);
#endif // ARDUINO
} // namespace improv

View file

@ -13,6 +13,9 @@ bool flInTransition = false;
void frontlightFlash(int flDelayTime)
{
if (preferences.getBool("flDisable"))
return;
if (frontlightOn)
{
frontlightFadeOutAll(flDelayTime, true);
@ -65,6 +68,8 @@ void frontlightFadeInAll(int flDelayTime)
void frontlightFadeInAll(int flDelayTime, bool staggered)
{
if (preferences.getBool("flDisable"))
return;
if (frontlightIsOn())
return;
if (flInTransition)
@ -115,6 +120,8 @@ void frontlightFadeOutAll(int flDelayTime)
void frontlightFadeOutAll(int flDelayTime, bool staggered)
{
if (preferences.getBool("flDisable"))
return;
if (!frontlightIsOn())
return;
if (flInTransition)
@ -179,6 +186,8 @@ bool frontlightIsOn()
void frontlightFadeIn(uint num, int flDelayTime)
{
if (preferences.getBool("flDisable"))
return;
for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5)
{
flArray.setPWM(num, 0, dutyCycle);
@ -188,6 +197,8 @@ void frontlightFadeIn(uint num, int flDelayTime)
void frontlightFadeOut(uint num, int flDelayTime)
{
if (preferences.getBool("flDisable"))
return;
if (!frontlightIsOn())
return;
@ -221,9 +232,9 @@ void ledTask(void *parameter)
{
oldLights[i] = pixels.getPixelColor(i);
}
#ifdef HAS_FRONTLIGHT
#ifdef HAS_FRONTLIGHT
uint flDelayTime = preferences.getUInt("flEffectDelay");
#endif
#endif
switch (ledTaskParams)
{
case LED_POWER_TEST:
@ -269,7 +280,51 @@ void ledTask(void *parameter)
pixels.setPixelColor(3, pixels.Color(0, 255, 0));
pixels.show();
break;
case LED_EFFECT_NOSTR_ZAP:
{
#ifdef HAS_FRONTLIGHT
bool frontlightWasOn = false;
if (preferences.getBool("flFlashOnZap", DEFAULT_FL_FLASH_ON_ZAP))
{
if (frontlightOn)
{
frontlightWasOn = true;
frontlightFadeOutAll(flDelayTime, true);
}
else
{
frontlightFadeInAll(flDelayTime, true);
}
}
#endif
for (int flash = 0; flash < random(7, 10); flash++)
{
lightningStrike();
delay(random(50, 150));
}
// blinkDelayColor(250, 3, 142, 48, 235);
// blinkDelayTwoColor(250, 3, pixels.Color(142, 48, 235),
// pixels.Color(169, 21, 255));
#ifdef HAS_FRONTLIGHT
if (preferences.getBool("flFlashOnZap", DEFAULT_FL_FLASH_ON_ZAP))
{
vTaskDelay(pdMS_TO_TICKS(10));
if (frontlightWasOn)
{
frontlightFadeInAll(flDelayTime, true);
}
else
{
frontlightFadeOutAll(flDelayTime, true);
}
}
#endif
break;
}
case LED_FLASH_UPDATE:
blinkDelayTwoColor(250, 3, pixels.Color(0, 230, 0),
pixels.Color(230, 230, 0));
break;
case LED_FLASH_BLOCK_NOTIFY:
{
@ -620,4 +675,29 @@ void ledTheaterChaseRainbow(int wait)
}
}
void lightningStrike()
{
uint32_t PURPLE = pixels.Color(128, 0, 128);
uint32_t YELLOW = pixels.Color(255, 226, 41);
// Randomly choose which LEDs to light up
for (int i = 0; i < pixels.numPixels(); i++)
{
if (random(2) == 0)
{ // 50% chance for each LED
pixels.setPixelColor(i, YELLOW);
}
else
{
pixels.setPixelColor(i, PURPLE);
}
}
pixels.show();
delay(random(10, 50)); // Flash duration
// Return to purple background
// setAllPixels(PURPLE);
}
Adafruit_NeoPixel getPixels() { return pixels; }

View file

@ -28,6 +28,7 @@ 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;
@ -35,6 +36,9 @@ 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;
@ -57,6 +61,7 @@ void ledRainbow(int wait);
void ledTheaterChaseRainbow(int wait);
void ledTheaterChase(uint32_t color, int wait);
Adafruit_NeoPixel getPixels();
void lightningStrike();
#ifdef HAS_FRONTLIGHT
void frontlightFlash(int flDelayTime);

View file

@ -0,0 +1,32 @@
#include "brains_pool.hpp"
void BraiinsPool::prepareRequest(HTTPClient& http) const {
http.addHeader("Pool-Auth-Token", poolUser.c_str());
}
std::string BraiinsPool::getApiUrl() const {
return "https://pool.braiins.com/accounts/profile/json/btc/";
}
PoolStats BraiinsPool::parseResponse(const JsonDocument &doc) const
{
if (doc["btc"].isNull()) {
return PoolStats{
.hashrate = "0",
.dailyEarnings = 0
};
}
std::string unit = doc["btc"]["hash_rate_unit"].as<std::string>();
static const std::unordered_map<std::string, int> multipliers = {
{"Zh/s", 21}, {"Eh/s", 18}, {"Ph/s", 15}, {"Th/s", 12}, {"Gh/s", 9}, {"Mh/s", 6}, {"Kh/s", 3}};
int multiplier = multipliers.at(unit);
float hashValue = doc["btc"]["hash_rate_5m"].as<float>();
return PoolStats{
.hashrate = std::to_string(static_cast<int>(std::round(hashValue))) + std::string(multiplier, '0'),
.dailyEarnings = static_cast<int64_t>(doc["btc"]["today_reward"].as<float>() * 100000000)};
}

View file

@ -0,0 +1,33 @@
#pragma once
#include "lib/mining_pool/mining_pool_interface.hpp"
#include <icons/icons.h>
#include <utils.hpp>
class BraiinsPool : public MiningPoolInterface
{
public:
void setPoolUser(const std::string &user) override { poolUser = user; }
void prepareRequest(HTTPClient &http) const override;
std::string getApiUrl() const override;
PoolStats parseResponse(const JsonDocument &doc) const override;
bool supportsDailyEarnings() const override { return true; }
bool hasLogo() const override { return true; }
std::string getDisplayLabel() const override { return "BRAIINS/POOL"; } // Fallback if needed
std::string getDailyEarningsLabel() const override { return "sats/earned"; }
std::string getLogoFilename() const override {
return "braiins.bin";
}
std::string getPoolName() const override {
return "braiins";
}
int getLogoWidth() const override {
return 37;
}
int getLogoHeight() const override {
return 230;
}
};

View file

@ -0,0 +1,6 @@
// src/noderunners/noderunners_pool.cpp
#include "gobrrr_pool.hpp"
std::string GoBrrrPool::getApiUrl() const {
return "https://pool.gobrrr.me/api/client/" + poolUser;
}

View file

@ -0,0 +1,30 @@
#pragma once
#include "lib/mining_pool/mining_pool_interface.hpp"
#include "lib/mining_pool/public_pool/public_pool.hpp"
#include <icons/icons.h>
class GoBrrrPool : public PublicPool {
public:
std::string getApiUrl() const override;
bool hasLogo() const override { return true; }
std::string getDisplayLabel() const override { return "GOBRRR/POOL"; }
std::string getLogoFilename() const override {
return "gobrrr.bin";
}
std::string getPoolName() const override {
return "gobrrr_pool";
}
int getLogoWidth() const override {
return 122;
}
int getLogoHeight() const override {
return 122;
}
};

View file

@ -0,0 +1,11 @@
#pragma once
#include <cstdint>
#include <stddef.h>
struct LogoData {
const uint8_t* data;
size_t width;
size_t height;
size_t size;
};

View file

@ -0,0 +1,18 @@
#include "mining_pool_interface.hpp"
#include "pool_factory.hpp"
LogoData MiningPoolInterface::getLogo() const {
if (!hasLogo()) {
return LogoData{nullptr, 0, 0, 0};
}
// Check if logo exists
String logoPath = String(PoolFactory::getLogosDir()) + "/" + String(getPoolName().c_str()) + "_logo.bin";
if (!LittleFS.exists(logoPath)) {
return LogoData{nullptr, 0, 0, 0};
}
// Now load the logo (whether it was just downloaded or already existed)
return PoolFactory::loadLogoFromFS(getPoolName(), this);
}

View file

@ -0,0 +1,35 @@
#pragma once
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "pool_stats.hpp"
#include "logo_data.hpp"
#include "lib/shared.hpp"
class MiningPoolInterface {
public:
virtual ~MiningPoolInterface() = default;
virtual void setPoolUser(const std::string& user) = 0;
virtual void prepareRequest(HTTPClient& http) const = 0;
virtual std::string getApiUrl() const = 0;
virtual PoolStats parseResponse(const JsonDocument& doc) const = 0;
virtual bool hasLogo() const = 0;
virtual LogoData getLogo() const;
virtual std::string getDisplayLabel() const = 0;
virtual bool supportsDailyEarnings() const = 0;
virtual std::string getDailyEarningsLabel() const = 0;
virtual std::string getLogoFilename() const { return ""; }
virtual std::string getPoolName() const = 0;
virtual int getLogoWidth() const { return 0; }
virtual int getLogoHeight() const { return 0; }
std::string getLogoUrl() const {
if (!hasLogo() || getLogoFilename().empty()) {
return "";
}
std::string baseUrl = preferences.getString("poolLogosUrl", DEFAULT_MINING_POOL_LOGOS_URL).c_str();
return baseUrl + "/" + getLogoFilename().c_str();
}
protected:
std::string poolUser;
};

View file

@ -0,0 +1,95 @@
#include "mining_pool_stats_handler.hpp"
std::array<std::string, NUM_SCREENS> parseMiningPoolStatsHashRate(const std::string& hashrate, const MiningPoolInterface& pool)
{
std::array<std::string, NUM_SCREENS> ret;
ret.fill(""); // Initialize all elements to empty strings
std::string label;
std::string output;
parseHashrateString(hashrate, label, output, 4);
std::size_t textLength = output.length();
// Calculate the position where the digits should start
// Account for the position of the mining pool logo and the hashrate label
std::size_t startIndex = NUM_SCREENS - 1 - textLength;
// Insert the pickaxe icon just before the digits
if (startIndex > 0)
{
ret[startIndex - 1] = "mdi:pickaxe";
}
// Place the digits
for (std::size_t i = 0; i < textLength; ++i)
{
ret[startIndex + i] = output.substr(i, 1);
}
ret[NUM_SCREENS - 1] = label;
if (pool.hasLogo()) {
ret[0] = "mdi:miningpool";
} else {
ret[0] = pool.getDisplayLabel();
}
return ret;
}
std::array<std::string, NUM_SCREENS> parseMiningPoolStatsDailyEarnings(int sats, std::string label, const MiningPoolInterface& pool)
{
std::array<std::string, NUM_SCREENS> ret;
ret.fill(""); // Initialize all elements to empty strings
std::string satsDisplay = std::to_string(sats);
if (sats >= 100000000) {
// A whale mining 1+ BTC per day! No decimal points; whales scoff at such things.
label = "BTC" + label.substr(4);
satsDisplay = satsDisplay.substr(0, satsDisplay.length() - 8);
} else if (sats >= 10000000) {
// 10.0M to 99.9M you get one decimal point
satsDisplay = satsDisplay.substr(0, satsDisplay.length() - 6) + "." + satsDisplay[2] + "M";
} else if (sats >= 1000000) {
// 1.00M to 9.99M you get two decimal points
satsDisplay = satsDisplay.substr(0, satsDisplay.length() - 6) + "." + satsDisplay.substr(2, 2) + "M";
} else if (sats >= 100000) {
// 100K to 999K you get no extra precision
satsDisplay = satsDisplay.substr(0, satsDisplay.length() - 3) + "K";
} else if (sats >= 10000) {
// 10.0K to 99.9K you get one decimal point
satsDisplay = satsDisplay.substr(0, satsDisplay.length() - 3) + "." + satsDisplay[2] + "K";
} else {
// Pleb miner! 4 digit or fewer sats will fit as-is. no-op.
}
std::size_t textLength = satsDisplay.length();
// Calculate the position where the digits should start
// Account for the position of the mining pool logo
std::size_t startIndex = NUM_SCREENS - 1 - textLength;
// Insert the pickaxe icon just before the digits if there's room
if (startIndex > 0)
{
ret[startIndex - 1] = "mdi:pickaxe";
}
// Place the digits
for (std::size_t i = 0; i < textLength; ++i)
{
ret[startIndex + i] = satsDisplay.substr(i, 1);
}
ret[NUM_SCREENS - 1] = label;
if (pool.hasLogo()) {
ret[0] = "mdi:miningpool";
} else {
ret[0] = pool.getDisplayLabel();
}
return ret;
}

View file

@ -0,0 +1,11 @@
#include <array>
#include <string>
#include <iostream>
#include <utils.hpp>
#ifndef UNITY_TEST
#include "lib/mining_pool/mining_pool_interface.hpp"
#endif
std::array<std::string, NUM_SCREENS> parseMiningPoolStatsHashRate(const std::string& hashrate, const MiningPoolInterface& pool);
std::array<std::string, NUM_SCREENS> parseMiningPoolStatsDailyEarnings(int sats, std::string label, const MiningPoolInterface& pool);

View file

@ -0,0 +1,27 @@
// src/noderunners/noderunners_pool.cpp
#include "noderunners_pool.hpp"
void NoderunnersPool::prepareRequest(HTTPClient& http) const {
// Empty as NodeRunners doesn't need special headers
}
std::string NoderunnersPool::getApiUrl() const {
return "https://pool.noderunners.network/api/v1/users/" + poolUser;
}
PoolStats NoderunnersPool::parseResponse(const JsonDocument& doc) const {
std::string hashrateStr = doc["hashrate1m"].as<std::string>();
char unit = hashrateStr.back();
std::string value = hashrateStr.substr(0, hashrateStr.size() - 1);
int multiplier = getHashrateMultiplier(unit);
double hashrate = std::stod(value) * std::pow(10, multiplier);
char buffer[32];
snprintf(buffer, sizeof(buffer), "%.0f", hashrate);
return PoolStats{
.hashrate = buffer,
.dailyEarnings = std::nullopt
};
}

View file

@ -0,0 +1,33 @@
#pragma once
#include "lib/mining_pool/mining_pool_interface.hpp"
#include <icons/icons.h>
#include <utils.hpp>
class NoderunnersPool : public MiningPoolInterface {
public:
void setPoolUser(const std::string& user) override { poolUser = user; }
void prepareRequest(HTTPClient& http) const override;
std::string getApiUrl() const override;
PoolStats parseResponse(const JsonDocument& doc) const override;
bool supportsDailyEarnings() const override { return false; }
std::string getDailyEarningsLabel() const override { return ""; }
bool hasLogo() const override { return true; }
std::string getDisplayLabel() const override { return "NODE/RUNNERS"; } // Fallback if needed
std::string getLogoFilename() const override {
return "noderunners.bin";
}
std::string getPoolName() const override {
return "noderunners";
}
int getLogoWidth() const override {
return 122;
}
int getLogoHeight() const override {
return 122;
}
};

View file

@ -0,0 +1,18 @@
#include "ocean_pool.hpp"
void OceanPool::prepareRequest(HTTPClient& http) const {
// Empty as Ocean doesn't need special headers
}
std::string OceanPool::getApiUrl() const {
return "https://api.ocean.xyz/v1/statsnap/" + poolUser;
}
PoolStats OceanPool::parseResponse(const JsonDocument& doc) const {
return PoolStats{
.hashrate = doc["result"]["hashrate_300s"].as<std::string>(),
.dailyEarnings = static_cast<int64_t>(
doc["result"]["estimated_earn_next_block"].as<float>() * 100000000
)
};
}

View file

@ -0,0 +1,31 @@
#pragma once
#include "lib/mining_pool/mining_pool_interface.hpp"
#include <icons/icons.h>
class OceanPool : public MiningPoolInterface {
public:
void setPoolUser(const std::string& user) override { poolUser = user; }
void prepareRequest(HTTPClient& http) const override;
std::string getApiUrl() const override;
PoolStats parseResponse(const JsonDocument& doc) const override;
bool hasLogo() const override { return true; }
std::string getDisplayLabel() const override { return "OCEAN/POOL"; } // Fallback if needed
bool supportsDailyEarnings() const override { return true; }
std::string getDailyEarningsLabel() const override { return "sats/block"; }
std::string getLogoFilename() const override {
return "ocean.bin";
}
std::string getPoolName() const override {
return "ocean";
}
int getLogoWidth() const override {
return 122;
}
int getLogoHeight() const override {
return 122;
}
};

View file

@ -0,0 +1,134 @@
#include "pool_factory.hpp"
const char* PoolFactory::MINING_POOL_NAME_OCEAN = "ocean";
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_GOBRRR_POOL = "gobrrr_pool";
const char* PoolFactory::LOGOS_DIR = "/logos";
std::unique_ptr<MiningPoolInterface> PoolFactory::createPool(const std::string& poolName) {
static const std::unordered_map<std::string, std::function<std::unique_ptr<MiningPoolInterface>()>> poolFactories = {
{MINING_POOL_NAME_OCEAN, []() { return std::make_unique<OceanPool>(); }},
{MINING_POOL_NAME_NODERUNNERS, []() { return std::make_unique<NoderunnersPool>(); }},
{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_GOBRRR_POOL, []() { return std::make_unique<GoBrrrPool>(); }}
};
auto it = poolFactories.find(poolName);
if (it == poolFactories.end()) {
return nullptr;
}
return it->second();
}
void PoolFactory::downloadPoolLogo(const std::string& poolName, const MiningPoolInterface* poolInterface)
{
const int MAX_RETRIES = 5;
const int RETRY_DELAY_MS = 1000; // 1 second between retries
if (!poolInterface || !poolInterface->hasLogo()) {
Serial.println(F("No pool interface or logo"));
return;
}
// Ensure logos directory exists
if (!LittleFS.exists(LOGOS_DIR)) {
LittleFS.mkdir(LOGOS_DIR);
}
String logoPath = String(LOGOS_DIR) + "/" + String(poolName.c_str()) + "_logo.bin";
// Only download if the logo doesn't exist
if (!LittleFS.exists(logoPath)) {
// Clean up logos directory first
File root = LittleFS.open(LOGOS_DIR, "r");
if (root) {
File file = root.openNextFile();
while (file) {
String path = file.path();
file.close();
LittleFS.remove(path);
file = root.openNextFile();
}
root.close();
}
// Download new logo with retries
std::string logoUrl = poolInterface->getLogoUrl();
if (!logoUrl.empty()) {
for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
Serial.printf("Downloading pool logo (attempt %d of %d)...\n", attempt, MAX_RETRIES);
HTTPClient http;
http.setUserAgent(USER_AGENT);
http.begin(logoUrl.c_str());
int httpCode = http.GET();
if (httpCode == 200) {
File file = LittleFS.open(logoPath, "w");
if (file) {
http.writeToStream(&file);
file.close();
Serial.println(F("Logo downloaded successfully"));
http.end();
return; // Success!
}
}
http.end();
if (attempt < MAX_RETRIES) {
Serial.printf("Failed to download logo, HTTP code: %d. Retrying...\n", httpCode);
vTaskDelay(pdMS_TO_TICKS(RETRY_DELAY_MS));
} else {
Serial.printf("Failed to download logo after %d attempts\n", MAX_RETRIES);
}
}
}
} else {
Serial.println(F("Logo already exists"));
}
}
LogoData PoolFactory::loadLogoFromFS(const std::string& poolName, const MiningPoolInterface* poolInterface)
{
// Initialize with dimensions from the pool interface
LogoData logo = {nullptr,
0,
0,
0};
String logoPath = String(LOGOS_DIR) + "/" + String(poolName.c_str()) + "_logo.bin";
if (!LittleFS.exists(logoPath)) {
return logo;
}
// Only set dimensions if file exists
logo.width = static_cast<size_t>(poolInterface->getLogoWidth());
logo.height = static_cast<size_t>(poolInterface->getLogoHeight());
File file = LittleFS.open(logoPath, "r");
if (!file) {
return logo;
}
size_t size = file.size();
uint8_t* buffer = new uint8_t[size];
if (file.read(buffer, size) == size) {
logo.data = buffer;
logo.size = size;
} else {
delete[] buffer;
logo.data = nullptr;
logo.size = 0;
}
file.close();
return logo;
}

View file

@ -0,0 +1,56 @@
#pragma once
#include "mining_pool_interface.hpp"
#include <memory>
#include <string>
#include "lib/shared.hpp"
#include "lib/config.hpp"
#include "noderunners/noderunners_pool.hpp"
#include "braiins/brains_pool.hpp"
#include "ocean/ocean_pool.hpp"
#include "satoshi_radio/satoshi_radio_pool.hpp"
#include "public_pool/public_pool.hpp"
#include "gobrrr_pool/gobrrr_pool.hpp"
#include <LittleFS.h>
#include <HTTPClient.h>
class PoolFactory {
public:
static const char* getLogosDir() { return LOGOS_DIR; }
static std::unique_ptr<MiningPoolInterface> createPool(const std::string& poolName);
static std::vector<std::string> getAvailablePools() {
return {
MINING_POOL_NAME_OCEAN,
MINING_POOL_NAME_NODERUNNERS,
MINING_POOL_NAME_SATOSHI_RADIO,
MINING_POOL_NAME_BRAIINS,
MINING_POOL_NAME_PUBLIC_POOL,
MINING_POOL_NAME_GOBRRR_POOL
};
}
static std::string getAvailablePoolsAsString() {
const auto pools = getAvailablePools();
std::string result;
for (size_t i = 0; i < pools.size(); ++i) {
result += pools[i];
if (i < pools.size() - 1) {
result += ", ";
}
}
return result;
}
static void downloadPoolLogo(const std::string& poolName, const MiningPoolInterface* poolInterface);
static LogoData loadLogoFromFS(const std::string& poolName, const MiningPoolInterface* poolInterface);
private:
static const char* MINING_POOL_NAME_OCEAN;
static const char* MINING_POOL_NAME_NODERUNNERS;
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_GOBRRR_POOL;
static const char* LOGOS_DIR;
};

View file

@ -0,0 +1,10 @@
#pragma once
#include <string>
#include <optional>
struct PoolStats {
std::string hashrate;
std::optional<int64_t> dailyEarnings;
};

View file

@ -0,0 +1,21 @@
// src/noderunners/noderunners_pool.cpp
#include "public_pool.hpp"
std::string PublicPool::getApiUrl() const {
return "https://public-pool.io:40557/api/client/" + poolUser;
}
PoolStats PublicPool::parseResponse(const JsonDocument& doc) const {
uint64_t totalHashrate = 0;
for (JsonVariantConst worker : doc["workers"].as<JsonArrayConst>()) {
totalHashrate += static_cast<uint64_t>(std::llround(worker["hashRate"].as<double>()));
}
return PoolStats{
.hashrate = std::to_string(totalHashrate),
.dailyEarnings = std::nullopt // Public Pool doesn't support daily earnings
};
}

View file

@ -0,0 +1,15 @@
#pragma once
#include "lib/mining_pool/mining_pool_interface.hpp"
#include "lib/mining_pool/noderunners/noderunners_pool.hpp"
#include <icons/icons.h>
class PublicPool : public NoderunnersPool {
public:
std::string getApiUrl() const override;
bool hasLogo() const override { return false; }
std::string getDisplayLabel() const override { return "PUBLIC/POOL"; }
PoolStats parseResponse(const JsonDocument& doc) const override;
};

View file

@ -0,0 +1,6 @@
// src/noderunners/noderunners_pool.cpp
#include "satoshi_radio_pool.hpp"
std::string SatoshiRadioPool::getApiUrl() const {
return "https://pool.satoshiradio.nl/api/v1/users/" + poolUser;
}

View file

@ -0,0 +1,14 @@
#pragma once
#include "lib/mining_pool/mining_pool_interface.hpp"
#include "lib/mining_pool/noderunners/noderunners_pool.hpp"
#include <icons/icons.h>
class SatoshiRadioPool : public NoderunnersPool {
public:
std::string getApiUrl() const override;
bool hasLogo() const override { return false; }
std::string getDisplayLabel() const override { return "SATOSHI/RADIO"; } // Fallback if needed
};

View file

@ -0,0 +1,123 @@
#include "mining_pool_stats_fetch.hpp"
TaskHandle_t miningPoolStatsFetchTaskHandle;
std::string miningPoolName;
std::string miningPoolStatsHashrate;
int miningPoolStatsDailyEarnings;
std::string getMiningPoolStatsHashRate()
{
return miningPoolStatsHashrate;
}
int getMiningPoolStatsDailyEarnings()
{
return miningPoolStatsDailyEarnings;
}
void taskMiningPoolStatsFetch(void *pvParameters)
{
std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str();
auto poolInterface = PoolFactory::createPool(poolName);
std::string poolUser = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER).c_str();
// Main stats fetching loop
for (;;)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
HTTPClient http;
http.setUserAgent(USER_AGENT);
poolInterface->setPoolUser(poolUser);
std::string apiUrl = poolInterface->getApiUrl();
http.begin(apiUrl.c_str());
poolInterface->prepareRequest(http);
int httpCode = http.GET();
if (httpCode == 200)
{
String payload = http.getString();
JsonDocument doc;
deserializeJson(doc, payload);
PoolStats stats = poolInterface->parseResponse(doc);
miningPoolStatsHashrate = stats.hashrate;
if (stats.dailyEarnings)
{
miningPoolStatsDailyEarnings = *stats.dailyEarnings;
}
else
{
miningPoolStatsDailyEarnings = 0; // or any other default value
}
if (workQueue != nullptr && (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE || 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: "));
Serial.println(httpCode);
}
}
}
void downloadMiningPoolLogoTask(void *pvParameters) {
std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str();
auto poolInterface = PoolFactory::createPool(poolName);
PoolFactory::downloadPoolLogo(poolName, poolInterface.get());
// If we're on the mining pool stats screen, trigger a display update
if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) {
WorkItem priceUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0};
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
}
xTaskNotifyGive(miningPoolStatsFetchTaskHandle);
vTaskDelete(NULL);
}
void setupMiningPoolStatsFetchTask()
{
xTaskCreate(downloadMiningPoolLogoTask,
"logoDownload",
(6 * 1024),
NULL,
12,
NULL);
xTaskCreate(taskMiningPoolStatsFetch,
"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;
}

View file

@ -0,0 +1,19 @@
#pragma once
#include <Arduino.h>
#include <HTTPClient.h>
#include "mining_pool/pool_factory.hpp"
#include "lib/config.hpp"
#include "lib/shared.hpp"
extern TaskHandle_t miningPoolStatsFetchTaskHandle;
void setupMiningPoolStatsFetchTask();
void taskMiningPoolStatsFetch(void *pvParameters);
std::string getMiningPoolStatsHashRate();
int getMiningPoolStatsDailyEarnings();
std::unique_ptr<MiningPoolInterface>& getMiningPool();
LogoData getMiningPoolLogo();

View file

@ -4,16 +4,20 @@ std::vector<nostr::NostrPool *> pools;
nostr::Transport *transport;
TaskHandle_t nostrTaskHandle = NULL;
boolean nostrIsConnected = false;
boolean nostrIsSubscribed = false;
boolean nostrIsSubscribing = true;
void setupNostrNotify()
String subIdZap;
void setupNostrNotify(bool asDatasource, bool zapNotify)
{
nostr::esp32::ESP32Platform::initNostr(false);
time_t now;
time(&now);
struct tm *utcTimeInfo;
utcTimeInfo = gmtime(&now);
time_t utcNow = mktime(utcTimeInfo);
time_t timestamp60MinutesAgo = utcNow - 3600;
// time_t now;
// time(&now);
// struct tm *utcTimeInfo;
// utcTimeInfo = gmtime(&now);
// time_t utcNow = mktime(utcTimeInfo);
// time_t timestamp60MinutesAgo = utcNow - 3600;
try
{
@ -22,88 +26,35 @@ void setupNostrNotify()
String relay = preferences.getString("nostrRelay");
String pubKey = preferences.getString("nostrPubKey");
pools.push_back(pool);
// Lets subscribe to the relay
String subId = pool->subscribeMany(
{relay},
{{// we set the filters here (see
// https://github.com/nostr-protocol/nips/blob/master/01.md#from-client-to-relay-sending-events-and-creating-subscriptions)
{"kinds", {"1"}},
{"since", {String(timestamp60MinutesAgo)}},
{"authors", {pubKey}}}},
[&](const String &subId, nostr::SignedNostrEvent *event)
{
// Received events callback, we can access the event content with
// event->getContent() Here you should handle the event, for this
// test we will just serialize it and print to console
JsonDocument doc;
JsonArray arr = doc["data"].to<JsonArray>();
event->toSendableEvent(arr);
// Access the second element which is the object
JsonObject obj = arr[1].as<JsonObject>();
// Access the "tags" array
JsonArray tags = obj["tags"].as<JsonArray>();
std::vector<std::map<NostrString, std::initializer_list<NostrString>>> filters;
// Flag to check if the tag was found
bool tagFound = false;
uint medianFee = 0;
String typeValue;
if (zapNotify)
{
subscribeZaps(pool, relay, 60);
}
// Iterate over the tags array
for (JsonArray tag : tags)
{
// Check if the tag is an array with two elements
if (tag.size() == 2)
{
const char *key = tag[0];
const char *value = tag[1];
if (asDatasource)
{
String subId = pool->subscribeMany(
{relay},
{// First filter
{
{"kinds", {"1"}},
{"since", {String(getMinutesAgo(60))}},
{"authors", {pubKey}},
}},
handleNostrEventCallback,
onNostrSubscriptionClosed,
onNostrSubscriptionEose);
// Check if the key is "type" and the value is "priceUsd"
if (strcmp(key, "type") == 0 && (strcmp(value, "priceUsd") == 0 || strcmp(value, "blockHeight") == 0))
{
typeValue = value;
tagFound = true;
}
else if (strcmp(key, "medianFee") == 0)
{
medianFee = tag[1].as<uint>();
}
}
}
if (tagFound)
{
if (typeValue.equals("priceUsd"))
{
processNewPrice(obj["content"].as<uint>());
}
else if (typeValue.equals("blockHeight"))
{
processNewBlock(obj["content"].as<uint>());
}
if (medianFee != 0)
{
processNewBlockFee(medianFee);
}
}
},
[&](const String &subId, const String &reason)
{
// This is the callback that will be called when the subscription is
// closed
Serial.println("Subscription closed: " + reason);
},
[&](const String &subId)
{
// This is the callback that will be called when the subscription is
// EOSE
Serial.println("Subscription EOSE: " + subId);
});
Serial.println("[ Nostr ] Subscribing to Nostr Data Feed");
}
std::vector<nostr::NostrRelay *> *relays = pool->getConnectedRelays();
for (nostr::NostrRelay *relay : *relays)
{
Serial.println("Registering to connection events of: " + relay->getUrl());
Serial.println("[ Nostr ] Registering to connection events of: " + relay->getUrl());
relay->getConnection()->addConnectionStatusListener([&](const nostr::ConnectionStatus &status)
{
String sstatus="UNKNOWN";
@ -112,23 +63,27 @@ void setupNostrNotify()
sstatus="CONNECTED";
}else if(status==nostr::ConnectionStatus::DISCONNECTED){
nostrIsConnected = false;
nostrIsSubscribed = false;
sstatus="DISCONNECTED";
}else if(status==nostr::ConnectionStatus::ERROR){
sstatus = "ERROR";
}
Serial.println("Connection status changed: " + sstatus); });
Serial.println("[ Nostr ] Connection status changed: " + sstatus);
});
}
}
catch (const std::exception &e)
{
Serial.println("Error: " + String(e.what()));
Serial.println("[ Nostr ] Error: " + String(e.what()));
}
}
void nostrTask(void *pvParameters)
{
int blockFetch = getBlockFetch();
processNewBlock(blockFetch);
if(preferences.getBool("useNostr", DEFAULT_USE_NOSTR)) {
int blockFetch = getBlockFetch();
processNewBlock(blockFetch);
}
while (1)
{
@ -137,6 +92,10 @@ void nostrTask(void *pvParameters)
// Run internal loop: refresh relays, complete pending connections, send
// pending messages
pool->loop();
if (!nostrIsSubscribed && !nostrIsSubscribing) {
Serial.println(F("Not subscribed"));
subscribeZaps(pool, preferences.getString("nostrRelay"), 1);
}
}
vTaskDelay(pdMS_TO_TICKS(100));
}
@ -150,4 +109,159 @@ void setupNostrTask()
boolean nostrConnected()
{
return nostrIsConnected;
}
void onNostrSubscriptionClosed(const String &subId, const String &reason)
{
// This is the callback that will be called when the subscription is
// closed
Serial.println("[ Nostr ] Subscription closed: " + reason);
}
void onNostrSubscriptionEose(const String &subId)
{
// This is the callback that will be called when the subscription is
// EOSE
Serial.println("[ Nostr ] Subscription EOSE: " + subId);
nostrIsSubscribing = false;
nostrIsSubscribed = true;
}
void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *event)
{
// Received events callback, we can access the event content with
// event->getContent() Here you should handle the event, for this
// test we will just serialize it and print to console
JsonDocument doc;
JsonArray arr = doc["data"].to<JsonArray>();
event->toSendableEvent(arr);
// Access the second element which is the object
JsonObject obj = arr[1].as<JsonObject>();
JsonArray tags = obj["tags"].as<JsonArray>();
// Flag to check if the tag was found
bool tagFound = false;
uint medianFee = 0;
String typeValue;
// Iterate over the tags array
for (JsonArray tag : tags)
{
// Check if the tag is an array with two elements
if (tag.size() == 2)
{
const char *key = tag[0];
const char *value = tag[1];
// Check if the key is "type" and the value is "priceUsd"
if (strcmp(key, "type") == 0 && (strcmp(value, "priceUsd") == 0 || strcmp(value, "blockHeight") == 0))
{
typeValue = value;
tagFound = true;
}
else if (strcmp(key, "medianFee") == 0)
{
medianFee = tag[1].as<uint>();
}
}
}
if (tagFound)
{
if (typeValue.equals("priceUsd"))
{
processNewPrice(obj["content"].as<uint>(), CURRENCY_USD);
}
else if (typeValue.equals("blockHeight"))
{
processNewBlock(obj["content"].as<uint>());
}
if (medianFee != 0)
{
processNewBlockFee(medianFee);
}
}
}
time_t getMinutesAgo(int min) {
time_t now;
time(&now);
return now - (min * 60);
}
void subscribeZaps(nostr::NostrPool *pool, const String &relay, int minutesAgo) {
if (subIdZap) {
pool->closeSubscription(subIdZap);
}
nostrIsSubscribing = true;
subIdZap = pool->subscribeMany(
{relay},
{
{
{"kinds", {"9735"}},
{"limit", {"1"}},
{"since", {String(getMinutesAgo(minutesAgo))}},
{"#p", {preferences.getString("nostrZapPubkey", DEFAULT_ZAP_NOTIFY_PUBKEY)}},
},
},
handleNostrZapCallback,
onNostrSubscriptionClosed,
onNostrSubscriptionEose);
Serial.println("[ Nostr ] Subscribing to Zap Notifications since " + String(getMinutesAgo(minutesAgo)));
}
void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event) {
// Received events callback, we can access the event content with
// event->getContent() Here you should handle the event, for this
// test we will just serialize it and print to console
JsonDocument doc;
JsonArray arr = doc["data"].to<JsonArray>();
event->toSendableEvent(arr);
// Access the second element which is the object
JsonObject obj = arr[1].as<JsonObject>();
JsonArray tags = obj["tags"].as<JsonArray>();
// Iterate over the tags array
for (JsonArray tag : tags)
{
// Check if the tag is an array with two elements
if (tag.size() == 2)
{
const char *key = tag[0];
const char *value = tag[1];
if (strcmp(key, "bolt11") == 0)
{
Serial.print(F("Got a zap of "));
int64_t satsAmount = getAmountInSatoshis(std::string(value));
Serial.print(satsAmount);
Serial.println(F(" sats"));
std::array<std::string, NUM_SCREENS> textEpdContent = parseZapNotify(satsAmount, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL));
uint64_t timerPeriod = 0;
if (isTimerActive())
{
// store timer periode before making inactive to prevent artifacts
timerPeriod = getTimerSeconds();
esp_timer_stop(screenRotateTimer);
}
setCurrentScreen(SCREEN_CUSTOM);
setEpdContent(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);
}
if (timerPeriod > 0)
{
esp_timer_start_periodic(screenRotateTimer,
timerPeriod * usPerSecond);
}
}
}
}
}

View file

@ -3,17 +3,28 @@
#include "shared.hpp"
#include <ArduinoJson.h>
#include <esp32/ESP32Platform.h>
#include <NostrEvent.h>
#include <NostrPool.h>
#include <Transport.h>
#include <Utils.h>
#include <nostrdisplay_handler.hpp>
#include <string>
#include "esp32/ESP32Platform.h"
#include "NostrEvent.h"
#include "NostrPool.h"
#include "price_notify.hpp"
#include "block_notify.hpp"
#include "lib/timers.hpp"
void setupNostrNotify();
void setupNostrNotify(bool asDatasource, bool zapNotify);
void nostrTask(void *pvParameters);
void setupNostrTask();
boolean nostrConnected();
boolean nostrConnected();
void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *event);
void handleNostrZapCallback(const String &subId, nostr::SignedNostrEvent *event);
void onNostrSubscriptionClosed(const String &subId, const String &reason);
void onNostrSubscriptionEose(const String &subId);
time_t getMinutesAgo(int min);
void subscribeZaps(nostr::NostrPool *pool, const String &relay, int minutesAgo);

View file

@ -2,9 +2,14 @@
TaskHandle_t taskOtaHandle = NULL;
bool isOtaUpdating = false;
QueueHandle_t otaQueue;
void setupOTA() {
if (preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED)) {
void setupOTA()
{
if (preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED))
{
ArduinoOTA.onStart(onOTAStart);
ArduinoOTA.onProgress(onOTAProgress);
@ -16,31 +21,38 @@ void setupOTA() {
ArduinoOTA.setRebootOnSuccess(false);
ArduinoOTA.begin();
// downloadUpdate();
otaQueue = xQueueCreate(1, sizeof(UpdateMessage));
xTaskCreate(handleOTATask, "handleOTA", 4096, NULL, tskIDLE_PRIORITY,
xTaskCreate(handleOTATask, "handleOTA", 8192, NULL, 20,
&taskOtaHandle);
}
}
void onOTAProgress(unsigned int progress, unsigned int total) {
void onOTAProgress(unsigned int progress, unsigned int total)
{
uint percentage = progress / (total / 100);
pixels.fill(pixels.Color(0, 255, 0));
if (percentage < 100) {
if (percentage < 100)
{
pixels.setPixelColor(0, pixels.Color(0, 0, 0));
}
if (percentage < 75) {
if (percentage < 75)
{
pixels.setPixelColor(1, pixels.Color(0, 0, 0));
}
if (percentage < 50) {
if (percentage < 50)
{
pixels.setPixelColor(2, pixels.Color(0, 0, 0));
}
if (percentage < 25) {
if (percentage < 25)
{
pixels.setPixelColor(3, pixels.Color(0, 0, 0));
}
pixels.show();
}
void onOTAStart() {
void onOTAStart()
{
forceFullRefresh();
std::array<String, NUM_SCREENS> epdContent = {"U", "P", "D", "A",
"T", "E", "!"};
@ -55,79 +67,331 @@ void onOTAStart() {
vTaskSuspend(workerTaskHandle);
vTaskSuspend(taskScreenRotateTaskHandle);
vTaskSuspend(ledTaskHandle);
// vTaskSuspend(ledTaskHandle);
vTaskSuspend(buttonTaskHandle);
stopWebServer();
// stopWebServer();
stopBlockNotify();
stopPriceNotify();
}
void handleOTATask(void *parameter) {
for (;;) {
ArduinoOTA.handle(); // Allow OTA updates to occur
void handleOTATask(void *parameter)
{
UpdateMessage msg;
for (;;)
{
if (xQueueReceive(otaQueue, &msg, 0) == pdTRUE)
{
if (msg.updateType == UPDATE_ALL) {
queueLedEffect(LED_FLASH_UPDATE);
int resultWebUi = downloadUpdateHandler(UPDATE_WEBUI);
queueLedEffect(LED_FLASH_UPDATE);
int resultFw = downloadUpdateHandler(UPDATE_FIRMWARE);
if (resultWebUi == 0 && resultFw == 0) {
ESP.restart();
} else {
queueLedEffect(LED_FLASH_ERROR);
vTaskDelay(pdMS_TO_TICKS(3000));
ESP.restart();
}
}
}
ArduinoOTA.handle(); // Allow OTA updates to occur
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
void downloadUpdate() {
ReleaseInfo getLatestRelease(const String &fileToDownload)
{
String releaseUrl = preferences.getString("gitReleaseUrl");
WiFiClientSecure client;
client.setInsecure();
// client.setCACert(isrg_root_x1cert);
client.setCACertBundle(rootca_crt_bundle_start);
HTTPClient http;
http.begin(client, releaseUrl);
http.setUserAgent(USER_AGENT);
// Send HTTP request to CoinGecko API
http.useHTTP10(true);
http.begin(client,
"https://api.github.com/repos/btclock/btclock_v3/releases/latest");
int httpCode = http.GET();
if (httpCode == 200) {
// WiFiClient * stream = http->getStreamPtr();
ReleaseInfo info = {"", ""};
JsonDocument filter;
JsonObject filter_assets_0 = filter["assets"].add<JsonObject>();
filter_assets_0["name"] = true;
filter_assets_0["browser_download_url"] = true;
if (httpCode > 0)
{
String payload = http.getString();
JsonDocument doc;
deserializeJson(doc, payload);
DeserializationError error = deserializeJson(
doc, http.getStream(), DeserializationOption::Filter(filter));
JsonArray assets = doc["assets"];
if (error) {
Serial.print("deserializeJson() failed: ");
Serial.println(error.c_str());
return;
}
for (JsonObject asset : assets)
{
String assetName = asset["name"].as<String>();
if (assetName == fileToDownload)
{
info.fileUrl = asset["browser_download_url"].as<String>();
}
else if (assetName == fileToDownload + ".sha256")
{
info.checksumUrl = asset["browser_download_url"].as<String>();
}
String downloadUrl;
for (JsonObject asset : doc["assets"].as<JsonArray>()) {
if (asset["name"].as<String>().compareTo("firmware.bin") == 0) {
downloadUrl = asset["browser_download_url"].as<String>();
if (!info.fileUrl.isEmpty() && !info.checksumUrl.isEmpty())
{
break;
}
}
Serial.printf("Latest release URL: %s\r\n", info.fileUrl.c_str());
Serial.printf("Checksum URL: %s\r\n", info.checksumUrl.c_str());
}
http.end();
return info;
}
Serial.printf("Download update from %s", downloadUrl);
int downloadUpdateHandler(char updateType)
{
WiFiClientSecure client;
client.setCACertBundle(rootca_crt_bundle_start);
HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
// esp_http_client_config_t config = {
// .url = CONFIG_FIRMWARE_UPGRADE_URL,
// };
// esp_https_ota_config_t ota_config = {
// .http_config = &config,
// };
// esp_err_t ret = esp_https_ota(&ota_config);
// if (ret == ESP_OK)
// {
// esp_restart();
// }
ReleaseInfo latestRelease;
switch (updateType)
{
case UPDATE_FIRMWARE:
{
latestRelease = getLatestRelease(getFirmwareFilename());
}
break;
case UPDATE_WEBUI:
{
latestRelease = getLatestRelease(getWebUiFilename());
// updateWebUi(latestRelease.fileUrl, U_SPIFFS);
// return 0;
}
break;
}
// First, download the expected SHA256
String expectedSHA256 = downloadSHA256(latestRelease.checksumUrl);
if (expectedSHA256.isEmpty())
{
Serial.println("Failed to get SHA256 checksum. Aborting update.");
return false;
}
http.begin(client, latestRelease.fileUrl);
http.setUserAgent(USER_AGENT);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK)
{
int contentLength = http.getSize();
if (contentLength > 0)
{
// Allocate memory to store the firmware
uint8_t *firmware = (uint8_t *)malloc(contentLength);
if (!firmware)
{
Serial.println(F("Not enough memory to store firmware"));
return false;
}
WiFiClient *stream = http.getStreamPtr();
size_t bytesRead = 0;
while (bytesRead < contentLength)
{
size_t available = stream->available();
if (available)
{
size_t readBytes = stream->readBytes(firmware + bytesRead, available);
bytesRead += readBytes;
}
yield(); // Allow background tasks to run
}
if (bytesRead != contentLength)
{
Serial.println("Failed to read entire firmware");
free(firmware);
return false;
}
// Calculate SHA256
String calculated_sha256 = calculateSHA256(firmware, contentLength);
Serial.print("Calculated checksum: ");
Serial.println(calculated_sha256);
Serial.print("Expected checksum: ");
Serial.println(expectedSHA256);
if (calculated_sha256 != expectedSHA256)
{
Serial.println("Checksum mismatch. Aborting update.");
free(firmware);
return false;
}
Update.onProgress(onOTAProgress);
if (Update.begin(contentLength, updateType))
{
onOTAStart();
size_t written = Update.write(firmware, contentLength);
if (written == contentLength)
{
Serial.println("Written : " + String(written) + " successfully");
free(firmware);
}
else
{
Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?");
free(firmware);
return 503;
}
if (Update.end())
{
Serial.println("OTA done!");
if (Update.isFinished())
{
Serial.println("Update successfully completed. Rebooting.");
// ESP.restart();
}
else
{
Serial.println("Update not finished? Something went wrong!");
free(firmware);
return 503;
}
}
else
{
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
free(firmware);
return 503;
}
}
else
{
Serial.println("Not enough space to begin OTA");
free(firmware);
return 503;
}
}
else
{
Serial.println("Invalid content length");
return 503;
}
}
else
{
Serial.printf("HTTP error: %d\n", httpCode);
return 503;
}
http.end();
return 0;
}
void updateWebUi(String latestRelease, int command)
{
WiFiClientSecure client;
client.setCACertBundle(rootca_crt_bundle_start);
HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.begin(client, latestRelease);
http.setUserAgent(USER_AGENT);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK)
{
int contentLength = http.getSize();
if (contentLength > 0)
{
uint8_t *buffer = (uint8_t *)malloc(contentLength);
if (buffer)
{
WiFiClient *stream = http.getStreamPtr();
size_t written = stream->readBytes(buffer, contentLength);
if (written == contentLength)
{
String expectedSHA256 = "";
if (command == U_FLASH)
{
expectedSHA256 = downloadSHA256(getFirmwareFilename());
Serial.print("Expected checksum: ");
Serial.println(expectedSHA256);
}
String calculated_sha256 = calculateSHA256(buffer, contentLength);
Serial.print("Checksum is ");
Serial.println(calculated_sha256);
if ((command == U_FLASH && expectedSHA256.equals(calculated_sha256)) || command == U_SPIFFS)
{
Serial.println("Checksum verified. Proceeding with update.");
Update.onProgress(onOTAProgress);
if (Update.begin(contentLength, command))
{
onOTAStart();
Update.write(buffer, contentLength);
if (Update.end())
{
Serial.println("Update complete. Rebooting.");
ESP.restart();
}
else
{
Serial.println("Error in update process.");
}
}
else
{
Serial.println("Not enough space to begin OTA");
}
}
else
{
Serial.println("Checksum mismatch. Aborting update.");
}
}
else
{
Serial.println("Error downloading firmware");
}
free(buffer);
}
else
{
Serial.println("Not enough memory to allocate buffer");
}
}
else
{
Serial.println("Invalid content length");
}
}
else
{
Serial.print(httpCode);
Serial.println("Error on HTTP request");
}
}
void onOTAError(ota_error_t error) {
void onOTAError(ota_error_t error)
{
Serial.println(F("\nOTA update error, restarting"));
Wire.end();
SPI.end();
@ -136,7 +400,8 @@ void onOTAError(ota_error_t error) {
ESP.restart();
}
void onOTAComplete() {
void onOTAComplete()
{
Serial.println(F("\nOTA update finished"));
Wire.end();
SPI.end();
@ -144,6 +409,36 @@ void onOTAComplete() {
ESP.restart();
}
bool getIsOTAUpdating() {
bool getIsOTAUpdating()
{
return isOtaUpdating;
}
String downloadSHA256(const String &sha256Url)
{
if (sha256Url.isEmpty())
{
Serial.println("Failed to get SHA256 file URL");
return "";
}
WiFiClientSecure client;
client.setCACertBundle(rootca_crt_bundle_start);
HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.begin(client, sha256Url);
http.setUserAgent(USER_AGENT);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK)
{
String sha256 = http.getString();
sha256.trim(); // Remove any whitespace or newline characters
return sha256;
}
else
{
Serial.printf("Failed to download SHA256 file. HTTP error: %d\n", httpCode);
return "";
}
}

View file

@ -1,15 +1,38 @@
#pragma once
#include <Arduino.h>
#include <ArduinoOTA.h>
#include "lib/config.hpp"
#include "lib/shared.hpp"
#include "lib/timers.hpp"
#ifndef UPDATE_MESSAGE_HPP
#define UPDATE_MESSAGE_HPP
typedef struct {
char updateType;
} UpdateMessage;
#endif
extern QueueHandle_t otaQueue;
struct ReleaseInfo {
String fileUrl;
String checksumUrl;
};
void setupOTA();
void onOTAStart();
void handleOTATask(void *parameter);
void onOTAProgress(unsigned int progress, unsigned int total);
void downloadUpdate();
// void downloadUpdate();
void onOTAError(ota_error_t error);
void onOTAComplete();
int downloadUpdateHandler(char updateType);
ReleaseInfo getLatestRelease(const String& fileToDownload);
bool getIsOTAUpdating();
void updateWebUi(String latestRelease, int command);
String downloadSHA256(const String& filename);
bool getIsOTAUpdating();

View file

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

View file

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

View file

@ -1,45 +1,19 @@
#include "price_notify.hpp"
const char *wsOwnServerPrice = "wss://ws.btclock.dev/ws?assets=bitcoin";
const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin";
const char *wsOwnServerV2 = "wss://ws-staging.btclock.dev/api/v2/ws";
// const char* coinCapWsCert = R"(-----BEGIN CERTIFICATE-----
// MIIFMjCCBNmgAwIBAgIQBtgXvFyc28MsvQ1HjCnXJTAKBggqhkjOPQQDAjBKMQsw
// CQYDVQQGEwJVUzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjEgMB4GA1UEAxMX
// Q2xvdWRmbGFyZSBJbmMgRUNDIENBLTMwHhcNMjMwNTEwMDAwMDAwWhcNMjQwNTA5
// MjM1OTU5WjB1MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG
// A1UEBxMNU2FuIEZyYW5jaXNjbzEZMBcGA1UEChMQQ2xvdWRmbGFyZSwgSW5jLjEe
// MBwGA1UEAxMVc25pLmNsb3VkZmxhcmVzc2wuY29tMFkwEwYHKoZIzj0CAQYIKoZI
// zj0DAQcDQgAEpvFIXzQKHuqTo+IE6c6sB4p0PMXK1KsseEGf2UN/CNRhG5hO7lr8
// JtXrPZkawWBysZxOsEoetkPrDHMugCLfXKOCA3QwggNwMB8GA1UdIwQYMBaAFKXO
// N+rrsHUOlGeItEX62SQQh5YfMB0GA1UdDgQWBBShsZDJohaR1a5E0Qj7yblZjKDC
// gDA6BgNVHREEMzAxggwqLmNvaW5jYXAuaW+CCmNvaW5jYXAuaW+CFXNuaS5jbG91
// ZGZsYXJlc3NsLmNvbTAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUH
// AwEGCCsGAQUFBwMCMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmwzLmRpZ2lj
// ZXJ0LmNvbS9DbG91ZGZsYXJlSW5jRUNDQ0EtMy5jcmwwN6A1oDOGMWh0dHA6Ly9j
// cmw0LmRpZ2ljZXJ0LmNvbS9DbG91ZGZsYXJlSW5jRUNDQ0EtMy5jcmwwPgYDVR0g
// BDcwNTAzBgZngQwBAgIwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2Vy
// dC5jb20vQ1BTMHYGCCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29j
// c3AuZGlnaWNlcnQuY29tMEAGCCsGAQUFBzAChjRodHRwOi8vY2FjZXJ0cy5kaWdp
// Y2VydC5jb20vQ2xvdWRmbGFyZUluY0VDQ0NBLTMuY3J0MAwGA1UdEwEB/wQCMAAw
// ggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB1AO7N0GTV2xrOxVy3nbTNE6Iyh0Z8
// vOzew1FIWUZxH7WbAAABiAPnoRAAAAQDAEYwRAIgAP2W09OozuhmKeKKMsaVBcae
// o+nPHF1WUWk0i387YYYCIDIM1Wll7/4O3GNx2/Fx9bC6pi69Uya4pLxsCfW3fZMe
// AHYASLDja9qmRzQP5WoC+p0w6xxSActW3SyB2bu/qznYhHMAAAGIA+eg+QAABAMA
// RzBFAiEAuNpSqrbx47gYBgBMz5M6q0CnV/WMJqWQOxYFKrwfwVACIH3nCs4bKToT
// e+MiBrqSDaekixk4kPFEQESO9qHCkWY5AHcA2ra/az+1tiKfm8K7XGvocJFxbLtR
// hIU0vaQ9MEjX+6sAAAGIA+eg1gAABAMASDBGAiEAolCFl2IfbOHUPAOxoi4BLclS
// v9FVXb7LwIvTuCfyrEQCIQDcvehwhV9XGopKGl17F2LYYKI7hvlO3RmpPZQJt1da
// MDAKBggqhkjOPQQDAgNHADBEAiAXRWZ/JVMsfpSFFTHQHUSqRnQ/7cCOWx+9svIy
// mYnFZQIgHMEG0Cm7O4cn5KUzKOsTwwK+2U15s/jPUQi2n2IDTEM=
// -----END CERTIFICATE-----)";
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;
uint currentPrice = 50000;
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;
void setupPriceNotify()
{
@ -52,14 +26,52 @@ void setupPriceNotify()
{
config = {.uri = wsServerPrice,
.user_agent = USER_AGENT};
config.cert_pem = isrg_root_x1cert;
config.task_stack = (6*1024);
}
clientPrice = esp_websocket_client_init(&config);
esp_websocket_register_events(clientPrice, WEBSOCKET_EVENT_ANY,
onWebsocketPriceEvent, clientPrice);
esp_websocket_client_start(clientPrice);
// priceNotifyWs.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin");
// priceNotifyWs.onEvent(onWebsocketPriceEvent);
// priceNotifyWs.setReconnectInterval(5000);
// priceNotifyWs.enableHeartbeat(15000, 3000, 2);
}
// void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) {
// switch(type) {
// case WStype_DISCONNECTED:
// Serial.printf("[WSc] Disconnected!\n");
// break;
// case WStype_CONNECTED:
// {
// Serial.printf("[WSc] Connected to url: %s\n", payload);
// break;
// }
// case WStype_TEXT:
// String message = String((char*)payload);
// onWebsocketPriceMessage(message);
// break;
// case WStype_BIN:
// break;
// case WStype_ERROR:
// case WStype_FRAGMENT_TEXT_START:
// case WStype_FRAGMENT_BIN_START:
// case WStype_FRAGMENT:
// case WStype_PING:
// case WStype_PONG:
// case WStype_FRAGMENT_FIN:
// break;
// }
// }
void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data)
{
@ -98,48 +110,63 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data)
{
if (currentPrice != doc["bitcoin"].as<long>())
{
processNewPrice(doc["bitcoin"].as<long>());
processNewPrice(doc["bitcoin"].as<long>(), CURRENCY_USD);
}
}
}
void processNewPrice(uint newPrice)
void processNewPrice(uint newPrice, char currency)
{
uint minSecPriceUpd = preferences.getUInt(
"minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE);
uint currentTime = esp_timer_get_time() / 1000000;
if (lastPriceUpdate == 0 ||
(currentTime - lastPriceUpdate) > minSecPriceUpd)
if (lastUpdateMap.find(currency) == lastUpdateMap.end() ||
(currentTime - lastUpdateMap[currency]) > minSecPriceUpd)
{
// const unsigned long oldPrice = currentPrice;
currentPrice = newPrice;
if (lastPriceUpdate == 0 ||
(currentTime - lastPriceUpdate) > 120)
currencyMap[currency] = newPrice;
if (currency == CURRENCY_USD && ( lastUpdateMap[currency] == 0 ||
(currentTime - lastUpdateMap[currency]) > 120))
{
preferences.putUInt("lastPrice", currentPrice);
}
lastPriceUpdate = currentTime;
lastUpdateMap[currency] = currentTime;
// if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) {
if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER ||
getCurrentScreen() == SCREEN_MSCW_TIME ||
getCurrentScreen() == SCREEN_SATS_PER_CURRENCY ||
getCurrentScreen() == SCREEN_MARKET_CAP))
{
WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0};
WorkItem priceUpdate = {TASK_PRICE_UPDATE, currency};
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
}
//}
}
}
uint getLastPriceUpdate()
uint getLastPriceUpdate(char currency)
{
return lastPriceUpdate;
if (lastUpdateMap.find(currency) == lastUpdateMap.end())
{
return 0;
}
return lastUpdateMap[currency];
}
uint getPrice() { return currentPrice; }
uint getPrice(char currency)
{
if (currencyMap.find(currency) == currencyMap.end())
{
return 0;
}
return currencyMap[currency];
}
void setPrice(uint newPrice) { currentPrice = newPrice; }
void setPrice(uint newPrice, char currency)
{
currencyMap[currency] = newPrice;
}
bool isPriceNotifyConnected()
{

View file

@ -12,16 +12,19 @@ void setupPriceNotify();
void onWebsocketPriceEvent(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data);
//void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length);
void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data);
uint getPrice();
void setPrice(uint newPrice);
uint getPrice(char currency);
void setPrice(uint newPrice, char currency);
void processNewPrice(uint newPrice);
//void processNewPrice(uint newPrice);
void processNewPrice(uint newPrice, char currency);
bool isPriceNotifyConnected();
void stopPriceNotify();
void restartPriceNotify();
bool getPriceNotifyInit();
uint getLastPriceUpdate();
uint getLastPriceUpdate(char currency);

View file

@ -5,17 +5,16 @@
// TaskHandle_t timeUpdateTaskHandle;
TaskHandle_t taskScreenRotateTaskHandle;
TaskHandle_t workerTaskHandle;
esp_timer_handle_t screenRotateTimer;
esp_timer_handle_t minuteTimer;
std::array<std::string, NUM_SCREENS> taskEpdContent = {"", "", "", "",
"", "", ""};
std::array<std::string, NUM_SCREENS> taskEpdContent = {};
std::string priceString;
#define WORK_QUEUE_SIZE 10
QueueHandle_t workQueue = NULL;
uint currentScreen;
uint currentScreen = SCREEN_BLOCK_HEIGHT;
uint currentCurrency = CURRENCY_USD;
void workerTask(void *pvParameters) {
WorkItem receivedItem;
@ -23,8 +22,6 @@ void workerTask(void *pvParameters) {
while (1) {
// Wait for a work item to be available in the queue
if (xQueueReceive(workQueue, &receivedItem, portMAX_DELAY)) {
uint firstIndex = 0;
// Process the work item based on its type
switch (receivedItem.type) {
case TASK_BITAXE_UPDATE: {
@ -36,22 +33,33 @@ void workerTask(void *pvParameters) {
parseBitaxeBestDiff(getBitaxeBestDiff());
}
setEpdContent(taskEpdContent);
break;
}
break;
case TASK_PRICE_UPDATE: {
uint price = getPrice();
char priceSymbol = '$';
if (preferences.getBool("fetchEurPrice", DEFAULT_FETCH_EUR_PRICE)) {
priceSymbol = '[';
case TASK_MINING_POOL_STATS_UPDATE: {
if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_HASHRATE) {
taskEpdContent =
parseMiningPoolStatsHashRate(getMiningPoolStatsHashRate(), *getMiningPool());
} else if (getCurrentScreen() == SCREEN_MINING_POOL_STATS_EARNINGS) {
taskEpdContent =
parseMiningPoolStatsDailyEarnings(getMiningPoolStatsDailyEarnings(), getMiningPool()->getDailyEarningsLabel(), *getMiningPool());
}
setEpdContent(taskEpdContent);
break;
}
case TASK_PRICE_UPDATE: {
uint currency = getCurrentCurrency();
uint price = getPrice(currency);
if (getCurrentScreen() == SCREEN_BTC_TICKER) {
taskEpdContent = parsePriceData(price, priceSymbol, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE));
} else if (getCurrentScreen() == SCREEN_MSCW_TIME) {
taskEpdContent = parseSatsPerCurrency(price, priceSymbol, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL));
taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE),
preferences.getBool("mowMode", DEFAULT_MOW_MODE),
preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT)
);
} else if (getCurrentScreen() == SCREEN_SATS_PER_CURRENCY) {
taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL));
} else {
taskEpdContent =
parseMarketCap(getBlockHeight(), price, priceSymbol,
parseMarketCap(getBlockHeight(), price, currency,
preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR));
}
@ -120,104 +128,21 @@ void taskScreenRotate(void *pvParameters) {
}
}
void IRAM_ATTR minuteTimerISR(void *arg) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// vTaskNotifyGiveFromISR(timeUpdateTaskHandle, &xHigherPriorityTaskWoken);
WorkItem timeUpdate = {TASK_TIME_UPDATE, 0};
xQueueSendFromISR(workQueue, &timeUpdate, &xHigherPriorityTaskWoken);
if (priceFetchTaskHandle != NULL) {
vTaskNotifyGiveFromISR(priceFetchTaskHandle, &xHigherPriorityTaskWoken);
}
if (bitaxeFetchTaskHandle != NULL) {
vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken);
}
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
void IRAM_ATTR screenRotateTimerISR(void *arg) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(taskScreenRotateTaskHandle, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
void setupTasks() {
workQueue = xQueueCreate(WORK_QUEUE_SIZE, sizeof(WorkItem));
xTaskCreate(workerTask, "workerTask", 4096, NULL, tskIDLE_PRIORITY,
&workerTaskHandle);
xTaskCreate(taskScreenRotate, "rotateScreen", 2048, NULL, tskIDLE_PRIORITY,
xTaskCreate(taskScreenRotate, "rotateScreen", 4096, NULL, tskIDLE_PRIORITY,
&taskScreenRotateTaskHandle);
waitUntilNoneBusy();
setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN));
if (findScreenIndexByValue(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN)) != -1)
setCurrentScreen(preferences.getUInt("currentScreen", DEFAULT_CURRENT_SCREEN));
}
void setupTimeUpdateTimer(void *pvParameters) {
const esp_timer_create_args_t minuteTimerConfig = {
.callback = &minuteTimerISR, .name = "minute_timer"};
esp_timer_create(&minuteTimerConfig, &minuteTimer);
time_t currentTime;
struct tm timeinfo;
time(&currentTime);
localtime_r(&currentTime, &timeinfo);
uint32_t secondsUntilNextMinute = 60 - timeinfo.tm_sec;
if (secondsUntilNextMinute > 0)
vTaskDelay(pdMS_TO_TICKS((secondsUntilNextMinute * 1000)));
esp_timer_start_periodic(minuteTimer, usPerMinute);
WorkItem timeUpdate = {TASK_TIME_UPDATE, 0};
xQueueSend(workQueue, &timeUpdate, portMAX_DELAY);
// xTaskNotifyGive(timeUpdateTaskHandle);
vTaskDelete(NULL);
}
void setupScreenRotateTimer(void *pvParameters) {
const esp_timer_create_args_t screenRotateTimerConfig = {
.callback = &screenRotateTimerISR, .name = "screen_rotate_timer"};
esp_timer_create(&screenRotateTimerConfig, &screenRotateTimer);
if (preferences.getBool("timerActive", DEFAULT_TIMER_ACTIVE)) {
esp_timer_start_periodic(screenRotateTimer,
getTimerSeconds() * usPerSecond);
}
vTaskDelete(NULL);
}
uint getTimerSeconds() { return preferences.getUInt("timerSeconds", DEFAULT_TIMER_SECONDS); }
bool isTimerActive() { return esp_timer_is_active(screenRotateTimer); }
void setTimerActive(bool status) {
if (status) {
esp_timer_start_periodic(screenRotateTimer,
getTimerSeconds() * usPerSecond);
queueLedEffect(LED_EFFECT_START_TIMER);
preferences.putBool("timerActive", true);
} else {
esp_timer_stop(screenRotateTimer);
queueLedEffect(LED_EFFECT_PAUSE_TIMER);
preferences.putBool("timerActive", false);
}
if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle);
}
void toggleTimerActive() { setTimerActive(!isTimerActive()); }
uint getCurrentScreen() { return currentScreen; }
void setCurrentScreen(uint newScreen) {
@ -242,7 +167,7 @@ void setCurrentScreen(uint newScreen) {
break;
}
case SCREEN_MARKET_CAP:
case SCREEN_MSCW_TIME:
case SCREEN_SATS_PER_CURRENCY:
case SCREEN_BTC_TICKER: {
WorkItem priceUpdate = {TASK_PRICE_UPDATE, 0};
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
@ -264,16 +189,53 @@ void setCurrentScreen(uint newScreen) {
return;
}
break;
}
case SCREEN_MINING_POOL_STATS_HASHRATE:
case SCREEN_MINING_POOL_STATS_EARNINGS: {
if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED)) {
WorkItem miningPoolStatsUpdate = {TASK_MINING_POOL_STATS_UPDATE, 0};
xQueueSend(workQueue, &miningPoolStatsUpdate, portMAX_DELAY);
} else {
setCurrentScreen(SCREEN_BLOCK_HEIGHT);
return;
}
break;
}
}
if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle);
}
bool isCurrencySpecific(uint screen) {
switch (screen) {
case SCREEN_BTC_TICKER:
case SCREEN_SATS_PER_CURRENCY:
case SCREEN_MARKET_CAP:
return true;
default:
return false;
}
}
void nextScreen() {
int currentIndex = findScreenIndexByValue(getCurrentScreen());
std::vector<ScreenMapping> screenMappings = getScreenNameMap();
if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE) && isCurrencySpecific(getCurrentScreen())) {
std::vector<std::string> ac = getActiveCurrencies();
std::string curCode = getCurrencyCode(getCurrentCurrency());
if (getCurrencyCode(getCurrentCurrency()) != ac.back()) {
auto it = std::find(ac.begin(), ac.end(), curCode);
if (it != ac.end()) {
size_t index = std::distance(ac.begin(), it);
setCurrentCurrency(getCurrencyChar(ac.at(index+1)));
setCurrentScreen(getCurrentScreen());
return;
}
}
setCurrentCurrency(getCurrencyChar(ac.front()));
}
int newCurrentScreen;
if (currentIndex < screenMappings.size() - 1) {
@ -282,7 +244,7 @@ void nextScreen() {
newCurrentScreen = screenMappings.front().value;
}
String key = "screen" + String(screenMappings[currentIndex - 1].value) + "Visible";
String key = "screen" + String(newCurrentScreen) + "Visible";
while (!preferences.getBool(key.c_str(), true)) {
currentIndex = findScreenIndexByValue(newCurrentScreen);
@ -294,7 +256,7 @@ void nextScreen() {
key = "screen" + String(newCurrentScreen) + "Visible";
}
setCurrentScreen(newCurrentScreen);
}
@ -302,6 +264,23 @@ void previousScreen() {
int currentIndex = findScreenIndexByValue(getCurrentScreen());
std::vector<ScreenMapping> screenMappings = getScreenNameMap();
if (preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE) && isCurrencySpecific(getCurrentScreen())) {
std::vector<std::string> ac = getActiveCurrencies();
std::string curCode = getCurrencyCode(getCurrentCurrency());
if (getCurrencyCode(getCurrentCurrency()) != ac.front()) {
auto it = std::find(ac.begin(), ac.end(), curCode);
if (it != ac.end()) {
size_t index = std::distance(ac.begin(), it);
setCurrentCurrency(getCurrencyChar(ac.at(index-1)));
setCurrentScreen(getCurrentScreen());
return;
}
}
setCurrentCurrency(getCurrencyChar(ac.back()));
}
int newCurrentScreen;
if (currentIndex > 0) {
@ -326,8 +305,9 @@ void previousScreen() {
}
void showSystemStatusScreen() {
std::array<String, NUM_SCREENS> sysStatusEpdContent = {"", "", "", "",
"", "", ""};
std::array<String, NUM_SCREENS> sysStatusEpdContent;
std::fill(sysStatusEpdContent.begin(), sysStatusEpdContent.end(), "");
String ipAddr = WiFi.localIP().toString();
String subNet = WiFi.subnetMask().toString();
@ -351,4 +331,13 @@ void showSystemStatusScreen() {
(int)round(ESP.getHeapSize() / 1024);
setCurrentScreen(SCREEN_CUSTOM);
setEpdContent(sysStatusEpdContent);
}
void setCurrentCurrency(char currency) {
currentCurrency = currency;
preferences.putUChar("lastCurrency", currency);
}
uint getCurrentCurrency() {
return currentCurrency;
}

View file

@ -6,9 +6,9 @@
#include <data_handler.hpp>
#include <bitaxe_handler.hpp>
#include "lib/mining_pool/mining_pool_stats_handler.hpp"
#include "lib/epd.hpp"
#include "lib/price_fetch.hpp"
#include "lib/shared.hpp"
// extern TaskHandle_t priceUpdateTaskHandle;
@ -17,9 +17,6 @@
extern TaskHandle_t workerTaskHandle;
extern TaskHandle_t taskScreenRotateTaskHandle;
extern esp_timer_handle_t screenRotateTimer;
extern esp_timer_handle_t minuteTimer;
extern QueueHandle_t workQueue;
typedef enum {
@ -27,7 +24,8 @@ typedef enum {
TASK_BLOCK_UPDATE,
TASK_FEE_UPDATE,
TASK_TIME_UPDATE,
TASK_BITAXE_UPDATE
TASK_BITAXE_UPDATE,
TASK_MINING_POOL_STATS_UPDATE
} TaskType;
typedef struct {
@ -43,20 +41,16 @@ void previousScreen();
void showSystemStatusScreen();
void setupTimeUpdateTimer(void *pvParameters);
void setupScreenRotateTimer(void *pvParameters);
void IRAM_ATTR minuteTimerISR(void *arg);
void IRAM_ATTR screenRotateTimerISR(void *arg);
// void taskPriceUpdate(void *pvParameters);
// void taskBlockUpdate(void *pvParameters);
// void taskTimeUpdate(void *pvParameters);
void taskScreenRotate(void *pvParameters);
uint getTimerSeconds();
bool isTimerActive();
void setTimerActive(bool status);
void toggleTimerActive();
void setupTasks();
void setCurrentCurrency(char currency);
uint getCurrentCurrency();

View file

@ -1,10 +1,182 @@
#include "shared.hpp"
// const char *github_root_ca =
// "-----BEGIN CERTIFICATE-----\n"
// "MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL\n"
// "MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\n"
// "eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\n"
// "JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx\n"
// "MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\n"
// "Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg\n"
// "VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm\n"
// "aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo\n"
// "I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng\n"
// "o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G\n"
// "A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD\n"
// "VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB\n"
// "zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW\n"
// "RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=\n"
// "-----END CERTIFICATE-----\n"
// "-----BEGIN CERTIFICATE-----\n"
// "MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\n"
// "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
// "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n"
// "MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\n"
// "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n"
// "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n"
// "9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n"
// "2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n"
// "1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n"
// "q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\n"
// "tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\n"
// "vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\n"
// "BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n"
// "5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n"
// "1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\n"
// "NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\n"
// "Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n"
// "8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\n"
// "pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\n"
// "MrY=\n"
// "-----END CERTIFICATE-----\n";
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
uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24
uint8_t output_row_mono_buffer[max_row_width / 8]; // buffer for at least one row of b/w bits
uint8_t output_row_color_buffer[max_row_width / 8]; // buffer for at least one row of color bits
uint8_t mono_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 b/w
uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24
uint8_t output_row_mono_buffer[max_row_width / 8]; // buffer for at least one row of b/w bits
uint8_t output_row_color_buffer[max_row_width / 8]; // buffer for at least one row of color bits
uint8_t mono_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 b/w
uint8_t color_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 c/w
uint16_t rgb_palette_buffer[max_palette_pixels]; // palette buffer for depth <= 8 for buffered graphics, needed for 7-color display
#endif
uint16_t rgb_palette_buffer[max_palette_pixels]; // palette buffer for depth <= 8 for buffered graphics, needed for 7-color display
#endif
// Function to calculate SHA-256 hash
String calculateSHA256(uint8_t *data, size_t len)
{
byte shaResult[32];
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
mbedtls_md_starts(&ctx);
mbedtls_md_update(&ctx, data, len);
mbedtls_md_finish(&ctx, shaResult);
mbedtls_md_free(&ctx);
char sha256_str[65];
for (int i = 0; i < 32; i++)
{
sprintf(sha256_str + (i * 2), "%02x", shaResult[i]);
}
sha256_str[64] = 0;
return String(sha256_str);
}
String calculateSHA256(WiFiClient *stream, size_t contentLength) {
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
mbedtls_md_starts(&ctx);
uint8_t buff[1024];
size_t bytesRead = 0;
while (bytesRead < contentLength) {
size_t toRead = min((size_t)(contentLength - bytesRead), sizeof(buff));
size_t readBytes = stream->readBytes(buff, toRead);
if (readBytes == 0) {
break;
}
mbedtls_md_update(&ctx, buff, readBytes);
bytesRead += readBytes;
}
byte shaResult[32];
mbedtls_md_finish(&ctx, shaResult);
mbedtls_md_free(&ctx);
String result = "";
for (int i = 0; i < sizeof(shaResult); i++) {
char str[3];
sprintf(str, "%02x", (int)shaResult[i]);
result += str;
}
return result;
}
// uint8_t* getOceanIcon() {
// zlib_turbo zt;
// int iUncompSize = zt.gzip_info((uint8_t *)ocean_logo_comp, ocean_logo_size);
// uint8_t *pUncompressed;
// pUncompressed = (uint8_t *)malloc(iUncompSize+4);
// zt.gunzip((uint8_t *)ocean_logo_comp, ocean_logo_size, pUncompressed);
// }
WiFiClientSecure HttpHelper::secureClient;
WiFiClient HttpHelper::insecureClient;
bool HttpHelper::certBundleSet = false;
HTTPClient* HttpHelper::begin(const String& url) {
HTTPClient* http = new HTTPClient();
if (url.startsWith("https://")) {
if (!certBundleSet) {
secureClient.setCACertBundle(rootca_crt_bundle_start);
certBundleSet = true;
}
http->begin(secureClient, url);
} else {
http->begin(insecureClient, url);
}
http->setUserAgent(USER_AGENT);
return http;
}
void HttpHelper::end(HTTPClient* http) {
if (http) {
http->end();
delete http;
}
}

View file

@ -1,21 +1,29 @@
#pragma once
#include <Adafruit_MCP23X17.h>
#include "MCP23017.h"
// #include <zlib_turbo.h>
#include <ArduinoJson.h>
#include <WiFiClientSecure.h>
#include <Preferences.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <GxEPD2.h>
#include <GxEPD2_BW.h>
#include <mbedtls/md.h>
#include "esp_crt_bundle.h"
#include <Update.h>
#include <HTTPClient.h>
#include <mutex>
#include <utils.hpp>
#include "defaults.hpp"
extern Adafruit_MCP23X17 mcp1;
#ifdef IS_BTCLOCK_S3
extern Adafruit_MCP23X17 mcp2;
#define USER_AGENT "BTClock/3.0"
extern MCP23017 mcp1;
#ifdef IS_BTCLOCK_V8
extern MCP23017 mcp2;
#endif
extern Preferences preferences;
extern std::mutex mcpMutex;
@ -29,40 +37,75 @@ extern std::mutex mcpMutex;
#endif
const PROGMEM int SCREEN_BLOCK_HEIGHT = 0;
const PROGMEM int SCREEN_MSCW_TIME = 1;
const PROGMEM int SCREEN_BTC_TICKER = 2;
const PROGMEM int SCREEN_TIME = 3;
const PROGMEM int SCREEN_HALVING_COUNTDOWN = 4;
const PROGMEM int SCREEN_MARKET_CAP = 5;
const PROGMEM int SCREEN_BLOCK_FEE_RATE = 6;
const PROGMEM int SCREEN_SATS_PER_CURRENCY = 10;
const PROGMEM int SCREEN_BTC_TICKER = 20;
const PROGMEM int SCREEN_MARKET_CAP = 30;
const PROGMEM int SCREEN_MINING_POOL_STATS_HASHRATE = 70;
const PROGMEM int SCREEN_MINING_POOL_STATS_EARNINGS = 71;
const PROGMEM int SCREEN_BITAXE_HASHRATE = 80;
const PROGMEM int SCREEN_BITAXE_BESTDIFF = 81;
const PROGMEM int SCREEN_COUNTDOWN = 98;
const PROGMEM int SCREEN_CUSTOM = 99;
const int SCREEN_COUNT = 7;
const PROGMEM int screens[SCREEN_COUNT] = {
SCREEN_BLOCK_HEIGHT, SCREEN_MSCW_TIME, SCREEN_BTC_TICKER,
SCREEN_BLOCK_HEIGHT, SCREEN_SATS_PER_CURRENCY, SCREEN_BTC_TICKER,
SCREEN_TIME, SCREEN_HALVING_COUNTDOWN, SCREEN_MARKET_CAP,
SCREEN_BLOCK_FEE_RATE};
const int usPerSecond = 1000000;
const int usPerMinute = 60 * usPerSecond;
struct SpiRamAllocator : ArduinoJson::Allocator {
void* allocate(size_t size) override {
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM);
}
// extern const char *github_root_ca;
extern const char *isrg_root_x1cert;
void deallocate(void* pointer) override {
heap_caps_free(pointer);
}
extern const uint8_t rootca_crt_bundle_start[] asm("_binary_x509_crt_bundle_start");
// extern const uint8_t ocean_logo_comp[] asm("_binary_ocean_gz_start");
// extern const uint8_t ocean_logo_comp_end[] asm("_binary_ocean_gz_end");
void* reallocate(void* ptr, size_t new_size) override {
return heap_caps_realloc(ptr, new_size, MALLOC_CAP_SPIRAM);
}
};
// uint8_t* getOceanIcon();
// const size_t ocean_logo_size = ocean_logo_comp_end - ocean_logo_comp;
const PROGMEM char UPDATE_FIRMWARE = U_FLASH;
const PROGMEM char UPDATE_WEBUI = U_SPIFFS;
const PROGMEM char UPDATE_ALL = 99;
struct ScreenMapping {
int value;
const char* name;
};
String calculateSHA256(uint8_t* data, size_t len);
String calculateSHA256(WiFiClient *stream, size_t contentLength);
namespace ArduinoJson {
template <typename T>
struct Converter<std::vector<T>> {
static void toJson(const std::vector<T>& src, JsonVariant dst) {
JsonArray array = dst.to<JsonArray>();
for (T item : src)
array.add(item);
}
};
}
class HttpHelper {
public:
static HTTPClient* begin(const String& url);
static void end(HTTPClient* http);
private:
static WiFiClientSecure secureClient;
static bool certBundleSet;
static WiFiClient insecureClient;
};

90
src/lib/timers.cpp Normal file
View file

@ -0,0 +1,90 @@
#include "timers.hpp"
esp_timer_handle_t screenRotateTimer;
esp_timer_handle_t minuteTimer;
void setupTimeUpdateTimer(void *pvParameters) {
const esp_timer_create_args_t minuteTimerConfig = {
.callback = &minuteTimerISR, .name = "minute_timer"};
esp_timer_create(&minuteTimerConfig, &minuteTimer);
time_t currentTime;
struct tm timeinfo;
time(&currentTime);
localtime_r(&currentTime, &timeinfo);
uint32_t secondsUntilNextMinute = 60 - timeinfo.tm_sec;
if (secondsUntilNextMinute > 0)
vTaskDelay(pdMS_TO_TICKS((secondsUntilNextMinute * 1000)));
esp_timer_start_periodic(minuteTimer, usPerMinute);
WorkItem timeUpdate = {TASK_TIME_UPDATE, 0};
xQueueSend(workQueue, &timeUpdate, portMAX_DELAY);
// xTaskNotifyGive(timeUpdateTaskHandle);
vTaskDelete(NULL);
}
void setupScreenRotateTimer(void *pvParameters) {
const esp_timer_create_args_t screenRotateTimerConfig = {
.callback = &screenRotateTimerISR, .name = "screen_rotate_timer"};
esp_timer_create(&screenRotateTimerConfig, &screenRotateTimer);
if (preferences.getBool("timerActive", DEFAULT_TIMER_ACTIVE)) {
esp_timer_start_periodic(screenRotateTimer,
getTimerSeconds() * usPerSecond);
}
vTaskDelete(NULL);
}
uint getTimerSeconds() { return preferences.getUInt("timerSeconds", DEFAULT_TIMER_SECONDS); }
bool isTimerActive() { return esp_timer_is_active(screenRotateTimer); }
void setTimerActive(bool status) {
if (status) {
esp_timer_start_periodic(screenRotateTimer,
getTimerSeconds() * usPerSecond);
queueLedEffect(LED_EFFECT_START_TIMER);
preferences.putBool("timerActive", true);
} else {
esp_timer_stop(screenRotateTimer);
queueLedEffect(LED_EFFECT_PAUSE_TIMER);
preferences.putBool("timerActive", false);
}
if (eventSourceTaskHandle != NULL) xTaskNotifyGive(eventSourceTaskHandle);
}
void toggleTimerActive() { setTimerActive(!isTimerActive()); }
void IRAM_ATTR minuteTimerISR(void *arg) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// vTaskNotifyGiveFromISR(timeUpdateTaskHandle, &xHigherPriorityTaskWoken);
WorkItem timeUpdate = {TASK_TIME_UPDATE, 0};
xQueueSendFromISR(workQueue, &timeUpdate, &xHigherPriorityTaskWoken);
if (bitaxeFetchTaskHandle != NULL) {
vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken);
}
if (miningPoolStatsFetchTaskHandle != NULL) {
vTaskNotifyGiveFromISR(miningPoolStatsFetchTaskHandle, &xHigherPriorityTaskWoken);
}
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
void IRAM_ATTR screenRotateTimerISR(void *arg) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(taskScreenRotateTaskHandle, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}

22
src/lib/timers.hpp Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include <esp_timer.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "lib/shared.hpp"
#include "lib/screen_handler.hpp"
extern esp_timer_handle_t screenRotateTimer;
extern esp_timer_handle_t minuteTimer;
void setupTimeUpdateTimer(void *pvParameters);
void setupScreenRotateTimer(void *pvParameters);
void IRAM_ATTR minuteTimerISR(void *arg);
void IRAM_ATTR screenRotateTimerISR(void *arg);
uint getTimerSeconds();
bool isTimerActive();
void setTimerActive(bool status);
void toggleTimerActive();

160
src/lib/v2_notify.cpp Normal file
View file

@ -0,0 +1,160 @@
#include "v2_notify.hpp"
using namespace V2Notify;
namespace V2Notify
{
WebSocketsClient webSocket;
TaskHandle_t v2NotifyTaskHandle;
void setupV2Notify()
{
String hostname = "ws.btclock.dev";
if (preferences.getBool("stagingSource", DEFAULT_STAGING_SOURCE))
{
Serial.println(F("Connecting to V2 staging source"));
hostname = "ws-staging.btclock.dev";
}
else
{
Serial.println(F("Connecting to V2 source"));
}
webSocket.beginSSL(hostname, 443, "/api/v2/ws");
webSocket.onEvent(V2Notify::onWebsocketV2Event);
webSocket.setReconnectInterval(5000);
webSocket.enableHeartbeat(15000, 3000, 2);
V2Notify::setupV2NotifyTask();
}
void onWebsocketV2Event(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);
JsonDocument response;
response["type"] = "subscribe";
response["eventType"] = "blockfee";
size_t responseLength = measureMsgPack(response);
uint8_t *buffer = new uint8_t[responseLength];
serializeMsgPack(response, buffer, responseLength);
webSocket.sendBIN(buffer, responseLength);
delete[] buffer;
buffer = new uint8_t[responseLength];
response["type"] = "subscribe";
response["eventType"] = "blockheight";
responseLength = measureMsgPack(response);
buffer = new uint8_t[responseLength];
serializeMsgPack(response, buffer, responseLength);
webSocket.sendBIN(buffer, responseLength);
delete[] buffer;
buffer = new uint8_t[responseLength];
response["type"] = "subscribe";
response["eventType"] = "price";
JsonArray currenciesArray = response["currencies"].to<JsonArray>();
for (const auto &str : getActiveCurrencies())
{
currenciesArray.add(str);
}
// response["currencies"] = currenciesArray;
responseLength = measureMsgPack(response);
buffer = new uint8_t[responseLength];
serializeMsgPack(response, buffer, responseLength);
webSocket.sendBIN(buffer, responseLength);
break;
}
case WStype_TEXT:
Serial.printf("[WSc] get text: %s\n", payload);
// send message to server
// webSocket.sendTXT("message here");
break;
case WStype_BIN:
{
JsonDocument doc;
DeserializationError error = deserializeMsgPack(doc, payload, length);
V2Notify::handleV2Message(doc);
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 handleV2Message(JsonDocument doc)
{
if (doc.containsKey("blockheight"))
{
uint newBlockHeight = doc["blockheight"].as<uint>();
if (newBlockHeight == getBlockHeight())
{
return;
}
processNewBlock(newBlockHeight);
}
else if (doc.containsKey("blockfee"))
{
uint medianFee = doc["blockfee"].as<uint>();
processNewBlockFee(medianFee);
}
else if (doc.containsKey("price"))
{
// Iterate through the key-value pairs of the "price" object
for (JsonPair kv : doc["price"].as<JsonObject>())
{
const char *currency = kv.key().c_str();
uint newPrice = kv.value().as<uint>();
processNewPrice(newPrice, getCurrencyChar(currency));
}
}
}
void taskV2Notify(void *pvParameters)
{
for (;;)
{
webSocket.loop();
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void setupV2NotifyTask()
{
xTaskCreate(V2Notify::taskV2Notify, "v2Notify", (6 * 1024), NULL, tskIDLE_PRIORITY,
&V2Notify::v2NotifyTaskHandle);
}
bool isV2NotifyConnected()
{
return webSocket.isConnected();
}
}

26
src/lib/v2_notify.hpp Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
#include <esp_websocket_client.h>
#include "block_notify.hpp"
#include <string>
#include "lib/screen_handler.hpp"
namespace V2Notify {
extern TaskHandle_t v2NotifyTaskHandle;
void setupV2NotifyTask();
void taskV2Notify(void *pvParameters);
void setupV2Notify();
void onWebsocketV2Event(WStype_t type, uint8_t * payload, size_t length);
void handleV2Message(JsonDocument doc);
bool isV2NotifyConnected();
}
// void stopV2Notify();
// void restartV2Notify();
// bool getPriceNotifyInit();
// uint getLastPriceUpdate();

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,7 @@
#include "lib/price_notify.hpp"
#include "lib/screen_handler.hpp"
#include "webserver/OneParamRewrite.hpp"
#include "lib/mining_pool/pool_factory.hpp"
extern TaskHandle_t eventSourceTaskHandle;
@ -21,11 +22,17 @@ void stopWebServer();
void setupWebserver();
bool processEpdColorSettings(AsyncWebServerRequest *request);
void onApiStatus(AsyncWebServerRequest *request);
void onApiSystemStatus(AsyncWebServerRequest *request);
void onApiSetWifiTxPower(AsyncWebServerRequest *request);
void onApiScreenControl(AsyncWebServerRequest *request);
void onApiShowScreen(AsyncWebServerRequest *request);
void onApiShowCurrency(AsyncWebServerRequest *request);
void onApiShowText(AsyncWebServerRequest *request);
void onApiIdentify(AsyncWebServerRequest *request);
@ -34,7 +41,6 @@ void onApiShowTextAdvanced(AsyncWebServerRequest *request, JsonVariant &json);
void onApiActionPause(AsyncWebServerRequest *request);
void onApiActionTimerRestart(AsyncWebServerRequest *request);
void onApiSettingsGet(AsyncWebServerRequest *request);
void onApiSettingsPost(AsyncWebServerRequest *request);
void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json);
void onApiFullRefresh(AsyncWebServerRequest *request);
@ -48,6 +54,7 @@ void onFirmwareUpdate(AsyncWebServerRequest *request);
void asyncFirmwareUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void asyncFileUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final, int command);
void asyncWebuiUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void onAutoUpdateFirmware(AsyncWebServerRequest *request);
void onIndex(AsyncWebServerRequest *request);
void onNotFound(AsyncWebServerRequest *request);

View file

@ -51,7 +51,7 @@ extern "C" void app_main()
if (hasLightLevel()) {
if (preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0)
{
if (hasLightLevel() && getLightLevel() == 0)
if (hasLightLevel() && getLightLevel() <= 1 && preferences.getBool("flOffWhenDark", DEFAULT_FL_OFF_WHEN_DARK))
{
if (frontlightIsOn()) {
frontlightFadeOutAll();
@ -134,7 +134,7 @@ extern "C" void app_main()
}
// if more than 5 price updates are missed, there is probably something wrong, reconnect
if ((getLastPriceUpdate() - currentUptime) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE) * 5))
if ((getLastPriceUpdate(CURRENCY_USD) - currentUptime) > (preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE) * 5))
{
Serial.println(F("Detected 5 missed price updates... restarting price handler."));

View file

@ -1,118 +1,310 @@
#include <data_handler.hpp>
#include <unity.h>
void setUp(void) {
template<size_t N>
std::string joinArrayWithBrackets(const std::array<std::string, N>& arr, const std::string& separator = " ") {
std::ostringstream result;
for (size_t i = 0; i < N; ++i) {
if (i > 0) {
result << separator;
}
result << '[' << arr[i] << ']';
}
return result.str();
}
void setUp(void)
{
// set stuff up here
}
void tearDown(void) {
void tearDown(void)
{
// clean stuff up here
}
void test_CorrectSatsPerDollarConversion(void) {
std::array<std::string, NUM_SCREENS> output = parseSatsPerCurrency(37253, '$', false);
void test_CorrectSatsPerDollarConversion(void)
{
std::array<std::string, NUM_SCREENS> output = parseSatsPerCurrency(37253, CURRENCY_USD, false);
TEST_ASSERT_EQUAL_STRING("MSCW/TIME", output[0].c_str());
TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS-4].c_str());
TEST_ASSERT_EQUAL_STRING("6", output[NUM_SCREENS-3].c_str());
TEST_ASSERT_EQUAL_STRING("8", output[NUM_SCREENS-2].c_str());
TEST_ASSERT_EQUAL_STRING("4", output[NUM_SCREENS-1].c_str());
TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS - 4].c_str());
TEST_ASSERT_EQUAL_STRING("6", output[NUM_SCREENS - 3].c_str());
TEST_ASSERT_EQUAL_STRING("8", output[NUM_SCREENS - 2].c_str());
TEST_ASSERT_EQUAL_STRING("4", output[NUM_SCREENS - 1].c_str());
}
void test_SatsPerDollarAfter1B(void)
{
std::array<std::string, NUM_SCREENS> output = parseSatsPerCurrency(120000000, CURRENCY_USD, false);
TEST_ASSERT_EQUAL_STRING("SATS/USD", output[0].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 5].c_str());
TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS - 4].c_str());
TEST_ASSERT_EQUAL_STRING("8", output[NUM_SCREENS - 3].c_str());
TEST_ASSERT_EQUAL_STRING("3", output[NUM_SCREENS - 2].c_str());
TEST_ASSERT_EQUAL_STRING("3", output[NUM_SCREENS - 1].c_str());
}
void test_SixCharacterBlockHeight(void) {
void test_CorrectSatsPerPoundConversion(void)
{
std::array<std::string, NUM_SCREENS> output = parseSatsPerCurrency(37253, CURRENCY_GBP, false);
TEST_ASSERT_EQUAL_STRING("SATS/GBP", output[0].c_str());
TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS - 4].c_str());
TEST_ASSERT_EQUAL_STRING("6", output[NUM_SCREENS - 3].c_str());
TEST_ASSERT_EQUAL_STRING("8", output[NUM_SCREENS - 2].c_str());
TEST_ASSERT_EQUAL_STRING("4", output[NUM_SCREENS - 1].c_str());
}
void test_SixCharacterBlockHeight(void)
{
std::array<std::string, NUM_SCREENS> output = parseBlockHeight(999999);
TEST_ASSERT_EQUAL_STRING("BLOCK/HEIGHT", output[0].c_str());
TEST_ASSERT_EQUAL_STRING("9", output[1].c_str());
}
void test_SevenCharacterBlockHeight(void) {
void test_SevenCharacterBlockHeight(void)
{
std::array<std::string, NUM_SCREENS> output = parseBlockHeight(1000000);
TEST_ASSERT_EQUAL_STRING("1", output[0].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[1].c_str());
}
void test_FeeRateDisplay(void) {
void test_FeeRateDisplay(void)
{
uint testValue = 21;
std::array<std::string, NUM_SCREENS> output = parseBlockFees(static_cast<std::uint16_t>(testValue));
TEST_ASSERT_EQUAL_STRING("FEE/RATE", output[0].c_str());
TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS-3].c_str());
TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS-2].c_str());
TEST_ASSERT_EQUAL_STRING("sat/vB", output[NUM_SCREENS-1].c_str());
TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS - 3].c_str());
TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS - 2].c_str());
TEST_ASSERT_EQUAL_STRING("sat/vB", output[NUM_SCREENS - 1].c_str());
}
void test_PriceOf100kusd(void) {
void test_PriceOf100kusd(void)
{
std::array<std::string, NUM_SCREENS> output = parsePriceData(100000, '$');
TEST_ASSERT_EQUAL_STRING("$", output[0].c_str());
TEST_ASSERT_EQUAL_STRING("1", output[1].c_str());
}
void test_PriceOf1MillionUsd(void) {
void test_PriceOf1MillionUsd(void)
{
std::array<std::string, NUM_SCREENS> output = parsePriceData(1000000, '$');
TEST_ASSERT_EQUAL_STRING("BTC/USD", output[0].c_str());
TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS-5].c_str());
TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS-4].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-3].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-2].c_str());
TEST_ASSERT_EQUAL_STRING("M", output[NUM_SCREENS-1].c_str());
TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS - 5].c_str());
TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS - 4].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 2].c_str());
TEST_ASSERT_EQUAL_STRING("M", output[NUM_SCREENS - 1].c_str());
}
void test_McapLowerUsd(void) {
void test_PriceSuffixMode(void)
{
std::array<std::string, NUM_SCREENS> output = parsePriceData(93000, '$', true, false);
TEST_ASSERT_EQUAL_STRING("BTC/USD", output[0].c_str());
TEST_ASSERT_EQUAL_STRING("9", output[NUM_SCREENS - 5].c_str());
TEST_ASSERT_EQUAL_STRING("3", output[NUM_SCREENS - 4].c_str());
TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS - 3].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 2].c_str());
TEST_ASSERT_EQUAL_STRING("K", output[NUM_SCREENS - 1].c_str());
}
void test_PriceSuffixModeCompact1(void)
{
std::array<std::string, NUM_SCREENS> output = parsePriceData(100000, '$', true, false, true);
std::string joined = joinArrayWithBrackets(output);
TEST_ASSERT_EQUAL_STRING_MESSAGE("BTC/USD", output[0].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("$", output[NUM_SCREENS - 6].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("1", output[NUM_SCREENS - 5].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 4].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("0.", output[NUM_SCREENS - 3].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 2].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("K", output[NUM_SCREENS - 1].c_str(), joined.c_str());
}
void test_PriceSuffixModeCompact2(void)
{
std::array<std::string, NUM_SCREENS> output = parsePriceData(1000000, '$', true, false, true);
std::string joined = joinArrayWithBrackets(output);
TEST_ASSERT_EQUAL_STRING_MESSAGE("BTC/USD", output[0].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("$", output[NUM_SCREENS - 6].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("1.", output[NUM_SCREENS - 5].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 4].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 3].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 2].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("M", output[NUM_SCREENS - 1].c_str(), joined.c_str());
}
void test_PriceSuffixModeMow(void)
{
std::array<std::string, NUM_SCREENS> output = parsePriceData(93600, '$', true, true);
std::string joined = joinArrayWithBrackets(output);
TEST_ASSERT_EQUAL_STRING_MESSAGE("$", output[0].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE(".", output[NUM_SCREENS - 5].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 4].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("9", output[NUM_SCREENS - 3].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("3", output[NUM_SCREENS - 2].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("M", output[NUM_SCREENS - 1].c_str(), joined.c_str());
}
void test_PriceSuffixModeMowCompact(void)
{
std::array<std::string, NUM_SCREENS> output = parsePriceData(93600, '$', true, true, true);
std::string joined = joinArrayWithBrackets(output);
TEST_ASSERT_EQUAL_STRING_MESSAGE("MOW/UNITS", output[0].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("$", output[NUM_SCREENS - 6].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("0.", output[NUM_SCREENS - 5].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output[NUM_SCREENS - 4].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("9", output[NUM_SCREENS - 3].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("3", output[NUM_SCREENS - 2].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("M", output[NUM_SCREENS - 1].c_str(), joined.c_str());
}
void test_McapLowerUsd(void)
{
std::array<std::string, NUM_SCREENS> output = parseMarketCap(810000, 26000, '$', true);
TEST_ASSERT_EQUAL_STRING("USD/MCAP", output[0].c_str());
// TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS-6].c_str());
TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS-5].c_str());
TEST_ASSERT_EQUAL_STRING("5", output[NUM_SCREENS-4].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-3].c_str());
TEST_ASSERT_EQUAL_STRING("7", output[NUM_SCREENS-2].c_str());
TEST_ASSERT_EQUAL_STRING("B", output[NUM_SCREENS-1].c_str());
// TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS-6].c_str());
TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS - 5].c_str());
TEST_ASSERT_EQUAL_STRING("5", output[NUM_SCREENS - 4].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str());
TEST_ASSERT_EQUAL_STRING("7", output[NUM_SCREENS - 2].c_str());
TEST_ASSERT_EQUAL_STRING("B", output[NUM_SCREENS - 1].c_str());
}
void test_Mcap1TrillionUsd(void) {
void test_Mcap1TrillionUsd(void)
{
std::array<std::string, NUM_SCREENS> output = parseMarketCap(831000, 52000, '$', true);
TEST_ASSERT_EQUAL_STRING("USD/MCAP", output[0].c_str());
TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS-6].c_str());
TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS-5].c_str());
TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS-4].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-3].c_str());
TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS-2].c_str());
TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS-1].c_str());
TEST_ASSERT_EQUAL_STRING("$", output[NUM_SCREENS - 6].c_str());
TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS - 5].c_str());
TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS - 4].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str());
TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS - 2].c_str());
TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS - 1].c_str());
}
void test_Mcap1TrillionEur(void) {
std::array<std::string, NUM_SCREENS> output = parseMarketCap(831000, 52000, '[', true);
void test_Mcap1TrillionUsdSmallChars(void)
{
std::array<std::string, NUM_SCREENS> output = parseMarketCap(831000, 52000, '$', false);
TEST_ASSERT_EQUAL_STRING("USD/MCAP", output[0].c_str());
std::string joined = joinArrayWithBrackets(output);
TEST_ASSERT_EQUAL_STRING_MESSAGE(" $ ", output[NUM_SCREENS - 6].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE(" 1", output[NUM_SCREENS - 5].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("020", output[NUM_SCREENS - 4].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("825", output[NUM_SCREENS - 3].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("000", output[NUM_SCREENS - 2].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("000", output[NUM_SCREENS - 1].c_str(), joined.c_str());
}
void test_Mcap1TrillionEur(void)
{
std::array<std::string, NUM_SCREENS> output = parseMarketCap(831000, 52000, CURRENCY_EUR, true);
TEST_ASSERT_EQUAL_STRING("EUR/MCAP", output[0].c_str());
TEST_ASSERT_EQUAL_STRING("[", output[NUM_SCREENS-6].c_str());
TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS-5].c_str());
TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS-4].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS-3].c_str());
TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS-2].c_str());
TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS-1].c_str());
TEST_ASSERT_TRUE(CURRENCY_EUR == output[NUM_SCREENS - 6].c_str()[0]);
TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS - 5].c_str());
TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS - 4].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str());
TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS - 2].c_str());
TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS - 1].c_str());
}
void test_Mcap1TrillionEurSmallChars(void)
{
std::array<std::string, NUM_SCREENS> output = parseMarketCap(831000, 52000, CURRENCY_EUR, false);
TEST_ASSERT_EQUAL_STRING("EUR/MCAP", output[0].c_str());
std::string joined = joinArrayWithBrackets(output);
char result[4];
snprintf(result, sizeof(result), " %c ", CURRENCY_EUR);
TEST_ASSERT_EQUAL_STRING(result, output[NUM_SCREENS - 6].c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE(" 1", output[NUM_SCREENS - 5].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("020", output[NUM_SCREENS - 4].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("825", output[NUM_SCREENS - 3].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("000", output[NUM_SCREENS - 2].c_str(), joined.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("000", output[NUM_SCREENS - 1].c_str(), joined.c_str());
}
void test_Mcap1TrillionJpy(void)
{
std::array<std::string, NUM_SCREENS> output = parseMarketCap(831000, 52000, CURRENCY_JPY, true);
TEST_ASSERT_EQUAL_STRING("JPY/MCAP", output[0].c_str());
TEST_ASSERT_TRUE(CURRENCY_JPY == output[NUM_SCREENS - 6].c_str()[0]);
TEST_ASSERT_EQUAL_STRING("1", output[NUM_SCREENS - 5].c_str());
TEST_ASSERT_EQUAL_STRING(".", output[NUM_SCREENS - 4].c_str());
TEST_ASSERT_EQUAL_STRING("0", output[NUM_SCREENS - 3].c_str());
TEST_ASSERT_EQUAL_STRING("2", output[NUM_SCREENS - 2].c_str());
TEST_ASSERT_EQUAL_STRING("T", output[NUM_SCREENS - 1].c_str());
}
void test_Mcap1TrillionJpySmallChars(void)
{
std::array<std::string, NUM_SCREENS> output = parseMarketCap(831000, 52000, CURRENCY_JPY, false);
TEST_ASSERT_EQUAL_STRING("JPY/MCAP", output[0].c_str());
char result[4];
snprintf(result, sizeof(result), " %c ", CURRENCY_JPY);
TEST_ASSERT_EQUAL_STRING(result, output[NUM_SCREENS - 6].c_str());
TEST_ASSERT_EQUAL_STRING(" 1", output[NUM_SCREENS - 5].c_str());
TEST_ASSERT_EQUAL_STRING("020", output[NUM_SCREENS - 4].c_str());
TEST_ASSERT_EQUAL_STRING("825", output[NUM_SCREENS - 3].c_str());
TEST_ASSERT_EQUAL_STRING("000", output[NUM_SCREENS - 2].c_str());
TEST_ASSERT_EQUAL_STRING("000", output[NUM_SCREENS - 1].c_str());
}
// not needed when using generate_test_runner.rb
int runUnityTests(void) {
int runUnityTests(void)
{
UNITY_BEGIN();
RUN_TEST(test_CorrectSatsPerDollarConversion);
RUN_TEST(test_CorrectSatsPerPoundConversion);
RUN_TEST(test_SatsPerDollarAfter1B);
RUN_TEST(test_SixCharacterBlockHeight);
RUN_TEST(test_SevenCharacterBlockHeight);
RUN_TEST(test_FeeRateDisplay);
RUN_TEST(test_PriceOf100kusd);
RUN_TEST(test_McapLowerUsd);
RUN_TEST(test_Mcap1TrillionUsd);
RUN_TEST(test_Mcap1TrillionUsdSmallChars);
RUN_TEST(test_Mcap1TrillionEur);
//RUN_TEST(test_Mcap1MillionEur);
RUN_TEST(test_Mcap1TrillionEurSmallChars);
RUN_TEST(test_Mcap1TrillionJpy);
RUN_TEST(test_Mcap1TrillionJpySmallChars);
RUN_TEST(test_PriceSuffixMode);
RUN_TEST(test_PriceSuffixModeCompact1);
RUN_TEST(test_PriceSuffixModeCompact2);
RUN_TEST(test_PriceSuffixModeMow);
RUN_TEST(test_PriceSuffixModeMowCompact);
return UNITY_END();
}
int main(void) {
return runUnityTests();
int main(void)
{
return runUnityTests();
}
extern "C" void app_main() {
runUnityTests();
extern "C" void app_main()
{
runUnityTests();
}

View file

@ -0,0 +1,75 @@
#include <utils.hpp>
#include <unity.h>
void test_parseMiningPoolStatsHashRate1dot34TH(void)
{
std::string hashrate;
std::string label;
std::string output;
parseHashrateString("1340000000000", label, output, 4);
TEST_ASSERT_EQUAL_STRING_MESSAGE("TH/S", label.c_str(), label.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("1.34", output.c_str(), output.c_str());
}
void test_parseMiningPoolStatsHashRate645GH(void)
{
std::string hashrate = "645000000000";
std::string label;
std::string output;
parseHashrateString(hashrate, label, output, 4);
TEST_ASSERT_EQUAL_STRING_MESSAGE("GH/S", label.c_str(), label.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("645", output.c_str(), output.c_str());
}
void test_parseMiningPoolStatsHashRateEmpty(void)
{
std::string hashrate = "";
std::string label;
std::string output;
parseHashrateString(hashrate, label, output, 4);
TEST_ASSERT_EQUAL_STRING_MESSAGE("H/S", label.c_str(), label.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output.c_str(), output.c_str());
}
void test_parseMiningPoolStatsHashRateZero(void)
{
std::string hashrate = "0";
std::string label;
std::string output;
parseHashrateString(hashrate, label, output, 4);
TEST_ASSERT_EQUAL_STRING_MESSAGE("H/S", label.c_str(), label.c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("0", output.c_str(), output.c_str());
}
// not needed when using generate_test_runner.rb
int runUnityTests(void)
{
UNITY_BEGIN();
RUN_TEST(test_parseMiningPoolStatsHashRate1dot34TH);
RUN_TEST(test_parseMiningPoolStatsHashRate645GH);
RUN_TEST(test_parseMiningPoolStatsHashRateZero);
RUN_TEST(test_parseMiningPoolStatsHashRateEmpty);
return UNITY_END();
}
int main(void)
{
return runUnityTests();
}
extern "C" void app_main()
{
runUnityTests();
}

BIN
x509_crt_bundle Normal file

Binary file not shown.