Firmware for BTClock which uses WebSockets to fetch data.
  • C++ 51.9%
  • C 43.6%
  • Python 1.6%
  • CMake 1.5%
  • Shell 1.4%
Find a file
Djuri Baars 49bbfd095b
All checks were successful
Lint / format (push) Successful in 30s
Lint / tidy (push) Successful in 3m16s
BTClock CI / host-tests (push) Successful in 2m6s
BTClock CI / webui-and-lfs (push) Successful in 3m20s
BTClock CI / build (btclock_rev_b_213epd) (push) Successful in 1m53s
BTClock CI / build (lolin_s3_mini_213epd) (push) Successful in 1m57s
BTClock CI / build (lolin_s3_mini_29epd) (push) Successful in 2m9s
BTClock CI / build (btclock_v8_213epd) (push) Successful in 13m51s
BTClock CI / release (push) Successful in 1m37s
fix(ota): downloadSHA256 must return only the hash, not the manifest line
The release `.sha256` files follow the standard shasum output format —
`<64-char hex>  <filename>\n` — but downloadSHA256() returned the whole
trimmed body, so expectedSHA256 ended up being something like
"bcd371…d31  lolin_s3_mini_213epd_firmware.bin". Comparing that against
the 64-char hex we compute in calculateSHA256() / verifyStagedPartition
SHA256() never matched.

Latent since v3.x. The WebUI path silently appeared to work because
Update.begin(U_SPIFFS) writes to the LittleFS partition incrementally
during the loop; Update.abort() on the SHA mismatch did not undo the
writes, so the new bundle was already in flash on the next boot. The
firmware path through esp_https_ota properly stages into a passive app
slot and only flips the boot pointer on _finish — abort-on-mismatch
correctly left the running app untouched, which is how this bug got
visible after the Phase 1 OTA refactor.

Fix: split off the first whitespace-delimited token after trim().

Verified end-to-end on Rev A: triggered /api/firmware/auto_update with
gitRev=7e17c1c (the post-3.5.1 fix-commit build) and watched the device
transition to gitRev=d8fc2ef (3.5.1 release commit) via OTA download +
SHA verify + esp_https_ota_finish + ESP.restart().
2026-05-25 02:06:43 +02:00
.forgejo/workflows Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00
.github/workflows ci(github): drop push-to-web-flasher step 2026-05-24 21:43:02 +02:00
.vscode Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00
data@249601477e Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00
docs Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00
lib chore(lint): apply clang-format + clang-tidy fixes across the tree 2026-05-24 23:19:19 +02:00
main fix(ota): downloadSHA256 must return only the hash, not the manifest line 2026-05-25 02:06:43 +02:00
scripts fix(rev): align gitRev and fsRev so the WebUI update-checker matches 2026-05-24 23:19:04 +02:00
tests chore(lint): apply clang-format + clang-tidy fixes across the tree 2026-05-24 23:19:19 +02:00
.clang-tidy ci(lint): scope clang-tidy to compile DB and relax warnings-as-errors 2026-05-08 16:08:21 +02:00
.gitignore Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00
.gitmodules chore(data): track webui v3 branch and bump to 3eab9ea 2026-05-05 22:06:53 +02:00
arduino_libraries.json Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00
CMakeLists.txt Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00
dependencies.lock Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00
LICENSE.txt Added license and readme 2023-11-10 23:33:24 +01:00
maintainers.yaml Bugfix for suffix compact mode 2024-12-05 04:32:32 +01:00
partition.csv Switch to leaner MCP23017 library, create new aligned partition tables 2024-12-18 19:47:03 +01:00
partition_8mb.csv Switch to leaner MCP23017 library, create new aligned partition tables 2024-12-18 19:47:03 +01:00
partition_16mb.csv Switch to leaner MCP23017 library, create new aligned partition tables 2024-12-18 19:47:03 +01:00
README.md Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00
renovate.json Add renovate.json 2024-12-27 08:28:10 +00:00
sdkconfig.defaults Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00
sdkconfig.defaults.btclock_rev_b_213epd Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00
sdkconfig.defaults.btclock_v8_213epd Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00
sdkconfig.defaults.lolin_s3_mini_29epd Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00
sdkconfig.defaults.lolin_s3_mini_213epd Release 3.5.0: ESP-IDF as canonical build system 2026-05-24 21:24:46 +02:00

BTClock v3

Latest release

BTClock CI

Software for the BTClock project. Built on the ESP-IDF with Arduino as a library, using WebSockets for all live data and making heavy use of native timers and interrupts.

See the docs repo for user-facing information and flashing instructions. Developer docs (architecture, build envs, API reference, testing, preferences) live in docs/ in this repo.

Features

  • Screens, each individually toggleable and shown in rotation with a configurable interval:

    • Block height
    • Clock
    • Halving countdown
    • Block fee rate
    • Sats per currency
    • BTC ticker (price)
    • Market cap
    • Bitcoin supply
    • Bitaxe hashrate / best difficulty (when Bitaxe is enabled)
    • Mining pool hashrate / earnings (when mining pool stats are enabled)
  • Steal focus on new block — display jumps to the block height screen when a new block is mined, with an LED flash in a color you choose.

  • Multiple data sources, each supporting the full list of configured currencies on the currency-dependent screens:

    • The default BTClock source
    • Any public mempool.space instance, including self-hosted. Prices come from Kraken and populate every currency in actCurrencies, not just USD.
    • A Nostr relay
    • A custom WebSocket endpoint that speaks the BTClock data format — see ws-go-server for a self-hostable reference implementation
  • Bitaxe integration — fetch hashrate and best difficulty from a local Bitaxe miner and show it on its own screens.

  • Mining pool stats — hashrate and (where the pool exposes it) earnings screens. Supported pools:

    • Braiins
    • Ocean
    • Noderunners
    • Satoshi Radio
    • public-pool.io
    • Self-hosted Public Pool
    • GoBrrr Pool
    • CKPool
    • EU CKPool

    Noderunners and Satoshi Radio additionally expose a pool-wide hashrate; a "Show pool-wide hashrate" toggle switches the screen from per-user to global without needing a username.

  • Nostr Zap notifier — flash the LEDs (and frontlight on supported hardware) and optionally switch the screen when a zap lands on a given pubkey.

  • Frontlight control — brightness, effect speed, flash on block/zap, always-on, and auto-off when the ambient light sensor reports a dark room (on hardware that has one).

  • Time-based Do Not Disturb — dim/disable the display between a set start and end time.

  • Customisation — inverted colors, multiple fonts, LED brightness and flash color, currencies shown on the currency-dependent screens, big characters for market cap, block countdown mode, sats symbol, price suffix formatting, MoW mode (Samson-Mow-style price in millions, e.g. 0.1M instead of 100k), Moscow Time toggle that switches the USD sats-per-currency screen between the classic "MSCW/TIME" label and the generic "SATS/USD" used by the other currencies, and more.

  • Multi-language WebUI — English, Dutch, German, Spanish.

  • Over-the-air updates — ESP OTA via the WebUI, and ArduinoOTA for push updates over the network.

  • mDNS discovery so you can reach the device by hostname.

Security: the device is meant to run on a trusted private network. Since 3.2.0 the WebUI can be password-protected and the firmware verifies server certificates on outbound requests. Since 3.4.0 the ArduinoOTA push-update mechanism can be password-protected too.

Building

Built with ESP-IDF 5.5 and arduino-esp32 as a managed component. The short version:

git clone --recurse-submodules https://git.btclock.dev/btclock/btclock_v3.git
cd btclock_v3
source ~/esp/esp-idf/export.sh    # IDF v5.5 installed at ~/esp/esp-idf
./scripts/build.sh               # all four shipping variants

Full build, flash, and OTA instructions are in docs/BUILD.md. Tests are in docs/TESTING.md.

Mining pool stats

Enable mining pool stats from the WebUI under Settings → Extra Features → Enable Mining Pool Stats, then pick your pool and enter your pool username or API key.

The Mining Pool Earnings screen displays:

  • Braiins: today's mining reward so far.
  • Ocean: your estimated earnings if the pool were to find a block right now.

For solo pools there are no earnings estimations; your username is the on-chain withdrawal address you configured at the pool, without the worker name.

Braiins Pool integration

Create an API key using the steps here. The key's permissions should be:

  • Web Access: no
  • API Access: yes
  • Access Permissions: Read-only

Copy the token and enter it as your "Mining Pool username or api key" in the WebUI.

Ocean integration

Your "Mining Pool username" is the on-chain withdrawal address you specify when pointing your miners at Ocean.

Self-hosted Public Pool

Select Local Public Pool as the mining pool and enter the endpoint of your node's pool API (for example umbrel.local:2019). The WebUI has a "Test" button that verifies it can reach the instance.