Skip to content

feat: real XBee SPI transport and local AT commands (PR 8/10) - #43

Merged
thePunderWoman merged 1 commit into
mainfrom
firmware-rewrite/pr8-xbee-spi-transport
Sep 8, 2026
Merged

feat: real XBee SPI transport and local AT commands (PR 8/10)#43
thePunderWoman merged 1 commit into
mainfrom
firmware-rewrite/pr8-xbee-spi-transport

Conversation

@thePunderWoman

Copy link
Copy Markdown
Owner

Summary

PR 8 of the firmware rewrite (PR 1: #35, PR 2: #36, PR 3: #38, PR 4: #39, PR 5: #40, PR 6: #41, PR 7: #42). First functional radio link.

Splits the original plan's PR 8 ("XBee SPI transport + packet protocol") into two, per the plan's own explicit contingency to do so if it got unwieldy — it did. This PR is the transport half; the custom uplink/downlink state-report schema becomes PR 9, and the final complications/handedness/shutdown work becomes PR 10.

  • xbee_frame.h/.cpp: pure XBee API frame logic — checksum compute/validate, and AT Command (0x08) / AT Command Response (0x88) frame build/parse. Round-trip tested.
  • xbee_spi.cpp: thin SPI adapter (3MHz/MSB-first/mode 0, ATTN-polled reads) — ported from thePunderWoman/Amidala's proven xbee_spi.cpp read path (same XBee3 module family), extended with a write path and a blocking AT-command round trip. Excluded from native build/coverage.
  • xbee_control.cpp: XbeeControl now issues real AT commands (NR/ID/AC) for leave/set-PAN/rejoin instead of stubbing them out, and adds a real SL query for Device Info. Exact AT command sequencing is a best-effort reading of Digi's XBee3 manual, flagged in code as needing validation against real hardware. Now hardware-dependent, so it joins the native exclusion list.
  • droid_switcher.h/.cpp: DroidSwitcher (the pure orchestration logic, previously living alongside XbeeControl) moved to its own file so it stays compiled and covered natively even though XbeeControl itself is now excluded — this is what broke the build the first time through this PR, and is why it's a separate file now. menu.h depends on this instead of xbee_control.h, so the pure menu logic never needs to know XbeeControl/XbeeSpi exist.
  • Device Info now shows the module's real SL, queried once at boot, replacing PR 6's placeholder.

Test plan

  • pio test -e native — all 149 test cases pass
  • gcovr --exclude 'src/SnipsController\.ino' --exclude 'src/oled\.cpp' --exclude 'src/rgb_led\.cpp' --exclude 'src/calibration_store\.cpp' --exclude 'src/droid_persistence\.cpp' --exclude 'src/xbee_spi\.cpp' --exclude 'src/xbee_control\.cpp' --fail-under-line 90 — 98.4% line coverage
  • pio run -e esp32s3 — builds successfully
  • Flash to real hardware once boards arrive and the XBee module is provisioned in API mode (AP=1 — required, SPI is dead in transparent mode): confirm Device Info shows a real, correctly-formatted SL; confirm Switch Droid's leave/set-PAN/rejoin sequence actually works end to end (this is the part most likely to need adjusting — see the sequencing caveat in xbee_control.cpp)

🤖 Generated with Claude Code

First functional radio link: replaces the XbeeControl stub from PR 7
with a real implementation talking to the XBee3 over SPI, and splits
this PR out of the original PR 8 (transport + packet protocol) per the
rewrite plan's own contingency — this alone is big enough on its own.
The custom uplink/downlink state-report schema is now PR 9, and the
final complications/handedness/shutdown work becomes PR 10.

- xbee_frame.h/.cpp: pure XBee API frame logic — checksum compute/
  validate, and AT Command (0x08) / AT Command Response (0x88) frame
  build/parse. Round-trip tested.
- xbee_spi.cpp: thin SPI adapter (3MHz/MSB-first/mode 0, ATTN-polled
  reads) — ported from thePunderWoman/Amidala's proven xbee_spi.cpp read
  path (same module family), extended with a write path and a blocking
  AT-command round trip. Excluded from native build/coverage.
- xbee_control.cpp: XbeeControl now issues real AT commands (NR/ID/AC)
  for leave/set-PAN/rejoin, and adds a real SL query for Device Info.
  Exact AT command sequencing is a best-effort reading of Digi's XBee3
  manual, flagged in code for validation against real hardware. Now
  hardware-dependent, so it joins the native exclusion list.
- droid_switcher.h/.cpp: DroidSwitcher (the pure orchestration logic,
  previously living alongside the now-excluded XbeeControl) moved to its
  own file so it stays compiled and covered natively — this is what
  broke the build the first time through and is why it's now separate.
  menu.h now depends on this instead of xbee_control.h, so the pure menu
  logic never needs to know XbeeControl/XbeeSpi exist at all.

Device Info now shows the module's real SL once XbeeControl queries it
at boot, replacing the PR 6 placeholder.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@thePunderWoman
thePunderWoman merged commit 3cb1ae9 into main Sep 8, 2026
2 checks passed
@thePunderWoman
thePunderWoman deleted the firmware-rewrite/pr8-xbee-spi-transport branch September 8, 2026 01:50
thePunderWoman added a commit that referenced this pull request Sep 8, 2026
…44)

Adds Snips' own application-level payload format, defined fresh since
Amidala has no existing schema for this controller type (tracked as
thePunderWoman/Amidala#204) — this was split out of the original PR 8
once that grew large enough on its own (see PR 8, #43).

- xbee_frame.h/.cpp gains Transmit Request (0x10) / Receive Packet
  (0x90) frame build/parse, symmetric to PR 8's AT Command support —
  generic XBee envelope logic, pure and round-trip tested. Defaults to
  addressing the coordinator (64-bit 0, 16-bit 0x0000 per Digi's
  "unknown 64-bit, route by 16-bit" convention) — flagged for
  real-hardware validation like the rest of this PR's protocol-level
  assumptions.
- packet.h/.cpp: fixed-width, big-endian encode/decode for UplinkPacket
  (button mask, calibrated trigger/stick, battery %, charge state — 7
  bytes) and DownlinkPacket (handedness + Left/Right slot label+value —
  33 bytes). Pure, round-trip tested including truncation/padding edge
  cases on the string fields.
- xbee_spi.cpp gains sendPacket()/pollForPacket(), thin wrappers using
  the new frame types; XbeeControl exposes both as passthroughs so it
  stays the single facade over the one physical XBee connection.
- SnipsController.ino sends the uplink every 50ms (faster than the 1s
  human-readable Serial telemetry, which stays as a bring-up aid) and
  polls for downlink packets every tick. Nothing consumes handedness or
  the Left/Right label+value yet — that's the complications system,
  PR 10 — so for now a decoded downlink just gets logged to prove the
  round trip works.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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