Live heart-rate monitoring in PE and fitness classes is already a solved problem, commercially (and expensively). There's a whole category of systems sold to PE departments and athletic trainers that put every student's heart rate on a display in real time. They also cost thousands of dollars for a class set, frequently with per-student subscriptions on top, and they carry a lot of overhead to actually run: proprietary sensors, dedicated receivers or a companion app, student accounts, and data that lives on the vendor's servers. Someone who just wants live heart rates on their laptop or projected on their wall has to buy into an entire platform to get them.
Cost and complexity are what keep this out of most gyms, because the idea itself is sound. Tracking heart rate lets you grade what a student actually does in class, their effort, instead of their raw fitness. A mile time mostly reflects things a 45-minute class can't change: growth spurts, body type, asthma, whether a family can afford club sports. Heart-rate zones measure one thing every student controls equally.
Meanwhile, AliExpress sells generic "M4"-style BLE fitness bands with real optical heart-rate sensors for about a dollar each in bulk. Outfitting a whole class costs less than one commercial chest strap. The hardware already existed and cost almost nothing. What was missing was software simple enough for a teacher to run on a laptop they already own, so I wrote it.
Note: Many bands appear to be available from different sellers- I would search AliExpress and eBay specifically for M4 bands that look like the above examples.
What it DoesA teacher pairs each band to a student name once, and the pairing persists across launches. The Projector tab goes up on the wall, and every student gets a tile showing live BPM and effort zone (green, yellow, or red) set against configurable zone thresholds. Readings log per student for after-class review in the History tab. Everything stays on the laptop: no cloud, no accounts, nothing leaving the machine. Privacy was considered from start to finish.
The whole thing ships as a native app. Tagged releases include ready-to-run app-images for Linux, Windows, and macOS with a bundled Java 26 runtime, so a teacher unzips, double-clicks, and starts pairing bands.
Modern Java, put to WorkThis is a Java 26 project top to bottom (Gradle toolchain 26, --release 26 --enable-preview), and several of the newer language features are doing real work here.
Polling a classroom of bands is a natural fit for virtual threads plus StructuredTaskScope (JEP 525, in its current open(Joiner, Configuration) form). Each band measurement is 8 to 10 seconds of blocking BLE I/O, so the poller fans out one virtual thread per band under a scope whose Joiner.allUntil(_ -> false) policy runs every subtask to completion. One flaky band can't take down the rest of the class. Records carry every immutable value that crosses a thread boundary: readings, snapshots, students. The BLE protocol decoder is a sealed interface with pattern matching, where Frame permits exactly its known variants, so when I added a new frame type mid-project the compiler walked me to every switch that needed updating. java.util.concurrent.Flow keeps the core module GUI-agnostic (JavaFX subscribes and marshals onto the FX thread), and sequenced collections give the history view its most-recent-first ordering for free.
Getting heart rate off a one-dollar band was most of the work, and almost none of it was graceful. The band advertised itself as B01H_M4, but "M4" is a label stuck on wildly different hardware rather than a real model, so every protocol I tried was a confident guess against the wrong target. Gadgetbridge, the usual savior for cheap wearables, couldn't identify it either. I tried its FitPro integration, the documented match for M4-branded bands, then Lefun after reading an issue about a visually similar band, and both hung mid-sync. nRF Connect showed the band exposing a Nordic UART pipe plus a vendor 0xFF00 service, but with no idea what commands to send, subscribing to the notification characteristic just gave me a stream of single ASCII bytes: O, H, M, marching through the alphabet. I spent a while trying to decode that as heart-rate data before realizing it was idle status chatter. The fix, when it came, wasn't anything clever. I began investigating an advertised companion app on some of the band listings: Yoho Sports. The app itself had been pulled from the store, but the name was the thread to pull. Searching the Yoho Sports protocol led to a Gadgetbridge issue for the Laxasfit Q11, where fr3ts0n had reverse-engineered the entire df…-framed command set (heart rate, SpO2, blood pressure, the 8-bit-sum checksum, all of it)). Every frame in that spec matched my captures byte for byte. The bands were unbranded Laxasfit clones, and I'd been asking them questions in three wrong languages before I found the listing that told me their real name.
BPM Coach's LaxasfitProtocol is an independent Java implementation of that spec, verified against real hardware captures. Making it reliable enough for a classroom took a second round of work, and some highlights are documented here:
- A captured frame that had been labeled "finger not seated" turned out to be a real 82 bpm reading truncated at exactly the 20-byte default-MTU boundary; its failing checksum reconciled to the byte once the missing fragment was accounted for. That's what drove the
FrameReassemblerwith resynchronization, so a dropped notification now costs one frame instead of the rest of the session. Stale reconnects on the band's aggressive low-power radio pushed the design toward persistent per-band connections, with link health judged purely by observed behavior rather thanisConnected(), which kept reporting links that were already dead. And because BLE adapters cap out at 7 to 10 concurrent links, a roster larger than the cap rotates pool residency using rules I worked out in a controlled reconnect experiment: bands are admitted only on a fresh advertisement sighting, least-recently-serviced first, with an integration test asserting that a 9-band roster all gets data within two rounds. - Cross-platform BLE on the JVM comes from SimpleBLE's Java binding, which puts BlueZ, WinRT, and CoreBluetooth behind one API.
Grab the app-image for your OS from the GitHub Releases page, unzip it, and run bin/bpm-coach (or bpm-coach.exe on Windows). It launches straight into live mode: turn on a band, open the Pairing tab, and it shows up in the scan list within a cycle or two.
Pair it to any name you wish! On the Zone Config tab you can tune what heart rates trigger what colors:
Flip to the Projector tab, and put the band on your wrist. You will start to see heart rate reports! You can also see your band's history of heart rates report on the History tab:
To run from source instead, ./gradlew :desktop:run gives you the identical experience; the Gradle wrapper auto-provisions the Java 26 toolchain, so all you need is any JDK to bootstrap the wrapper and a Bluetooth adapter. The README's troubleshooting guide covers every real-hardware failure mode I hit during development.
At a few thousand dollars, a class set needs a grant application or a line in the budget. At thirty, a teacher can cover it out of pocket.








Comments