A field report from the Braidpool device dashboard, and everything that broke along the way
Hi, I’m Priya Rani, a 4th-year Computer Science student and open-source contributor. I’ve spent over a year working on @braidpool, mostly around its dashboard and device-monitoring infrastructure.
If you’d like to know more about me, feel free to connect with me on LinkedIn and check out my work on GitHub.
If you’ve ever tried to get a piece of physical mining hardware to talk cleanly to a piece of software, you already know the punchline: hardware doesn’t care about your abstractions. It has its own firmware, its own quirks, its own opinions about what a “float” is. Software wants clean, predictable types. Hardware, especially older hardware, does not RSVP to that party.
I’ve spent the last year and a half contributing to Braidpool under the mentorship of Bob McElrath. This decentralised, peer-to-peer Bitcoin mining pool replaces the usual centralised pool operator with a DAG of shares shared directly between miners. One of the pieces the project needed was simple to describe and stubborn to build: a dashboard that shows a miner’s own device analytics, hashrate, temperature, fan speed, and firmware version, and that works the same way for every user, on every device, with as little manual setup as possible.

This is the story of the three architectures it took to get there, why the first two didn’t survive contact with real hardware, and what the current design looks like.
If you’re interested in learning more about how Bitcoin transactions move through Braidpool and get confirmed, check out my previous article: How Bitcoin Transactions Get Confirmed on Braidpool.
The goal, restated simply#
Every solo miner or pool participant running Braidpool has some ASIC sitting on their network- could be a $150 Bitaxe, could be a decade-old Antminer S9. The dashboard’s job is to find that device, pull its telemetry, and show it without the user typing in IP addresses, model numbers, or API ports. Same experience, every user, every device.

That “same experience” requirement is the whole difficulty. It’s easy to build a dashboard for one known device. It’s a different problem to build one that’s honest about not knowing, in advance, what’s plugged into someone’s router.
Attempt 1: register the miner over RPC#
The first design was straightforward: add an RPC endpoint that a miner (or the user, via the UI) could call to register itself with Braidpool. PR #246 opened by Aritra8438 implemented exactly this a registration endpoint that would let miners announce themselves into the database.
It made sense on paper. In practice, it pushed the entire integration burden onto the user or onto each miner’s firmware supporting a Braidpool-specific call, which almost none of them do. It also didn’t solve the actual hard problem, which is: how do you find and read from wildly different ASIC hardware in the first place? Registration answered “how does Braidpool know a device exists” but said nothing about “how does Braidpool talk to it once it does.” The PR was dropped, and the search moved to the hardware-communication layer itself.
Attempt 2: pyasic, and the Antminer S9 problem#
The next stop was pyasic, a Python library built specifically to provide a single, standardised async interface across many different ASIC brands and firmwares, such as Antminer, WhatsMiner, Innosilicon, Avalon, and others, communicating with them through their native APIs or SSH instead of making the caller learn every miner’s dialect.
Connecting a Bitaxe through pyasic was close to painless. Bitaxes are small, modern, open-source, ESP32-based miners with a clean HTTP API, exactly the kind of device a library like this is built for.
The Antminer S9 was a different animal entirely. The S9 is old, first released in 2016, and some of its responses didn’t line up cleanly with what pyasic’s typed models expected.

Specifically:
- Pydantic validation errors: the S9’s configuration returned a temperature target of 74.8, while pyasic's TemperatureConfig expected an integer. Pydantic therefore rejected the fractional value with an int_from_floatvalidation error.
- Pool configuration errors: the S9’s pool-config responses didn’t consistently match the shape the library expected either.
The important part wasn’t that one S9 failed; it was that a single malformed response could propagate through the typed model and break the inventory path.
Neither of these is exotic. These are typical compatibility problems when legacy firmware responses meet modern typed abstractions. The fix at this stage was to write a thin API layer on top of pyasic whose whole job was catching and normalising these errors before they reached the rest of the system, so one cranky S9 wouldn’t take down the inventory service.
With that wrapper in place, the backend took shape: an inventory service using pyasic for device communication, an async SQLite database for storage, and a periodic async polling loop to keep telemetry fresh.
Related Prs: #471, #377, #364 , #340 Related Issue: #367
Attempt 3: switching to asic-rs#
Here’s where a completely different kind of constraint entered: most of the rest of Braidpool’s backend is written in Rust. pyasic, being the one Python dependency used for miner communication, was an odd seam in an otherwise Rust codebase: a different runtime, a different deployment story, and a separate set of dependencies to maintain.
One of the maintainers, Luke, suggested the natural fix: switch to asic-rs, a Rust library that solves essentially the same problem as pyasic, providing a common abstraction for discovering, querying, and controlling different ASIC miners.
The important distinction is that pyasic wasn’t being replaced because it was fundamentally incapable of doing the job. It worked. The move to asic-rs was primarily about bringing the miner integration into the same Rust stack as the rest of Braidpool, while also giving us a better discovery model.
The APIs are conceptually very similar:

API comparison between asic-rs and pyasic
With pyasic, we initially used a simple manual workflow: the user entered the miner’s IP address in the UI, and the backend stored it and used it to query the device. Since we only needed to support a single miner at the time, this was enough.
As the inventory service grew to support multiple miners, we moved toward automatic subnet discovery. pyasic also supports subnet discovery, but our earlier implementation didn’t need it. With asic-rs, we could build subnet scanning directly into the new inventory workflow and automatically discover supported miners on the local network.
The current architecture: auto-detected subnet scanning#
Rather than asking the user to type in a subnet like 192.168.1.0/24exactly the kind of setup step the "same for every user, easy to connect" goal was trying to avoid - the Inventory Service works it out for itself at startup:
- It enumerates the machine’s network interfaces and filters out the ones that aren’t the real local network loopback (127.0.0.1), Docker bridges, or WSL adapters.
- From the interface that’s actually connected to the LAN, it reads the machine’s IP address (say, 192.168.1.15) and subnet mask (255.255.255.0).
- From those two values, it computes the local network’s CIDR block 192.168.1.0/24 in this example.
- It hands that subnet to asic-rs, which scans every reachable host in it and queries any supported miner it finds over that miner’s HTTP API, pulling temperature, hashrate, firmware version, fan speed, and other telemetry.
- Results land in the database on a periodic cycle, and the dashboard filters what it shows down to devices that are actually associated with Braidpool, rather than every random host that happened to answer the scan.
No IP entry, no subnet math, no manual registration step: the user plugs their miner into the same network as the machine running the Inventory Service, and it shows up.
What a year of this actually taught me#
A few things kept coming up, and they’re worth naming plainly rather than dressing up:
- Old firmware doesn’t always fit modern abstractions. The S9 wasn’t “broken”; it was released years before the library trying to parse its output existed. Any integration layer for a heterogeneous fleet of ASICs has to assume that some devices will return data that doesn’t match modern type expectations, and build a normalisation layer instead of trusting strict validation everywhere.
- The best abstraction is the one that matches your stack. pyasic wasn’t wrong; it worked. But being the only Python service in a Rust backend is its own kind of tax: separate deploy path, separate dependency tree, one more thing that can drift out of sync. Sometimes the right move is switching libraries for organisational reasons, not technical failure.
- Discovery beats registration. Asking users or devices to announce themselves (attempt 1) puts the integration burden on the wrong side. Scanning for what’s already there (attempt 3) puts it where it belongs on the software that’s supposed to be doing the work.
- “Same for every user” really means “assume nothing about their network.” Auto-detecting the interface and subnet, instead of asking for input, is a small piece of code that removes an entire category of setup friction and support questions.
Where it stands now#
The inventory service auto-discovers the subnet, ASIC-RS scans it, telemetry lands in async SQLite on a periodic cycle, and the dashboard shows only the devices tied to Braidpool. Bitaxe and Antminer S9 both come up cleanly through the same pipeline now: one modern, one a decade old, same code path.

Current Dashboard analytics.
Different hardware, different firmware, different APIs, one discovery and telemetry path.
If you’re working on similar problems bridging inconsistent hardware APIs into a clean typed interface, or building auto-discovery for local devices, the Braidpool repo is open, and so are the docs on pyasic and asic-rs if you want to see how each library approaches the same problem in Python and Rust.
Acknowledgements#
I’d like to sincerely thank my mentor, Bob McElrath. Without his guidance and experience, I don’t think I would have been able to make so many meaningful contributions to Braidpool. His technical knowledge, patience, and willingness to discuss different approaches taught me a lot not just about the project, but about how to think through engineering problems more carefully.
Special thanks to Mohd Zaid, who helped me work through the Antminer S9 issues and the many errors that came along with them. His help in debugging problems, suggesting ideas, and thinking through ways to improve the dashboard made a huge difference throughout the project.
I’d also like to thank Ansh Sharma for his support and help throughout the project. His guidance, feedback, and willingness to help whenever I got stuck made the development process much smoother.
And finally, thank you to the entire Braidpool community, especially Luke and everyone who took the time to review, discuss, suggest improvements, and help along the way. Open source is built by people willing to share their knowledge, and I’m grateful to have been part of this community.
If you’d like to be part of the braidpool community, we’d love to have you join the community on Discord.
*This article is written by @priyaashuu for Bitshala.


Comments
No comments yet. Start the conversation.