Compare commits

..

3 commits

Author SHA1 Message Date
ee81edbcd2 exclude light sensor calls on unsupported devices
add mqtt default host define
2024-12-06 11:08:55 +01:00
adf6b38bfb add will message on <device>/status topic, so status is always available even when offline.
add wifi/bssid topic to track which AP is connected
fix reconnect breaking out of loop
2024-12-04 15:44:02 +01:00
67f1d03919 add mqtt module 2024-12-02 15:03:28 +01:00
55 changed files with 1013 additions and 2185 deletions

View file

@ -1,4 +1,4 @@
name: "BTClock CI" name: 'BTClock CI'
on: on:
push: push:
@ -22,7 +22,7 @@ jobs:
with: with:
node-version: lts/* node-version: lts/*
cache: yarn cache: yarn
cache-dependency-path: "**/yarn.lock" cache-dependency-path: '**/yarn.lock'
- uses: actions/cache@v4 - uses: actions/cache@v4
with: with:
path: | path: |
@ -34,8 +34,8 @@ jobs:
key: ${{ runner.os }}-pio key: ${{ runner.os }}-pio
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: "3.9" python-version: '3.9'
cache: "pip" cache: 'pip'
- name: Get current date - name: Get current date
id: dateAndTime id: dateAndTime
shell: bash shell: bash
@ -48,7 +48,7 @@ jobs:
run: mkdir -p junit-reports && pio test -e native_test_only --junit-output-path junit-reports/ run: mkdir -p junit-reports && pio test -e native_test_only --junit-output-path junit-reports/
- name: Build BTClock firmware - name: Build BTClock firmware
shell: bash shell: bash
run: pio run run: pio run
- name: Build BTClock filesystem - name: Build BTClock filesystem
shell: bash shell: bash
run: pio run --target buildfs run: pio run --target buildfs
@ -81,9 +81,9 @@ jobs:
version: esp32s3 version: esp32s3
epd_variant: [213epd, 29epd] epd_variant: [213epd, 29epd]
exclude: exclude:
- chip: { name: btclock_rev_b, version: esp32s3 } - chip: {name: btclock_rev_b, version: esp32s3}
epd_variant: 29epd epd_variant: 29epd
- chip: { name: btclock_v8, version: esp32s3 } - chip: {name: btclock_v8, version: esp32s3}
epd_variant: 29epd epd_variant: 29epd
steps: steps:
- uses: https://code.forgejo.org/forgejo/download-artifact@v4 - uses: https://code.forgejo.org/forgejo/download-artifact@v4
@ -93,10 +93,9 @@ jobs:
- name: Install esptools.py - name: Install esptools.py
run: pip install --upgrade esptool run: pip install --upgrade esptool
- name: Create merged firmware binary - name: Create merged firmware binary
shell: bash
run: | run: |
mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }}
if [ "${{ matrix.chip.name }}" == "btclock_v8" ]; then if [ "${{ matrix.chip.name }}" == "btclock_v8" ]; then
mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \
esptool.py --chip ${{ matrix.chip.version }} merge_bin \ esptool.py --chip ${{ matrix.chip.version }} merge_bin \
-o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \ -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \
--flash_mode dio \ --flash_mode dio \
@ -106,19 +105,10 @@ jobs:
0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.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 \ 0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \
0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \
0xDF0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs_16MB.bin 0x810000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.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 else
# Original command for other cases
mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && \
esptool.py --chip ${{ matrix.chip.version }} merge_bin \ esptool.py --chip ${{ matrix.chip.version }} merge_bin \
-o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \ -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin \
--flash_mode dio \ --flash_mode dio \
@ -126,26 +116,19 @@ jobs:
0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.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 \ 0xe000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/ota_data_initial.bin \
0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \ 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin \
0x380000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs_4MB.bin 0x370000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin
# Adjust the offset for littlefs or other files as needed for the original case # Adjust the offset for littlefs or other files as needed for the original case
fi fi
- name: Create checksum for firmware - 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 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 - 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 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 - name: Create checksum for littlefs partition
shell: bash 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
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 - 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 }} run: cp .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin .pio/boot_app0.bin ${{ matrix.chip.name }}_${{ matrix.epd_variant }}
@ -178,13 +161,13 @@ jobs:
- name: Create release - name: Create release
uses: https://code.forgejo.org/actions/forgejo-release@v2.4.0 uses: https://code.forgejo.org/actions/forgejo-release@v2.4.0
with: with:
url: "https://git.btclock.dev" url: 'https://git.btclock.dev'
repo: "${{ github.repository }}" repo: '${{ github.repository }}'
direction: upload direction: upload
tag: "${{ github.ref_name }}" tag: '${{ github.ref_name }}'
sha: "${{ github.sha }}" sha: '${{ github.sha }}'
release-dir: release release-dir: release
token: ${{ secrets.TOKEN }} token: ${{ secrets.TOKEN }}
override: ${{ github.ref_type != 'tag' && github.ref_name != 'main' }} override: ${{ github.ref_type != 'tag' && github.ref_name != 'main' }}
prerelease: ${{ github.ref_type != 'tag' && github.ref_name != 'main' }} prerelease: ${{ github.ref_type != 'tag' && github.ref_name != 'main' }}
release-notes-assistant: false release-notes-assistant: false

View file

@ -17,7 +17,7 @@ Biggest differences with v2 are:
New features: New features:
- BitAxe integration - BitAxe integration
- Zap notifier - Zap notifier
- Braiins Pool and Ocean mining stats integration -
"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. "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.
@ -27,31 +27,4 @@ Most [information](https://github.com/btclock/btclock_v2/wiki) about BTClock v2
## Building ## 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://github.com/btclock/webui) submodule.
## Braiins Pool and Ocean integration
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
### 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.

2
data

@ -1 +1 @@
Subproject commit fd328d4f05345eaa73cf27d05bb542eaa6915cdb Subproject commit f0fa58b5ea60f695aeaae9ddd7138cbb3686e96a

View file

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

View file

@ -73,8 +73,7 @@ std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, char cu
std::string priceString; std::string priceString;
if (std::to_string(price).length() >= NUM_SCREENS || useSuffixFormat) if (std::to_string(price).length() >= NUM_SCREENS || useSuffixFormat)
{ {
int numScreens = shareDot || mowMode ? NUM_SCREENS - 1 : NUM_SCREENS - 2; priceString = getCurrencySymbol(currencySymbol) + formatNumberWithSuffix(price, NUM_SCREENS - 2, mowMode);
priceString = getCurrencySymbol(currencySymbol) + formatNumberWithSuffix(price, numScreens, mowMode);
} }
else else
{ {
@ -85,24 +84,16 @@ std::array<std::string, NUM_SCREENS> parsePriceData(std::uint32_t price, char cu
{ {
priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' '); priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' ');
if (mowMode) ret[0] = "BTC/" + getCurrencyCode(currencySymbol);
{
ret[0] = "MOW/UNITS";
}
else
{
ret[0] = "BTC/" + getCurrencyCode(currencySymbol);
}
firstIndex = 1; firstIndex = 1;
} }
size_t dotPosition = priceString.find('.'); if (shareDot)
if (shareDot && dotPosition != std::string::npos && dotPosition > 0)
{ {
std::vector<std::string> tempArray; std::vector<std::string> tempArray;
size_t dotPosition = priceString.find('.');
if (dotPosition != std::string::npos && dotPosition > 0) if (dotPosition != std::string::npos && dotPosition > 0)
{ {
for (size_t i = 0; i < priceString.length(); ++i) for (size_t i = 0; i < priceString.length(); ++i)
@ -146,26 +137,9 @@ std::array<std::string, NUM_SCREENS> parseSatsPerCurrency(std::uint32_t price,ch
if (priceString.length() < (NUM_SCREENS)) if (priceString.length() < (NUM_SCREENS))
{ {
// Check if price is greater than 1 billion priceString.insert(priceString.begin(), NUM_SCREENS - priceString.length(), ' ');
if (price >= 100000000)
{
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
{
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 (currencySymbol != CURRENCY_USD)
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); ret[0] = "SATS/" + getCurrencyCode(currencySymbol);
else else
ret[0] = "MSCW/TIME"; ret[0] = "MSCW/TIME";
@ -348,9 +322,9 @@ emscripten::val parseBlockHeightArray(std::uint32_t blockHeight)
return arrayToStringArray(parseBlockHeight(blockHeight)); return arrayToStringArray(parseBlockHeight(blockHeight));
} }
emscripten::val parsePriceDataArray(std::uint32_t price, const std::string &currencySymbol, bool useSuffixFormat = false, bool mowMode = false, bool shareDot = false) emscripten::val parsePriceDataArray(std::uint32_t price, const std::string &currencySymbol, bool useSuffixFormat = false)
{ {
return arrayToStringArray(parsePriceData(price, currencySymbol[0], useSuffixFormat, mowMode, shareDot)); return arrayToStringArray(parsePriceData(price, currencySymbol[0], useSuffixFormat));
} }
emscripten::val parseHalvingCountdownArray(std::uint32_t blockHeight, bool asBlocks) emscripten::val parseHalvingCountdownArray(std::uint32_t blockHeight, bool asBlocks)

View file

@ -83,31 +83,14 @@ std::string formatNumberWithSuffix(std::uint64_t num, int numCharacters, bool mo
} }
// Add suffix // Add suffix
int len; int len = snprintf(result, sizeof(result), "%.0f%c", numDouble, suffix);
// 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 there's room, add more decimal places
if (len < numCharacters) if (len < numCharacters)
{ {
int restLen = mowMode ? numCharacters - len : numCharacters - len - 1; int restLen = mowMode ? numCharacters - len : numCharacters - len - 1;
if (mowMode) { snprintf(result, sizeof(result), "%.*f%c", restLen, numDouble, suffix);
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; return result;

View file

@ -1,20 +0,0 @@
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

@ -3,5 +3,5 @@ nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000, otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x1b8000, app0, app, ota_0, 0x10000, 0x1b8000,
app1, app, ota_1, , 0x1b8000, app1, app, ota_1, , 0x1b8000,
spiffs, data, spiffs, , 0x66C00, spiffs, data, spiffs, , 0x66800,
coredump, data, coredump,, 0x10000, coredump, data, coredump,, 0x10000,
1 # Name Type SubType Offset Size Flags
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x1b8000
5 app1 app ota_1 0x1b8000
6 spiffs data spiffs 0x66C00 0x66800
7 coredump data coredump 0x10000

View file

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

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

View file

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

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

View file

@ -20,9 +20,8 @@ framework = arduino, espidf
monitor_speed = 115200 monitor_speed = 115200
monitor_filters = esp32_exception_decoder, colorize monitor_filters = esp32_exception_decoder, colorize
board_build.filesystem = littlefs board_build.filesystem = littlefs
extra_scripts = pre:scripts/pre_script.py, post:scripts/extra_script.py extra_scripts = post:scripts/extra_script.py
board_build.embed_files = board_build.embed_files = x509_crt_bundle
x509_crt_bundle
build_flags = build_flags =
!python scripts/git_rev.py !python scripts/git_rev.py
-DLAST_BUILD_TIME=$UNIX_TIME -DLAST_BUILD_TIME=$UNIX_TIME
@ -36,13 +35,14 @@ lib_deps =
https://github.com/joltwallet/esp_littlefs.git https://github.com/joltwallet/esp_littlefs.git
bblanchon/ArduinoJson@^7.2.1 bblanchon/ArduinoJson@^7.2.1
mathieucarbou/ESPAsyncWebServer @ 3.3.23 mathieucarbou/ESPAsyncWebServer @ 3.3.23
robtillaart/MCP23017@^0.8.0 adafruit/Adafruit BusIO@^1.16.2
adafruit/Adafruit MCP23017 Arduino Library@^2.3.2
adafruit/Adafruit NeoPixel@^1.12.3 adafruit/Adafruit NeoPixel@^1.12.3
https://github.com/dsbaars/universal_pin#feature/mcp23017_rt https://github.com/dsbaars/universal_pin
https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/dsbaars/GxEPD2#universal_pin
https://github.com/tzapu/WiFiManager.git#v2.0.17 https://github.com/tzapu/WiFiManager.git#v2.0.17
rblb/Nostrduino@1.2.8 rblb/Nostrduino@1.2.8
elims/PsychicMqttClient@^0.2.0 knolleary/PubSubClient@2.8
[env:lolin_s3_mini] [env:lolin_s3_mini]
extends = btclock_base extends = btclock_base
@ -65,7 +65,7 @@ build_unflags =
[env:btclock_rev_b] [env:btclock_rev_b]
extends = btclock_base extends = btclock_base
board = btclock_rev_b board = btclock_rev_b
board_build.partitions = partition_8mb.csv board_build.partitions = partition.csv
build_flags = build_flags =
${btclock_base.build_flags} ${btclock_base.build_flags}
-D MCP_INT_PIN=8 -D MCP_INT_PIN=8

View file

@ -5,9 +5,6 @@ from shutil import copyfileobj, rmtree
from pathlib import Path from pathlib import Path
import subprocess import subprocess
revision = ( revision = (
subprocess.check_output(["git", "rev-parse", "HEAD"]) subprocess.check_output(["git", "rev-parse", "HEAD"])
.strip() .strip()
@ -46,15 +43,5 @@ def before_buildfs(source, target, env):
output_directory = 'data/build_gz' output_directory = 'data/build_gz'
process_directory(input_directory, output_directory) process_directory(input_directory, output_directory)
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"] = "" os.environ["PUBLIC_BASE_URL"] = ""
fs_name = env.get("ESP32_FS_IMAGE_NAME", "littlefs.bin") env.AddPreAction("$BUILD_DIR/littlefs.bin", before_buildfs)
# 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)

View file

@ -1,7 +0,0 @@
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)

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
char *wsServer; char *wsServer;
esp_websocket_client_handle_t blockNotifyClient = NULL; esp_websocket_client_handle_t blockNotifyClient = NULL;
uint currentBlockHeight = 873400; uint currentBlockHeight = 860000;
uint blockMedianFee = 1; uint blockMedianFee = 1;
bool blockNotifyInit = false; bool blockNotifyInit = false;
unsigned long int lastBlockUpdate; unsigned long int lastBlockUpdate;

View file

@ -5,15 +5,15 @@ const TickType_t debounceDelay = pdMS_TO_TICKS(50);
TickType_t lastDebounceTime = 0; TickType_t lastDebounceTime = 0;
#ifdef IS_BTCLOCK_V8 #ifdef IS_BTCLOCK_V8
#define BTN_1 256 #define BTN_1 0
#define BTN_2 512 #define BTN_2 1
#define BTN_3 1024 #define BTN_3 2
#define BTN_4 2048 #define BTN_4 3
#else #else
#define BTN_1 2048 #define BTN_1 3
#define BTN_2 1024 #define BTN_2 2
#define BTN_3 512 #define BTN_3 1
#define BTN_4 256 #define BTN_4 0
#endif #endif
void buttonTask(void *parameter) { void buttonTask(void *parameter) {
@ -22,12 +22,11 @@ void buttonTask(void *parameter) {
std::lock_guard<std::mutex> lock(mcpMutex); std::lock_guard<std::mutex> lock(mcpMutex);
TickType_t currentTime = xTaskGetTickCount(); TickType_t currentTime = xTaskGetTickCount();
if ((currentTime - lastDebounceTime) >= debounceDelay) { if ((currentTime - lastDebounceTime) >= debounceDelay) {
lastDebounceTime = currentTime; lastDebounceTime = currentTime;
if (!digitalRead(MCP_INT_PIN)) { if (!digitalRead(MCP_INT_PIN)) {
uint pin = mcp1.getInterruptFlagRegister(); uint pin = mcp1.getLastInterruptPin();
switch (pin) { switch (pin) {
case BTN_1: case BTN_1:
@ -44,12 +43,12 @@ void buttonTask(void *parameter) {
break; break;
} }
} }
mcp1.getInterruptCaptureRegister(); mcp1.clearInterrupts();
} else { } else {
} }
// Very ugly, but for some reason this is necessary // Very ugly, but for some reason this is necessary
while (!digitalRead(MCP_INT_PIN)) { while (!digitalRead(MCP_INT_PIN)) {
mcp1.getInterruptCaptureRegister(); mcp1.clearInterrupts();
} }
} }
} }

View file

@ -2,12 +2,10 @@
#define MAX_ATTEMPTS_WIFI_CONNECTION 20 #define MAX_ATTEMPTS_WIFI_CONNECTION 20
// zlib_turbo zt;
Preferences preferences; Preferences preferences;
MCP23017 mcp1(0x20); Adafruit_MCP23X17 mcp1;
#ifdef IS_BTCLOCK_V8 #ifdef IS_BTCLOCK_V8
MCP23017 mcp2(0x21); Adafruit_MCP23X17 mcp2;
#endif #endif
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT
@ -37,7 +35,7 @@ void setup()
} }
{ {
std::lock_guard<std::mutex> lockMcp(mcpMutex); std::lock_guard<std::mutex> lockMcp(mcpMutex);
if (mcp1.read1(3) == LOW) if (mcp1.digitalRead(3) == LOW)
{ {
preferences.putBool("wifiConfigured", false); preferences.putBool("wifiConfigured", false);
preferences.remove("txPower"); preferences.remove("txPower");
@ -48,7 +46,7 @@ void setup()
} }
{ {
if (mcp1.read1(0) == LOW) if (mcp1.digitalRead(0) == LOW)
{ {
// Then loop forever to prevent anything else from writing to the screen // Then loop forever to prevent anything else from writing to the screen
while (true) while (true)
@ -56,7 +54,7 @@ void setup()
delay(1000); delay(1000);
} }
} }
else if (mcp1.read1(1) == LOW) else if (mcp1.digitalRead(1) == LOW)
{ {
preferences.clear(); preferences.clear();
queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS); queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS);
@ -68,7 +66,6 @@ void setup()
} }
setupWifi(); setupWifi();
// loadIcons();
setupWebserver(); setupWebserver();
@ -95,15 +92,10 @@ void setup()
setupBitaxeFetchTask(); setupBitaxeFetchTask();
} }
if (preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED))
{
setupMiningPoolStatsFetchTask();
}
if (preferences.getBool("mqttEnabled", DEFAULT_MQTT_ENABLED)) if (preferences.getBool("mqttEnabled", DEFAULT_MQTT_ENABLED))
{ {
if (setupMqtt()) setupMqtt();
setupMqttTask(); setupMqttTask();
} }
setupButtonTask(); setupButtonTask();
@ -120,7 +112,6 @@ void setup()
#endif #endif
forceFullRefresh(); forceFullRefresh();
} }
void setupWifi() void setupWifi()
@ -147,7 +138,7 @@ void setupWifi()
bool buttonPress = false; bool buttonPress = false;
{ {
std::lock_guard<std::mutex> lockMcp(mcpMutex); std::lock_guard<std::mutex> lockMcp(mcpMutex);
buttonPress = (mcp1.read1(2) == LOW); buttonPress = (mcp1.digitalRead(2) == LOW);
} }
{ {
@ -342,14 +333,6 @@ void setupPreferences()
addScreenMapping(SCREEN_BITAXE_HASHRATE, "BitAxe Hashrate"); addScreenMapping(SCREEN_BITAXE_HASHRATE, "BitAxe Hashrate");
addScreenMapping(SCREEN_BITAXE_BESTDIFF, "BitAxe Best Difficulty"); 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) String replaceAmbiguousChars(String input)
@ -530,7 +513,7 @@ void setupHardware()
Wire.begin(I2C_SDA_PIN, I2C_SCK_PIN, 400000); Wire.begin(I2C_SDA_PIN, I2C_SCK_PIN, 400000);
if (!mcp1.begin()) if (!mcp1.begin_I2C(0x20))
{ {
Serial.println(F("Error MCP23017 1")); Serial.println(F("Error MCP23017 1"));
@ -540,20 +523,17 @@ void setupHardware()
else else
{ {
pinMode(MCP_INT_PIN, INPUT_PULLUP); 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++) for (int i = 0; i < 4; i++)
{ {
mcp1.pinMode1(i, INPUT_PULLUP); mcp1.pinMode(i, INPUT_PULLUP);
mcp1.enableInterrupt(i, LOW); mcp1.setupInterruptPin(i, LOW);
} }
#ifndef IS_BTCLOCK_V8 #ifndef IS_BTCLOCK_V8
for (int i = 8; i <= 14; i++) for (int i = 8; i <= 14; i++)
{ {
mcp1.pinMode1(i, OUTPUT); mcp1.pinMode(i, OUTPUT);
} }
#endif #endif
} }
@ -564,7 +544,7 @@ void setupHardware()
#endif #endif
#ifdef IS_BTCLOCK_V8 #ifdef IS_BTCLOCK_V8
if (!mcp2.begin()) if (!mcp2.begin_I2C(0x21))
{ {
Serial.println(F("Error MCP23017 2")); Serial.println(F("Error MCP23017 2"));
@ -816,32 +796,4 @@ const char* getFirmwareFilename() {
} else { } else {
return ""; 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";
}
}
// void loadIcons() {
// size_t ocean_logo_size = 886;
// int iUncompSize = zt.gzip_info((uint8_t *)epd_compress_bitaxe, ocean_logo_size);
// Serial.printf("uncompressed size = %d\n", iUncompSize);
// uint8_t *pUncompressed;
// pUncompressed = (uint8_t *)malloc(iUncompSize+4);
// int rc = zt.gunzip((uint8_t *)epd_compress_bitaxe, ocean_logo_size, pUncompressed);
// if (rc == ZT_SUCCESS) {
// Serial.println("Decode success");
// }
// }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
#include <MCP23017.h> #include <Adafruit_MCP23X17.h>
#include <Arduino.h> #include <Arduino.h>
#include <Preferences.h> #include <Preferences.h>
#include <WiFiClientSecure.h> #include <WiFiClientSecure.h>
@ -18,7 +18,6 @@
#include "lib/ota.hpp" #include "lib/ota.hpp"
#include "lib/nostr_notify.hpp" #include "lib/nostr_notify.hpp"
#include "lib/bitaxe_fetch.hpp" #include "lib/bitaxe_fetch.hpp"
#include "lib/mining_pool_stats_fetch.hpp"
#include "lib/v2_notify.hpp" #include "lib/v2_notify.hpp"
@ -85,6 +84,4 @@ void addScreenMapping(int value, const char* name);
int findScreenIndexByValue(int value); int findScreenIndexByValue(int value);
String replaceAmbiguousChars(String input); String replaceAmbiguousChars(String input);
const char* getFirmwareFilename(); const char* getFirmwareFilename();
const char* getWebUiFilename();
// void loadIcons();

View file

@ -45,8 +45,6 @@
#define DEFAULT_FL_EFFECT_DELAY 15 #define DEFAULT_FL_EFFECT_DELAY 15
#define DEFAULT_LUX_LIGHT_TOGGLE 128 #define DEFAULT_LUX_LIGHT_TOGGLE 128
#define DEFAULT_FL_OFF_WHEN_DARK true
#define DEFAULT_FL_ALWAYS_ON false #define DEFAULT_FL_ALWAYS_ON false
#define DEFAULT_FL_FLASH_ON_UPDATE false #define DEFAULT_FL_FLASH_ON_UPDATE false
@ -58,12 +56,8 @@
#define DEFAULT_BITAXE_ENABLED false #define DEFAULT_BITAXE_ENABLED false
#define DEFAULT_BITAXE_HOSTNAME "bitaxe1" #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_MQTT_ENABLED false #define DEFAULT_MQTT_ENABLED false
#define DEFAULT_MQTT_URL "" #define DEFAULT_MQTT_HOST ""
#define DEFAULT_MQTT_ROOTTOPIC "home/" #define DEFAULT_MQTT_ROOTTOPIC "home/"
#define DEFAULT_ZAP_NOTIFY_ENABLED false #define DEFAULT_ZAP_NOTIFY_ENABLED false
@ -78,4 +72,3 @@
#define DEFAULT_ACTIVE_CURRENCIES "USD,EUR,JPY" #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_GIT_RELEASE_URL "https://git.btclock.dev/api/v1/repos/btclock/btclock_v3/releases/latest"
#define DEFAULT_VERTICAL_DESC true

View file

@ -191,7 +191,7 @@ void setupDisplays()
} }
// Hold lower button to enable "storage mode" (prevents burn-in of ePaper displays) // Hold lower button to enable "storage mode" (prevents burn-in of ePaper displays)
if (mcp1.read1(0) == LOW) if (mcp1.digitalRead(0) == LOW)
{ {
setFgColor(GxEPD_BLACK); setFgColor(GxEPD_BLACK);
setBgColor(GxEPD_WHITE); setBgColor(GxEPD_WHITE);
@ -373,11 +373,7 @@ extern "C" void updateDisplay(void *pvParameters) noexcept
void splitText(const uint dispNum, const String &top, const String &bottom, void splitText(const uint dispNum, const String &top, const String &bottom,
bool partial) bool partial)
{ {
if(preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC) && dispNum == 0) { displays[dispNum].setRotation(2);
displays[dispNum].setRotation(1);
} else {
displays[dispNum].setRotation(2);
}
displays[dispNum].setFont(&FONT_SMALL); displays[dispNum].setFont(&FONT_SMALL);
displays[dispNum].setTextColor(getFgColor()); displays[dispNum].setTextColor(getFgColor());
@ -604,46 +600,18 @@ void renderIcon(const uint dispNum, const String &text, bool partial)
displays[dispNum].setTextColor(getFgColor()); displays[dispNum].setTextColor(getFgColor());
uint iconIndex = 0; uint iconIndex = 0;
uint width = 122;
uint height = 122;
if (text.endsWith("rocket")) { if (text.endsWith("rocket")) {
iconIndex = 1; iconIndex = 1;
} }
else if (text.endsWith("lnbolt")) {
iconIndex = 2; if (text.endsWith("lnbolt")) {
}
else if (text.endsWith("bitaxe")) {
width = 122;
height = 250;
iconIndex = 3; iconIndex = 3;
} }
else if (text.endsWith("miningpool")) {
LogoData logo = getMiningPoolLogo();
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;
}
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());
// displays[dispNum].drawInvertedBitmap(0,0, getOceanIcon(), 122, 250, getFgColor());
displays[dispNum].drawInvertedBitmap(0,0, epd_icons_allArray[iconIndex], 122, 250, getFgColor());
} }
void renderQr(const uint dispNum, const String &text, bool partial) void renderQr(const uint dispNum, const String &text, bool partial)
{ {
#ifdef USE_QR #ifdef USE_QR

View file

@ -4,7 +4,6 @@
#include <Fonts/FreeSansBold9pt7b.h> #include <Fonts/FreeSansBold9pt7b.h>
#include <GxEPD2_BW.h> #include <GxEPD2_BW.h>
#include <mcp23x17_pin.hpp> #include <mcp23x17_pin.hpp>
#include <mutex> #include <mutex>
#include <native_pin.hpp> #include <native_pin.hpp>
@ -14,7 +13,6 @@
#include "lib/config.hpp" #include "lib/config.hpp"
#include "lib/shared.hpp" #include "lib/shared.hpp"
#include "icons/icons.h" #include "icons/icons.h"
#include "mining_pool_stats_fetch.hpp"
#ifdef USE_QR #ifdef USE_QR
#include "qrcodegen.h" #include "qrcodegen.h"

View file

@ -1,32 +0,0 @@
#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
{
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)};
}
LogoData BraiinsPool::getLogo() const {
return LogoData{
.data = epd_icons_allArray[5],
.width = 122,
.height = 250
};
}

View file

@ -1,20 +0,0 @@
#pragma once
#include "lib/mining_pool/mining_pool_interface.hpp"
#include <icons/icons.h>
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;
LogoData getLogo() 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"; }
private:
static int getHashrateMultiplier(const std::string &unit);
};

View file

@ -1,14 +0,0 @@
// src/noderunners/noderunners_pool.cpp
#include "gobrrr_pool.hpp"
std::string GoBrrrPool::getApiUrl() const {
return "https://pool.gobrrr.me/api/client/" + poolUser;
}
LogoData GoBrrrPool::getLogo() const {
return LogoData {
.data = epd_icons_allArray[7],
.width = 122,
.height = 122
};
}

View file

@ -1,15 +0,0 @@
#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"; }
LogoData getLogo() const override;
};

View file

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

View file

@ -1,23 +0,0 @@
#pragma once
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "pool_stats.hpp"
#include "logo_data.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 = 0;
virtual std::string getDisplayLabel() const = 0;
virtual bool supportsDailyEarnings() const = 0;
virtual std::string getDailyEarningsLabel() const = 0;
protected:
std::string poolUser;
};

View file

@ -1,122 +0,0 @@
#include "mining_pool_stats_handler.hpp"
#include <iostream>
std::array<std::string, NUM_SCREENS> parseMiningPoolStatsHashRate(std::string text, const MiningPoolInterface& pool)
{
std::array<std::string, NUM_SCREENS> ret;
ret.fill(""); // Initialize all elements to empty strings
std::string hashrate;
std::string label;
if (text.length() > 21) {
// We are massively future-proof!!
label = "ZH/S";
hashrate = text.substr(0, text.length() - 21);
} else if (text.length() > 18) {
label = "EH/S";
hashrate = text.substr(0, text.length() - 18);
} else if (text.length() > 15) {
label = "PH/S";
hashrate = text.substr(0, text.length() - 15);
} else if (text.length() > 12) {
label = "TH/S";
hashrate = text.substr(0, text.length() - 12);
} else if (text.length() > 9) {
label = "GH/S";
hashrate = text.substr(0, text.length() - 9);
} else if (text.length() > 6) {
label = "MH/S";
hashrate = text.substr(0, text.length() - 6);
} else if (text.length() > 3) {
label = "KH/S";
hashrate = text.substr(0, text.length() - 3);
} else {
label = "H/S";
hashrate = text;
}
std::size_t textLength = hashrate.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] = hashrate.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

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

View file

@ -1,42 +0,0 @@
// 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);
return PoolStats{
.hashrate = value + std::string(multiplier, '0'),
.dailyEarnings = std::nullopt
};
}
LogoData NoderunnersPool::getLogo() const {
return LogoData {
.data = epd_icons_allArray[6],
.width = 122,
.height = 122
};
}
int NoderunnersPool::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

@ -1,22 +0,0 @@
#pragma once
#include "lib/mining_pool/mining_pool_interface.hpp"
#include <icons/icons.h>
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;
LogoData getLogo() 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
protected:
static int getHashrateMultiplier(char unit);
};

View file

@ -1,26 +0,0 @@
#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
)
};
}
LogoData OceanPool::getLogo() const {
return LogoData{
.data = epd_icons_allArray[4],
.width = 122,
.height = 122
};
}

View file

@ -1,18 +0,0 @@
#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;
LogoData getLogo() 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"; }
};

View file

@ -1,25 +0,0 @@
#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";
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();
}

View file

@ -1,44 +0,0 @@
#pragma once
#include "mining_pool_interface.hpp"
#include <memory>
#include <string>
#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"
class PoolFactory {
public:
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;
}
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;
};

View file

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

View file

@ -1,21 +0,0 @@
// 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

@ -1,15 +0,0 @@
#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

@ -1,6 +0,0 @@
// 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

@ -1,14 +0,0 @@
#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

@ -1,99 +0,0 @@
#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)
{
for (;;)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
HTTPClient http;
http.setUserAgent(USER_AGENT);
std::string poolName = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME).c_str();
std::string poolUser = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER).c_str();
auto poolInterface = PoolFactory::createPool(poolName);
if (!poolInterface)
{
Serial.println("Unknown mining pool: \"" + String(poolName.c_str()) + "\"");
continue;
}
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 setupMiningPoolStatsFetchTask()
{
xTaskCreate(taskMiningPoolStatsFetch, "miningPoolStatsFetch", (6 * 1024), NULL, tskIDLE_PRIORITY,
&miningPoolStatsFetchTaskHandle);
xTaskNotifyGive(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()
{
return getMiningPool()->getLogo();
}

View file

@ -1,19 +0,0 @@
#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

@ -2,9 +2,8 @@
TaskHandle_t mqttTaskHandle = NULL; TaskHandle_t mqttTaskHandle = NULL;
// WiFiClient wifiClient; WiFiClient wifiClient;
//PubSubClient client(wifiClient); PubSubClient client(wifiClient);
PsychicMqttClient mqttClient;
// avoid circular deps, just forward declare externs used here. // avoid circular deps, just forward declare externs used here.
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT
@ -28,46 +27,43 @@ const String getDeviceTopic()
fullTopic += "/"; fullTopic += "/";
} }
fullTopic += hostname + "/"; fullTopic += hostname + "/";
return String(fullTopic); return String(fullTopic);
} }
// boolean connectMqtt() boolean connectMqtt()
// {
// const String willTopic = getDeviceTopic() + "status";
// mqttClient.connect()
// boolean result = client.connect("btclockClient", willTopic.c_str(), 0, true, "offline");
// if (!result)
// {
// Serial.println("[MQTT] could not connect");
// return result;
// }
// publish("status", "online", true);
// return result;
// }
boolean setupMqtt()
{ {
const String url = preferences.getString("mqttUrl", DEFAULT_MQTT_URL);
if (url == "")
{
Serial.println("[MQTT] url not set");
return false;
}
Serial.print("[MQTT] url: ");
Serial.println(url.c_str());
mqttClient.setClientId(getMyHostname().c_str());
mqttClient.setServer(url.c_str());
// if (url.startsWith("mqtts:") || url.startsWith("wss:"))
// mqttClient.attachArduinoCACertBundle();
mqttClient.connect();
const String willTopic = getDeviceTopic() + "status"; const String willTopic = getDeviceTopic() + "status";
mqttClient.setWill(willTopic.c_str(), 0, 1, "offline"); boolean result = client.connect("btclockClient", willTopic.c_str(), 0, true, "offline");
if (!result)
{
Serial.println("[MQTT] could not connect");
return result;
}
publish("status", "online", true); publish("status", "online", true);
return result;
}
return true; void setupMqtt()
{
const String host = preferences.getString("mqttHost", DEFAULT_MQTT_HOST);
if (host == "")
{
Serial.println("[MQTT] host not set");
return;
}
Serial.print("[MQTT] host: ");
Serial.println(host.c_str());
IPAddress addr((uint32_t)0);
if(!WiFi.hostByName(host.c_str(), addr))
{
Serial.println("[MQTT] host lookup fail");
return;
}
client.setServer(addr, 1883);
client.setCallback(onMqttCallback);
connectMqtt();
} }
void mqttTask(void *pvParameters) void mqttTask(void *pvParameters)
@ -75,10 +71,17 @@ void mqttTask(void *pvParameters)
int t=0; int t=0;
while (1) while (1)
{ {
// client.loop(); client.loop();
vTaskDelay(pdMS_TO_TICKS(1000)); vTaskDelay(pdMS_TO_TICKS(1000));
if (t++ % 10 == 0) if (t++ % 10 == 0)
{ {
if (!client.connected())
{
// reconnect
if (!connectMqtt())
continue;
}
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT
if (hasLightLevel()) if (hasLightLevel())
{ {
@ -106,25 +109,22 @@ void setupMqttTask()
xTaskCreate(mqttTask, "mqttTask", 8192, NULL, 10, &mqttTaskHandle); xTaskCreate(mqttTask, "mqttTask", 8192, NULL, 10, &mqttTaskHandle);
} }
void publishForDevice(const char *topic, const char *payload) void publish(const char *topic, const char *value)
{ {
publishForDevice(topic, payload, false); publish(topic, value, false);
} }
void publishForDevice(const char *topic, const char *payload, boolean retain) void publish(const char *topic, const char *value, boolean retain)
{ {
if (!client.connected())
{
Serial.println("[MQTT] not connected");
return;
}
const String fullTopic = getDeviceTopic() + topic; const String fullTopic = getDeviceTopic() + topic;
publish(fullTopic.c_str(), payload, retain);
}
void publish(const char *topic, const char *payload) if (!client.publish(fullTopic.c_str(), value, retain))
{
publish(topic, payload, false);
}
void publish(const char *topic, const char *payload, boolean retain)
{
if (!mqttClient.publish(topic, 0, retain, payload))
{ {
Serial.println("[MQTT] could not write"); Serial.println("[MQTT] could not write");
} }

View file

@ -1,13 +1,10 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <WiFiClientSecure.h>
#include <PsychicMqttClient.h>
#include "lib/shared.hpp" #include "lib/shared.hpp"
#include "PubSubClient.h"
boolean setupMqtt(); void setupMqtt();
void setupMqttTask(); void setupMqttTask();
void publishForDevice(const char *topic, const char *payload); void publish(const char *topic, const char *value);
void publishForDevice(const char *topic, const char *payload, boolean retain); void publish(const char *topic, const char *value, boolean retain);
void publish(const char *topic, const char *payload);
void publish(const char *topic, const char *payload, boolean retain);

View file

@ -171,13 +171,14 @@ int downloadUpdateHandler(char updateType)
break; break;
case UPDATE_WEBUI: case UPDATE_WEBUI:
{ {
latestRelease = getLatestRelease(getWebUiFilename()); latestRelease = getLatestRelease("littlefs.bin");
// updateWebUi(latestRelease.fileUrl, U_SPIFFS); // updateWebUi(latestRelease.fileUrl, U_SPIFFS);
// return 0; // return 0;
} }
break; break;
} }
// First, download the expected SHA256 // First, download the expected SHA256
String expectedSHA256 = downloadSHA256(latestRelease.checksumUrl); String expectedSHA256 = downloadSHA256(latestRelease.checksumUrl);
if (expectedSHA256.isEmpty()) if (expectedSHA256.isEmpty())

View file

@ -8,7 +8,7 @@ const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin";
// WebsocketsClient client; // WebsocketsClient client;
esp_websocket_client_handle_t clientPrice = NULL; esp_websocket_client_handle_t clientPrice = NULL;
esp_websocket_client_config_t config; esp_websocket_client_config_t config;
uint currentPrice = 90000; uint currentPrice = 50000;
unsigned long int lastPriceUpdate; unsigned long int lastPriceUpdate;
bool priceNotifyInit = false; bool priceNotifyInit = false;
std::map<char, std::uint64_t> currencyMap; std::map<char, std::uint64_t> currencyMap;

View file

@ -33,19 +33,9 @@ void workerTask(void *pvParameters) {
parseBitaxeBestDiff(getBitaxeBestDiff()); parseBitaxeBestDiff(getBitaxeBestDiff());
} }
setEpdContent(taskEpdContent); setEpdContent(taskEpdContent);
break;
}
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;
} }
break;
case TASK_PRICE_UPDATE: { case TASK_PRICE_UPDATE: {
uint currency = getCurrentCurrency(); uint currency = getCurrentCurrency();
uint price = getPrice(currency); uint price = getPrice(currency);
@ -189,17 +179,6 @@ void setCurrentScreen(uint newScreen) {
return; return;
} }
break; 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;
} }
} }

View file

@ -6,7 +6,6 @@
#include <data_handler.hpp> #include <data_handler.hpp>
#include <bitaxe_handler.hpp> #include <bitaxe_handler.hpp>
#include "lib/mining_pool/mining_pool_stats_handler.hpp"
#include "lib/epd.hpp" #include "lib/epd.hpp"
#include "lib/shared.hpp" #include "lib/shared.hpp"
@ -24,8 +23,7 @@ typedef enum {
TASK_BLOCK_UPDATE, TASK_BLOCK_UPDATE,
TASK_FEE_UPDATE, TASK_FEE_UPDATE,
TASK_TIME_UPDATE, TASK_TIME_UPDATE,
TASK_BITAXE_UPDATE, TASK_BITAXE_UPDATE
TASK_MINING_POOL_STATS_UPDATE
} TaskType; } TaskType;
typedef struct { typedef struct {

View file

@ -143,12 +143,4 @@ String calculateSHA256(WiFiClient *stream, size_t contentLength) {
} }
return result; 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);
// }

View file

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "MCP23017.h" #include <Adafruit_MCP23X17.h>
// #include <zlib_turbo.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <WiFiClientSecure.h> #include <WiFiClientSecure.h>
#include <Preferences.h> #include <Preferences.h>
@ -18,9 +17,9 @@
#include "defaults.hpp" #include "defaults.hpp"
extern MCP23017 mcp1; extern Adafruit_MCP23X17 mcp1;
#ifdef IS_BTCLOCK_V8 #ifdef IS_BTCLOCK_V8
extern MCP23017 mcp2; extern Adafruit_MCP23X17 mcp2;
#endif #endif
extern Preferences preferences; extern Preferences preferences;
extern std::mutex mcpMutex; extern std::mutex mcpMutex;
@ -42,16 +41,24 @@ const PROGMEM int SCREEN_BLOCK_FEE_RATE = 6;
const PROGMEM int SCREEN_SATS_PER_CURRENCY = 10; const PROGMEM int SCREEN_SATS_PER_CURRENCY = 10;
const PROGMEM int SCREEN_BTC_TICKER = 20; const PROGMEM int SCREEN_BTC_TICKER = 20;
// const PROGMEM int SCREEN_BTC_TICKER_USD = 20;
// const PROGMEM int SCREEN_BTC_TICKER_EUR = 21;
// const PROGMEM int SCREEN_BTC_TICKER_GBP = 22;
// const PROGMEM int SCREEN_BTC_TICKER_JPY = 23;
// const PROGMEM int SCREEN_BTC_TICKER_AUD = 24;
// const PROGMEM int SCREEN_BTC_TICKER_CAD = 25;
const PROGMEM int SCREEN_MARKET_CAP = 30; const PROGMEM int SCREEN_MARKET_CAP = 30;
// const PROGMEM int SCREEN_MARKET_CAP_USD = 30;
const PROGMEM int SCREEN_MINING_POOL_STATS_HASHRATE = 70; // const PROGMEM int SCREEN_MARKET_CAP_EUR = 31;
const PROGMEM int SCREEN_MINING_POOL_STATS_EARNINGS = 71; // const PROGMEM int SCREEN_MARKET_CAP_GBP = 32;
// const PROGMEM int SCREEN_MARKET_CAP_JPY = 33;
// const PROGMEM int SCREEN_MARKET_CAP_AUD = 34;
// const PROGMEM int SCREEN_MARKET_CAP_CAD = 35;
const PROGMEM int SCREEN_BITAXE_HASHRATE = 80; const PROGMEM int SCREEN_BITAXE_HASHRATE = 80;
const PROGMEM int SCREEN_BITAXE_BESTDIFF = 81; const PROGMEM int SCREEN_BITAXE_BESTDIFF = 81;
const PROGMEM int SCREEN_COUNTDOWN = 98; const PROGMEM int SCREEN_COUNTDOWN = 98;
const PROGMEM int SCREEN_CUSTOM = 99; const PROGMEM int SCREEN_CUSTOM = 99;
const int SCREEN_COUNT = 7; const int SCREEN_COUNT = 7;
@ -66,12 +73,7 @@ const int usPerMinute = 60 * usPerSecond;
extern const char *isrg_root_x1cert; extern const char *isrg_root_x1cert;
extern const uint8_t rootca_crt_bundle_start[] asm("_binary_x509_crt_bundle_start"); extern const uint8_t rootca_crt_bundle_start[] asm("_binary_x509_crt_bundle_start");
// extern const uint8_t ocean_logo_comp[] asm("_binary_ocean_gz_start");
// extern const uint8_t ocean_logo_comp_end[] asm("_binary_ocean_gz_end");
// 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_FIRMWARE = U_FLASH;
const PROGMEM char UPDATE_WEBUI = U_SPIFFS; const PROGMEM char UPDATE_WEBUI = U_SPIFFS;
@ -83,15 +85,4 @@ struct ScreenMapping {
}; };
String calculateSHA256(uint8_t* data, size_t len); String calculateSHA256(uint8_t* data, size_t len);
String calculateSHA256(WiFiClient *stream, size_t contentLength); 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);
}
};
}

View file

@ -72,10 +72,6 @@ void IRAM_ATTR minuteTimerISR(void *arg) {
vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken); vTaskNotifyGiveFromISR(bitaxeFetchTaskHandle, &xHigherPriorityTaskWoken);
} }
if (miningPoolStatsFetchTaskHandle != NULL) {
vTaskNotifyGiveFromISR(miningPoolStatsFetchTaskHandle, &xHigherPriorityTaskWoken);
}
if (xHigherPriorityTaskWoken == pdTRUE) { if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR(); portYIELD_FROM_ISR();
} }

View file

@ -513,7 +513,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json)
String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey",
"nostrRelay", "bitaxeHostname", "nostrZapPubkey", "nostrRelay", "bitaxeHostname", "nostrZapPubkey",
"httpAuthUser", "httpAuthPass", "gitReleaseUrl", "httpAuthUser", "httpAuthPass", "gitReleaseUrl",
"mqttUrl", "mqttRootTopic"}; "mqttHost", "mqttRootTopic"};
for (String setting : strSettings) for (String setting : strSettings)
{ {
@ -549,10 +549,9 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json)
"mdnsEnabled", "otaEnabled", "stealFocus", "mdnsEnabled", "otaEnabled", "stealFocus",
"mcapBigChar", "useSatsSymbol", "useBlkCountdown", "mcapBigChar", "useSatsSymbol", "useBlkCountdown",
"suffixPrice", "disableLeds", "ownDataSource", "suffixPrice", "disableLeds", "ownDataSource",
"mowMode", "suffixShareDot", "flOffWhenDark", "mowMode", "suffixShareDot", "mqttEnabled",
"flAlwaysOn", "flDisable", "flFlashOnUpd", "flAlwaysOn", "flDisable", "flFlashOnUpd",
"mempoolSecure", "useNostr", "bitaxeEnabled", "mempoolSecure", "useNostr", "bitaxeEnabled",
"miningPoolStats", "verticalDesc", "mqttEnabled",
"nostrZapNotify", "stagingSource", "httpAuthEnabled"}; "nostrZapNotify", "stagingSource", "httpAuthEnabled"};
for (String setting : boolSettings) for (String setting : boolSettings)
@ -693,8 +692,6 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["suffixPrice"] = preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE); root["suffixPrice"] = preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE);
root["disableLeds"] = preferences.getBool("disableLeds", DEFAULT_DISABLE_LEDS); root["disableLeds"] = preferences.getBool("disableLeds", DEFAULT_DISABLE_LEDS);
root["mowMode"] = preferences.getBool("mowMode", DEFAULT_MOW_MODE); root["mowMode"] = preferences.getBool("mowMode", DEFAULT_MOW_MODE);
root["verticalDesc"] = preferences.getBool("verticalDesc", DEFAULT_VERTICAL_DESC);
root["suffixShareDot"] = preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT); root["suffixShareDot"] = preferences.getBool("suffixShareDot", DEFAULT_SUFFIX_SHARE_DOT);
root["hostnamePrefix"] = preferences.getString("hostnamePrefix", DEFAULT_HOSTNAME_PREFIX); root["hostnamePrefix"] = preferences.getString("hostnamePrefix", DEFAULT_HOSTNAME_PREFIX);
@ -717,18 +714,14 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["bitaxeEnabled"] = preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED); root["bitaxeEnabled"] = preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED);
root["bitaxeHostname"] = preferences.getString("bitaxeHostname", DEFAULT_BITAXE_HOSTNAME); root["bitaxeHostname"] = preferences.getString("bitaxeHostname", DEFAULT_BITAXE_HOSTNAME);
root["miningPoolStats"] = preferences.getBool("miningPoolStats", DEFAULT_MINING_POOL_STATS_ENABLED);
root["miningPoolName"] = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME);
root["miningPoolUser"] = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER);
root["availablePools"] = PoolFactory::getAvailablePools();
root["mqttEnabled"] = preferences.getBool("mqttEnabled", DEFAULT_MQTT_ENABLED); root["mqttEnabled"] = preferences.getBool("mqttEnabled", DEFAULT_MQTT_ENABLED);
root["mqttUrl"] = preferences.getString("mqttUrl", DEFAULT_MQTT_URL); root["mqttHost"] = preferences.getString("mqttHost", "");
root["mqttRootTopic"] = preferences.getString("mqttRootTopic", DEFAULT_MQTT_ROOTTOPIC); root["mqttRootTopic"] = preferences.getString("mqttRootTopic", DEFAULT_MQTT_ROOTTOPIC);
root["httpAuthEnabled"] = preferences.getBool("httpAuthEnabled", DEFAULT_HTTP_AUTH_ENABLED); root["httpAuthEnabled"] = preferences.getBool("httpAuthEnabled", DEFAULT_HTTP_AUTH_ENABLED);
root["httpAuthUser"] = preferences.getString("httpAuthUser", DEFAULT_HTTP_AUTH_USERNAME); root["httpAuthUser"] = preferences.getString("httpAuthUser", DEFAULT_HTTP_AUTH_USERNAME);
root["httpAuthPass"] = preferences.getString("httpAuthPass", DEFAULT_HTTP_AUTH_PASSWORD); root["httpAuthPass"] = preferences.getString("httpAuthPass", DEFAULT_HTTP_AUTH_PASSWORD);
#ifdef HAS_FRONTLIGHT #ifdef HAS_FRONTLIGHT
root["hasFrontlight"] = true; root["hasFrontlight"] = true;
root["flDisable"] = preferences.getBool("flDisable"); root["flDisable"] = preferences.getBool("flDisable");
@ -740,8 +733,6 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["hasLightLevel"] = hasLightLevel(); root["hasLightLevel"] = hasLightLevel();
root["luxLightToggle"] = preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE); root["luxLightToggle"] = preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE);
root["flOffWhenDark"] = preferences.getBool("flOffWhenDark", DEFAULT_FL_OFF_WHEN_DARK);
#else #else
root["hasFrontlight"] = false; root["hasFrontlight"] = false;
root["hasLightLevel"] = false; root["hasLightLevel"] = false;
@ -762,8 +753,17 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
#endif #endif
JsonArray screens = root["screens"].to<JsonArray>(); JsonArray screens = root["screens"].to<JsonArray>();
root["actCurrencies"] = getActiveCurrencies(); JsonArray actCurrencies = root["actCurrencies"].to<JsonArray>();
root["availableCurrencies"] = getAvailableCurrencies(); for (const auto &str : getActiveCurrencies())
{
actCurrencies.add(str);
}
JsonArray availableCurrencies = root["availableCurrencies"].to<JsonArray>();
for (const auto &str : getAvailableCurrencies())
{
availableCurrencies.add(str);
}
std::vector<ScreenMapping> screenNameMap = getScreenNameMap(); std::vector<ScreenMapping> screenNameMap = getScreenNameMap();

View file

@ -14,7 +14,6 @@
#include "lib/price_notify.hpp" #include "lib/price_notify.hpp"
#include "lib/screen_handler.hpp" #include "lib/screen_handler.hpp"
#include "webserver/OneParamRewrite.hpp" #include "webserver/OneParamRewrite.hpp"
#include "lib/mining_pool/pool_factory.hpp"
extern TaskHandle_t eventSourceTaskHandle; extern TaskHandle_t eventSourceTaskHandle;

View file

@ -51,7 +51,7 @@ extern "C" void app_main()
if (hasLightLevel()) { if (hasLightLevel()) {
if (preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0) if (preferences.getUInt("luxLightToggle", DEFAULT_LUX_LIGHT_TOGGLE) != 0)
{ {
if (hasLightLevel() && getLightLevel() <= 1 && preferences.getBool("flOffWhenDark", DEFAULT_FL_OFF_WHEN_DARK)) if (hasLightLevel() && getLightLevel() <= 2)
{ {
if (frontlightIsOn()) { if (frontlightIsOn()) {
frontlightFadeOutAll(); frontlightFadeOutAll();

View file

@ -33,17 +33,6 @@ void test_CorrectSatsPerDollarConversion(void)
TEST_ASSERT_EQUAL_STRING("4", output[NUM_SCREENS - 1].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_CorrectSatsPerPoundConversion(void) void test_CorrectSatsPerPoundConversion(void)
{ {
std::array<std::string, NUM_SCREENS> output = parseSatsPerCurrency(37253, CURRENCY_GBP, false); std::array<std::string, NUM_SCREENS> output = parseSatsPerCurrency(37253, CURRENCY_GBP, false);
@ -109,41 +98,9 @@ void test_PriceSuffixMode(void)
TEST_ASSERT_EQUAL_STRING("K", output[NUM_SCREENS - 1].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) void test_PriceSuffixModeMow(void)
{ {
std::array<std::string, NUM_SCREENS> output = parsePriceData(93600, '$', true, true); std::array<std::string, NUM_SCREENS> output = parsePriceData(93000, '$', true, true);
std::string joined = joinArrayWithBrackets(output); std::string joined = joinArrayWithBrackets(output);
@ -158,12 +115,11 @@ void test_PriceSuffixModeMow(void)
void test_PriceSuffixModeMowCompact(void) void test_PriceSuffixModeMowCompact(void)
{ {
std::array<std::string, NUM_SCREENS> output = parsePriceData(93600, '$', true, true, true); std::array<std::string, NUM_SCREENS> output = parsePriceData(93000, '$', true, true, true);
std::string joined = joinArrayWithBrackets(output); std::string joined = joinArrayWithBrackets(output);
TEST_ASSERT_EQUAL_STRING_MESSAGE("MOW/UNITS", output[0].c_str(), joined.c_str()); TEST_ASSERT_EQUAL_STRING("BTC/USD", output[0].c_str());
TEST_ASSERT_EQUAL_STRING_MESSAGE("$", output[NUM_SCREENS - 6].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 - 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 - 4].c_str(), joined.c_str());
@ -278,7 +234,6 @@ int runUnityTests(void)
UNITY_BEGIN(); UNITY_BEGIN();
RUN_TEST(test_CorrectSatsPerDollarConversion); RUN_TEST(test_CorrectSatsPerDollarConversion);
RUN_TEST(test_CorrectSatsPerPoundConversion); RUN_TEST(test_CorrectSatsPerPoundConversion);
RUN_TEST(test_SatsPerDollarAfter1B);
RUN_TEST(test_SixCharacterBlockHeight); RUN_TEST(test_SixCharacterBlockHeight);
RUN_TEST(test_SevenCharacterBlockHeight); RUN_TEST(test_SevenCharacterBlockHeight);
RUN_TEST(test_FeeRateDisplay); RUN_TEST(test_FeeRateDisplay);
@ -291,8 +246,6 @@ int runUnityTests(void)
RUN_TEST(test_Mcap1TrillionJpy); RUN_TEST(test_Mcap1TrillionJpy);
RUN_TEST(test_Mcap1TrillionJpySmallChars); RUN_TEST(test_Mcap1TrillionJpySmallChars);
RUN_TEST(test_PriceSuffixMode); RUN_TEST(test_PriceSuffixMode);
RUN_TEST(test_PriceSuffixModeCompact1);
RUN_TEST(test_PriceSuffixModeCompact2);
RUN_TEST(test_PriceSuffixModeMow); RUN_TEST(test_PriceSuffixModeMow);
RUN_TEST(test_PriceSuffixModeMowCompact); RUN_TEST(test_PriceSuffixModeMowCompact);