Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save colonelpanic8/5f13db797ea7e50d5038fecf24159c3e to your computer and use it in GitHub Desktop.

Select an option

Save colonelpanic8/5f13db797ea7e50d5038fecf24159c3e to your computer and use it in GitHub Desktop.

glove80-rmk battery buginess discussion with Fable 5

Get on the latest. Something seems quite buggy with battery display, particular on the non central/slave side. Somehow the right half is now in a permanent state of displaying its battery status, in other cases it seems to never display its battery state.

I wonder if it could have to do with the intersection of layer specific lighting and conditional lighting layer logic, and also lighting being globally disabled/enabled.

Probably worth trying to disambiguate by like cartesian producting that whole thing and seeing what does remain broken and what doesnt by test deploying things.

Also could have to do with applying configuration at runtime, vs applied immediately with a flash.

Some thoughts:

I wonder if adding an api to glove80-control to be able to query exactly what each side is ACTUALLY displaying with lighting at the moment, and maybe even a “virtual press” api to be able to test all of this programatically could be a good idea (although i wonder about virtual press drifting from actual presses is a concern there).

Also, I’d like to be able to visualize battery charging vs discharging state with colors or something. that might actually help us debug as well. 1:20 PM

I’ll start by getting oriented — repo state, the glove80-rmk dependency, and what “latest” means here. hmmm, i do wonder if some of this work is generalizable to other split hardware that wants to have lighting. are the fixes also applying to go60? 3:53 PM

Good question — the repo docs claim go60 “reuses the Glove80 lighting/replication modules.” Let me check exactly how, because that determines whether the fixes flow through automatically: Broken as suspected — go60’s peripheral still calls the old name, and its central lacks the monitor entirely. Fixing both: Yes — automatically, and your question just caught a real break. Go60 doesn’t copy the code; its binaries #[path]-include the exact same source files ( lighting.rs , split_lighting.rs , central_lighting.rs ) with different board constants (30 LEDs/half, 40% ceiling). So all four wedge fixes compile straight into go60 with zero porting. But the include-by-path reuse has no compile-time contract, and checking your question exposed that my rename of peripheral_power_monitor() → power_monitor() had silently broken the go60 build — the glove80 dist recipe doesn’t compile go60, so nothing caught it, and CI (which attaches go60 bundles to master releases) would have failed on the previous push. Fixed and pushed: go60’s peripheral now uses the renamed monitor, and I registered it on go60’s central too, so go60 gets the VBUS-derived charge state on both halves exactly like the Glove80. Both go60 halves build clean; outer repo repinned (3aaf4f6). No reflash needed — the glove80 UF2s are byte-identical across this commit. On generalizability more broadly, three tiers: 1. Already generalized (crudely): the whole replication layer is board-parameterized only by constants (BOARD_LEDS_PER_HALF, capacities, topology) — that’s why two boards share it. But path-includes are the fragile form of sharing, as just demonstrated: renames and signature changes break consumers invisibly. 2. The natural next step: lift it into a real crate (in-workspace split-lighting-replication, const-generic over LED counts and capacities). Same code, but the compiler checks every consumer, and a third board becomes a Cargo.toml line plus pin definitions. 3. The interesting endgame: upstream it into rmk itself. Any RMK split board with per-key lighting has this exact problem — a peripheral that needs the declarative state and context replicated atomically over a lossy bounded channel — and rmk currently offers only the raw split_app bytes. The lighting stack is already heading upstream as PR 1031; the replication protocol (especially once it carries the digest/attestation layer we designed, which is what makes it robust for boards we’ve never tested) would be a strong follow-up chapter of that PR. The wedge diagnosis basically wrote its design-rationale section for us. I’d sequence it: finish observability + attestation on the current shape first (they’re the proof the protocol converges), then do the crate extraction as the cleanup that carries it toward PR 1031. Meanwhile the paused observability work and the assembly rebuild are still queued — want me to push on with those? hmmm another detail here is don’t lighting overrides in layers get represented as like some form of list? i guess order is important here in some sense but thats okay?

yeah i feel like the thing to upstream to rmk is like arbitrary half state sync? 3:57 PM

Two sharp observations, and the first one has a real consequence for the hashing design. On ordered lists: you’re right, and it splits by table. The scene table (plain per-layer colors) is semantically a map keyed (layer, slot) — duplicates are rejected, later writes replace. Order-free, so the order-independent XOR-fold is safe there. Composition order (which layer wins a shared LED) comes from the live layer stack, not from table order, so it’s context, not content. But the conditional table is genuinely an ordered list with later-wins semantics — your own config depends on it. The comment in your glove80.toml says it outright: “Order matters: later rules win the LEDs they share — level bands first, low-battery overrides next, charging last so it beats both.” Two conditional tables with the same cells in different order are different lighting programs. So an order-independent hash would false-match them — the digest must be order-sensitive for that domain: hash sequentially over cells in table order (or fold the index into each cell hash). Since we already chose full-recompute over incremental maintenance, sequential hashing costs nothing extra. Two supporting facts make this sound: the wire path preserves order end-to-end (FIFO channel, in-order BLE, staging pushes in arrival order), and the central must hash the right-half filtered projection it actually replicates — same filter, same order as the sender walk. So: set-hash for scenes, sequence-hash for conditionals. Good catch — that distinction would have been a subtle false-agreement bug. On “arbitrary half state sync”: yes — that’s the right altitude, and rmk is already begging for it. Look at what rmk’s split layer contains today: SplitMessage::Layer, ::BatteryStatus, ::ConnectionStatus, ::Pointing, each a hand-rolled, one-off state sync with its own ad-hoc semantics and its own latent bugs — and then the raw split_app bytes over which glove80-rmk hand-rolled a fifth, much more elaborate one (where we just found three wedges). The upstream pitch practically writes itself: one generic replicated-state primitive — • board (or rmk subsystem) registers a named state cell: a serializable type + an apply hook, optionally a delta type for cheap updates; • rmk owns the hard parts once: chunked atomic snapshot transfer over bounded messages, generation/revision, staging with all-or-nothing apply, deadline-based ack/retry, link-edge resync, and digest attestation with the order-sensitivity rule per cell (“map” vs “sequence” declared at registration); • existing hand-rolled syncs migrate onto it, lighting replication becomes its first big client, and any future split board gets correct sync for free. The wedge diagnosis is the design doc’s motivation section: every failure we found (partial-fill livelock, timer-restart starvation, up-edge flush, silent notification eviction) is a bug class that every hand-rolled sync can independently rediscover, and that a single hardened primitive kills everywhere. Routing-wise it’s fold/split-reliability territory rather than the lighting PR — sync of half state is a split-layer concern that lighting merely consumes. Sequencing stays as we said: prove the protocol on the current shape first (observability + attestation on glove80-rmk), then extract — first as the in-workspace crate, then as the upstream RFC with the attestation battle-tested. The generic primitive is a meaty design; when we get there it’s worth a proper design pass. Shall I resume the queued work (observability finish + assembly rebuild + attestation implementation)? okay are we almost done here? maybe we commit we have, and then you take a swing (separately) at that plan of upstreaming replicated state sync to rmk? 4:01 PM

Yes — we’re at a good stopping point on the firmware side. Wrap-up plan: preserve the observability work-in-progress, then write the upstreaming plan as a durable design doc. First, what the paused agent left uncommitted: Wrapped up. Here’s the final state of everything: Committed and pushed (glove80-rmk master, 8861bd0): • The four replication fixes + go60 power-monitor repair — live on your keyboard since the last flash. • docs/replicated-half-state-sync.md — the upstreaming plan, written as a proper design doc so it survives this conversation. It records: the four found bug classes as the motivating case; the split_sync primitive (durable vs ephemeral cells — tables get staged atomic snapshots + digests, fast-moving context gets seq-numbered last-value-wins deltas); a reliability-core table where every element maps to a specific defect we found; the digest rules including your order-sensitivity catch (map-shaped cells may use order-free folds, sequence-shaped cells like the later-wins conditional table must hash in order); multi-peripheral addressing from day one (today’s split_app assumes one peripheral); and the phased path — prove attestation here → extract the in-workspace crate (replacing the #[path] includes that already silently broke once) → RFC on fold/split-reliability → migrate rmk’s own Layer/BatteryStatus/ConnectionStatus syncs onto it. Checkpointed (local, deliberately unpushed): • The observability work: 3 commits on lighting-observability in the rmk worktree — protocol commands, frame readback, endpoint wiring, and I verified it before committing: rmk-types 95/95 (wire snapshots regenerate cleanly) and 5/5 integration tests including the new observability loopback. Pushing it is step one of the fork-fold cycle, which is the natural next work session along with the glove80-rmk split-fetch + CLI side. Outer repo: repins committed through b447189, config verified clean on the keyboard. So: the bug is fixed and flashed, charging/discharging colors exist for the first time (go hold Magic — left half should be blue while on USB), the observability implementation is two-thirds done and safely banked, and the upstream plan is written down where future sessions (and future you) will find it. The remaining arc — fork-fold rebuild, CLI, attestation implementation — is cleanly resumable from the task list and the doc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment