GearSense devlog
What the tarmac taught the screen
A head unit hit the road and lost its voice for good. Building its replacement meant picking up a screen shaped nothing like the one before it — and that forced GearSense to learn something it never needed until now: how to know, precisely, where its own screen ends.
The primary head unit came off the bike. Nothing dramatic — a bump, a rattle, and by the time I noticed it was gone it had already found the tarmac. The screen survived. The USB serial port did not, permanently, on every setting we tried. Flashing still works; the live diagnostic connection that lets me actually see what the firmware is thinking does not. For a project built on “read the logs, trust the logs,” that’s not a cosmetic loss.
So the spare came off the shelf: a different Waveshare board, smaller screen, same ESP32-S3 family. And the first thing I noticed, before a single line of new code, was that its corners are rounded. Properly rounded — not a stylistic bezel choice, an actual constraint on where pixels can physically appear.
GearSense had never had to think about that before. Every layout decision up to this point assumed a rectangle: pixel (0,0) is a real, visible pixel, always. On this screen, that’s not true anymore. Text anchored where it used to sit safely now clips straight into the case.
Guessing the shape, wrong, on purpose
No vendor supplies a pixel mask for “which pixels are actually visible on this glass.” So the first attempt was a superellipse — a mathematically clean curve that can be tuned from a perfect circle to something almost rectangular, just by turning one exponent. It seemed like the obvious tool: one number, infinite shapes.
It was the wrong tool. A superellipse curves gradually across a wide stretch near every corner, no matter how high you push the exponent — that’s simply what the shape family does. What a real phone or watch bezel actually looks like can be something else entirely. The S3 off the shelf for example has long flat edges, then a sudden, genuine circular arc right at the corner. These are two different families of curve. No amount of tuning turns one into the other.
I only found this out by drawing the guessed boundary directly onto the glass and holding the two up against each other — the math looked fine until it met the actual hardware. The fix was to throw the model out, not tune it: a true rounded rectangle, flat sides and a circular corner radius, is a two-line calculation instead of a curve-fitting exercise, and it matched the real bezel on the first real comparison.
Giving elements a sense of where they are
Having the right boundary shape is only half of it. The other half is teaching the screen’s own content to respect it — automatically, not by hand-picking safe pixel coordinates for one specific display and hoping the next one is close enough.
The elements near the corners — the little CADENCE and SPEED status labels — got a real fix instead of a guessed one. Every element on screen can now be expressed in polar terms: an angle and a distance from the exact center of the display. If an element’s corner strays outside the safe boundary, the firmware pulls it inward along that same angle, one pixel at a time, until it fits — then remembers the answer.
That’s a genuinely different way of thinking about layout than “here are some pixel coordinates that happened to work on the unit on my desk.” The positions are computed from the shape of the glass itself, which means the same code should hold up on the next screen too — round, square, or something stranger — without a rewrite.
Getting it wrong before getting it right
I want to be honest that the first version of this solver had a bug, because the bug is more instructive than the fix. It assumed a text label’s vertical anchor marked the bottom of the letters. It actually marks the top — a detail buried in how the display library places a cursor. The solver dutifully computed a “safe” position for a box that was never the one actually drawn. The real text ended up further down the screen than anything had checked, and collided with the label underneath it.
The fix, once found, was a two-line change. Finding it took a photo, a moment of confusion, and going back to first principles about what the drawing code actually does versus what I’d assumed it did. That’s twice in one evening the boundary got modeled wrong before it got modeled right — which is a useful reminder that “obviously correct” geometry deserves the same scrutiny as anything else, maybe more.
Small, provable, not over-built
The connectivity dots next to CADENCE and SPEED had the same problem as the text, just never checked. Rather than extend the solver to handle a circle-and-rectangle combination — solvable, but more machinery than the problem needed — each dot simply moved to sit on the center side of its label instead of the corner side. That’s safe by a simple geometric fact: the rounded-rectangle boundary is convex around the screen’s center, so anything closer to center than an already-safe point is safe too, with no further checking required. Small problems don’t always need big solutions.
I was tempted, more than once tonight, to build the whole general version of this — every element aware of every neighbor, angles adjusting as well as distances, a proper constraint solver. None of that exists yet, on purpose. There’s exactly one place right now where two elements needed to know about each other, and that one case is handled directly. The bigger system gets built if and when a second case shows up that the narrow fix can’t cover — not before there’s evidence it’s needed.
Why this matters past one broken screen
None of tonight’s work was really about this one spare board. It was about GearSense no longer assuming a screen is a plain rectangle with pixel (0,0) always safe. That assumption was baked into the very first version of the head unit and nobody had ever needed to question it — until a board fell on the road and the replacement happened to be round-cornered.
Whatever screen GearSense ends up shipping on — and that’s genuinely still an open question — this is the layer that should make the choice a lot less consequential than it would otherwise be.
— Paul
Loading…