Skip to content

fix: bundle review findings from PRs #9-#13 into one release - #14

Merged
bin101 merged 10 commits into
mainfrom
merge/review-fixes
Jul 23, 2026
Merged

fix: bundle review findings from PRs #9-#13 into one release#14
bin101 merged 10 commits into
mainfrom
merge/review-fixes

Conversation

@bin101

@bin101 bin101 commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

Combines the five review-fix branches (#9, #10, #11, #12, #13) into one merge so they land in main as a single push and produce exactly one release instead of five — merged here in the same sensible order they were reviewed/built in, each --no-ff so the original commits/history are preserved:

  1. fix: prevent charging-mode repeat-send, tighten duty-cycle accounting #9 fix/firmware-correctness — charging-mode repeat-send bug, duty-cycle accounting
  2. fix: recover real port from description-less device labels, dead repo guard #10 fix/flasher-port-and-reposelected_port() bug, dead repo guard, flasher tests
  3. fix: harden battery reads, warning decode, beep command, and roster overflow #11 fix/robustness — battery-voltage clamp, warning-type validation, beep/roster hardening
  4. docs: fix CLAUDE.md/README drift, document RELEASE_PAT, pin dependencies #12 docs/license-and-drift — CLAUDE.md/README drift, RELEASE_PAT docs, dependency pinning
  5. refactor: replace Ui::tick()'s timeout if-chain with a data table #13 refactor/ui-tick-timeoutstick() timeout table refactor

Only one merge conflict occurred (README.md's fork-setup section, touched by both #10 and #12) — resolved by keeping both paragraphs (the corrected "no releases found" wording from #10, plus the RELEASE_PAT documentation from #12). Everything else merged cleanly, including the two places #9 and #13 both touch ui.cpp's tick() (no textual overlap between the two changes).

No wire-format changes anywhere in this set; no Protocol::kVersion bump needed.

Test plan (run against the fully merged tree)

  • pio run — firmware builds clean
  • pio test -e native — 62/62 pass
  • python flasher/flasher.py --check — self-test passes
  • pytest flasher/test_flasher.py — 14/14 pass

bin101 added 10 commits July 23, 2026 17:53
…uty-cycle accounting

Ui::tick() ran the pending warning-repeat retransmit before the Charging
early-return, so a repeat armed just before a charger was plugged in could
call Radio::send() after main.cpp had already put the radio to sleep for
charging mode. Move the repeat check below the early return and clear
pendingRepeatArmed on the charging rising edge so a stale repeat can't fire
on resume either.

Also close two duty-cycle budget gaps in Radio: the token bucket started
full on every boot, letting a brownout-reboot mid-ride exceed the EU868 1%
hourly limit by simply power-cycling (now starts empty); and a failed
transmit() still deducted airtime from the budget even though nothing was
actually sent over the air (now deducts only on success).

Finally, correct the `charge` console command's calibration hint, which told
users to flip INA219_CURRENT_CHARGE_SIGN to -1 when it is already -1 -- the
fix for a negative reading is +1.
selected_port() split the combobox label on "  (" to recover the actual
serial port, but a "likely the Vaura device" label for a port with an empty
description (list_ports.comports() can return one, notably for the ESP32-S3
native USB-CDC/JTAG port on some platforms) has no "  (" to split on --
selected_port() then returned the whole label, arrow suffix included, and
every action (flash/read/apply) targeted a nonexistent port. Pull the
label<->device mapping out of refresh_ports() into a plain build_port_labels()
helper so selected_port() can look the real port up directly instead of
re-parsing the display label, with the old split as a fallback for a stale
selection.

Also replace the dead "YOUR-GITHUB-USER" placeholder guard in
fetch_latest_release() -- GITHUB_REPO is already a real repo, so the guard
could never fire -- with an explicit HTTP 404 handler that surfaces a clear
"no releases yet, use Local file" message instead of a raw HTTPError. Update
the matching README fork-setup note.

Add flasher/test_flasher.py (pytest, no hardware/GUI) covering the new
port-label round trip, the `status` key=value parser, and the merged-binary
NVS split, wired into CI.
…verflow

- Power::refreshIfDue() now sanity-clamps the raw INA219 bus-voltage read to
  [0, 5]V before caching it. A glitched/out-of-range I2C read previously
  wrapped silently into a nonsensical uint16_t millivolt value that then
  propagated into the battery display, the low-battery latch, and (via the
  heartbeat) every peer's view of this rider's battery.

- Protocol::decode() now rejects a Warning packet whose type byte is outside
  the WarningType enum instead of letting it through to degrade only much
  later, at display time, to warningLabel()'s "?" fallback -- symmetric with
  how an unknown MsgType is already rejected. Covered by a new
  test_reject_unknown_warning_type native test.

- The `beep` console command now range-checks its frequency argument as a
  signed long before casting to unsigned, rejecting negative or absurd
  values (e.g. "beep -5" or "beep 999999") that previously slipped past a
  plain `== 0` check once wrapped into an unsigned int.

- Roster::findOrCreate() now logs once (not once per dropped heartbeat) when
  MAX_PEERS is exhausted, so a rider beyond the cap doesn't silently vanish
  with no trace on a safety-relevant device.

No wire-format changes.
…endencies

- CLAUDE.md's repository layout table was missing coop.{h,cpp} (the
  cooperative drop-off gossip mesh), charging_decision.{h,cpp}, and
  node_id.h -- all load-bearing, natively-tested modules. Added them plus
  the corresponding test directories.

- README referenced "protocol version 2" and "v2 is radio-incompatible with
  v1" in two places; the wire format has since moved to v3 (cooperative
  drop-off confirmation). Reworded both version-agnostically so this doesn't
  drift again on the next protocol bump, and fixed the hardware table's
  "Wio-SX1262 (LoRa)" label, which contradicted the GFSK design described a
  few paragraphs later.

- Documented the RELEASE_PAT repo secret auto-tag.yml needs to push release
  tags -- previously only GITHUB_REPO was mentioned in the fork setup notes,
  so a fresh fork's release pipeline would silently never tag or release
  anything.

- Added a License section to the README (the MIT LICENSE file itself already
  existed) plus a short third-party notice for the bundled libraries.

- Pinned Adafruit_INA219 to the PlatformIO registry (adafruit/Adafruit
  INA219@^1.2.3) instead of an unpinned git-HEAD URL -- confirmed this is
  the exact commit already in use (same publish timestamp) via a clean
  rebuild, so behavior is unchanged. Also floor-pinned certifi
  (>=2024.7.4, fixing a known-expired intermediate CA in older releases)
  and pyinstaller (>=6.0,<7) in release.yml.

- Translated flasher/requirements.txt's one remaining German comment.

No wire-format or runtime logic changes.
CLAUDE.md flags tick()'s timeout handling as the one place in ui.cpp's
four-way State sync that a plain if-chain, not a switch, so the compiler
can't catch a State that should time out but has no entry. Replaced the
eleven near-identical if-blocks with a small TimeoutEntry table ({State,
timeoutMs, optional action}) and a single loop, so adding a timed state is
now one row instead of a copy-pasted block that's easy to get subtly wrong.

BootChannelSelect keeps its distinct action (confirmBootChannel instead of
enterIdle) via the table's optional action function pointer.
DismissPrompt's extra per-tick candidate-refresh logic (not a timeout) stays
exactly where it was, unfolded into the table on purpose. Idle/RangeTest/
Charging remain absent, same as before.

Behavior is unchanged -- this is a pure refactor, verified by inspection
(every {state, timeout, action} triple carried over unmodified) plus a
clean build and the full native test suite.
# Conflicts:
#	README.md
@bin101
bin101 merged commit 9afb7a8 into main Jul 23, 2026
2 checks passed
@bin101
bin101 deleted the merge/review-fixes branch July 23, 2026 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant