Skip to content

Feat/two way radio communication - #34

Merged
jasonkwok475 merged 4 commits into
mainfrom
feat/two-way-radio-communication
Jul 26, 2026
Merged

Feat/two way radio communication#34
jasonkwok475 merged 4 commits into
mainfrom
feat/two-way-radio-communication

Conversation

@ivan-lyf

@ivan-lyf ivan-lyf commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Implements the rocket side of two-way radio communication: FALCON now receives GroundCommand protobufs uplinked through the RFD900x/GNSS board, acts on camera-control commands (VTX/RunCam power), reports camera status in telemetry, shuts the cameras down on landing by cutting the camera power rail, and reconfigures the rocket-side RFD900x modem from an uplinked RfdConfig through a fail-safe AT command session.

Downlink

  • Added GroundCommand.proto to the nanopb generation list and removed the deleted HelloWorldPacket (its removal upstream had broken all builds).
  • New shared struct camera_data in data.c/h (same mutex pattern as the other snapshots); every telemetry packet now populates runcam_power (field 21) and runcam_recording (field 34).

Uplink receive path

  • radio/gnss_spi.c — single mutex-guarded access point for the GNSS/radio board on SPI1, which now has three consumers: telemetry TX, GPS RX, and command RX.
  • radio/command_thread.c — polls for uplinked frames every 200 ms, decodes COBS → CRC16 → protobuf, dedups on command_id, and dispatches through a k_msgq to an executor thread so slow command handling never stalls RX polling. Command effects are acked implicitly via the telemetry status fields.

Camera control

  • camera/vtx_power.c — drives the VTX/RunCam power switch GPIO (PD6, confirmed by hardware). Defaults on at boot so a mid-flight reboot can't leave the cameras dark; powering off also clears the recording flag.
  • camera/runcam.c — RunCam Device Protocol frames (0xCC header, CRC8-0xD5) over UART4. Dormant for now: the RunCam has auto-recording enabled, so recording follows the power rail and nothing sends these frames. The executor acts only on vtx_runcam_power; camera_recording commands are logged and ignored until the protobufs are reworked to carry RunCam commands. runcam_init() still runs at boot to validate the UART wiring.
  • Both fall back to simulated implementations when the devicetree alias is absent (native_sim).

Landed shutdown

  • state_action_landed() (previously a TODO stub) cuts VTX/RunCam power; since the camera auto-records while powered, that also ends the recording. No UART stop frame is sent. Final telemetry packets report both camera fields false. Covered by a state-machine test (no RunCam command sent, power cut exactly once).

RFD900x remote reconfiguration

Resolved: FALCON applies rfd_config to the rocket-side modem (the "ground-local" comment in GroundCommand.proto is stale). A half-applied config would kill the link permanently, so the session is built so it can never commit a partial config:

  • radio/rfd900x_at.c — transport-agnostic AT session engine: guard-time silence → +++OK, then per populated field ATSn=v (S2 air speed, S3 net id, S4 power, S8/S9 min/max freq, S10 channels), each requiring OK and a matching ATSn? readback, then AT&WOK, then ATZ.
  • The config is validated before the modem is touched (per-register ranges plus min_freq ≤ max_freq); an empty or invalid RfdConfig sends zero bytes. AT&W is only sent after every write is verified; ATZ only after AT&W is acknowledged. Any earlier failure (timeout, ERROR, readback mismatch) aborts with ATO, so EEPROM keeps the config the link was established with.
  • radio/rfd900x.c — UART transport behind the new rfd-uart devicetree alias; suspends telemetry TX for the session (radio_tx_suspend()) so the +++ guard time sees a silent serial line. Simulated when the alias is absent (native_sim).
  • Response parsing tolerates command echo, blank lines, and RSSI noise; only a bare OK / all-digit value line counts.
  • New test suite firmware/tests/rfd900x runs the engine against a scripted mock modem — 12/12 passing on native_sim: happy path (all six registers), partial config, zero-value field (NETID 0), echo/noise tolerance, empty config, min>max, out-of-range values, no OK after +++, ERROR reply, mid-sequence silence, readback mismatch, and unacknowledged AT&W (asserts ATZ is never sent).

Assumed — still needs an answer

Everything unconfirmed is centralized in firmware/src/uplink_config.h (constants) and boards/ubcrocket/polarity/ubcrocket_polarity.dts (pins), so each answer is a one-line change.

# Assumption
1 UART4 pin pair: PA0/PA1 assumed. Package also offers PA12/PA11, PB9/PB8, PD1/PD0, PD12/PD11 (PC10/PC11 are taken by SDMMC). UART4 as the RunCam port and USART2-disabled are confirmed.
2 PD6 active level: active-high assumed for the VTX/RunCam power switch.
3 Radio-RX SPI opcode 0x06 and its framing (assumed to mirror the GPS read 0x05: [CMD:1][DUMMY:4][PAYLOAD:256], COBS frame zero-filled when nothing is pending).
4 RunCam UART command set (public RunCam Device Protocol assumed: 0xCC header, CRC8 poly 0xD5, 115200 baud). Dormant until the protobuf rework, but needed before reactivation.
5 Modem serial path: a direct FALCON → modem line on USART2 PA2/PA3 @ 57600 is assumed (rfd-uart alias). If the real path is AT-passthrough via the GNSS board, only the transport in rfd900x.c changes — the AT session engine is transport-agnostic.
6 RfdConfig validation bounds in uplink_config.h (902–928 MHz, NETID ≤ 499, power ≤ 30 dBm, air speed 4–750, 1–50 channels) follow the RFD900x/SiK S-register docs — confirm against the actual modem firmware limits and the finalized command list.

Known limitations

  • Telemetry runcam_recording currently always reads false — recording is inferred from the power rail and nothing sets the flag. Revisit with the protobuf rework (or set it optimistically on power-on if the ground station needs it sooner).
  • The native_sim radio emulator (sim_radio) accepts telemetry TX and returns all-zero reads, so uplink commands cannot be injected in sim yet — the decode path (COBS/CRC/protobuf/dedup) runs only on real hardware. An injection hook is future work.
  • The AT session engine is unit-tested against a mocked modem only; the full flow still needs bench-testing with two real RFD900x modems, and the reconfiguration sequencing with the ground station (rocket-side modem first, ground follows; recovery window if the link doesn't come back) is a ground-side procedure that still needs to be defined.

ivan-lyf added 3 commits July 23, 2026 09:55
The test suites did not build in the west-workspace layout:
- BOARD_ROOT in both test CMakeLists pointed outside the manifest repo,
  so the ubcrocket_polarity board was never found.
- The integration testcase.yaml referenced the old board name
  ubcrocket_fc_2526_r1.
- The state_machine suite compiled state_machine_common.c without any
  implementation of pyro_fire_drogue/pyro_fire_main; add test stubs.
- transition_to_main_descent never waited for the drogue-fire delay, so
  the main-deploy check counter was reset every time (the drogue-before-
  main condition from #33); step past DROGUE_DEPLOY_DELAY_MS first.

State machine suite now passes 9/9 (run on qemu_cortex_m3).
Downlink additions (Phase 0):
- Generate GroundCommand.pb from falcon-protos (and drop the deleted
  HelloWorldPacket from the nanopb list, which broke the build).
- Add shared camera status (struct camera_data) alongside the other
  cross-thread snapshots and report runcam_power / runcam_recording in
  every telemetry packet.

Uplink receive path (Phase 1):
- gnss_spi: single mutex-guarded access point for the three consumers of
  the GNSS/radio board on SPI1 (telemetry TX, GPS RX, command RX).
- command_thread: RX poller (COBS decode -> CRC16 -> pb_decode) feeding a
  k_msgq consumed by an executor thread; replayed command_ids are ignored
  and command effects are acked implicitly via the telemetry fields.
- The radio-RX SPI opcode (0x06) is a PLACEHOLDER pending the
  bidirectional GNSS firmware spec; see firmware/src/uplink_config.h.

VTX/RunCam control (Phases 2-3):
- vtx_power: GPIO switch driver, powered on by default at boot.
  PLACEHOLDER pin PB0 in the devicetree until hardware confirms.
- runcam: RunCam Device Protocol frames over UART (PLACEHOLDER: USART2 on
  PA2/PA3 @115200 until hardware/David confirm; recording state is
  optimistic since CAMERA_CONTROL defines no response).
- Both fall back to simulated implementations when the devicetree alias
  is absent (native_sim).

RFD900x remote reconfiguration (Phase 4) is intentionally NOT implemented:
rfd_config commands are logged and ignored, per the proto comment marking
them ground-local. To be revisited once the team resolves the discrepancy
with the checklist.

Landed shutdown (Phase 5):
- state_action_landed() stops the recording, then cuts VTX/RunCam power;
  covered by a new state-machine test and a landed-phase assertion in the
  integration test.

Simulation support:
- New zephyr,sim-radio SPI emulator so the full firmware (including the
  radio, GPS and command threads) builds and runs on native_sim.

Verified: ubcrocket_polarity builds (firmware + both test suites),
state_machine suite 10/10 on qemu_cortex_m3, native_sim build + smoke run
in the zephyr-build container.
Hardware confirmations from the team lead:
- VTX/RunCam power switch is PD6 (was placeholder PB0). Active level
  still assumed high.
- RunCam UART is UART4 (was placeholder USART2); USART2 is disabled
  again. The UART4 pin pair is still unconfirmed -- PA0/PA1 assumed,
  see the PLACEHOLDER comment in the DTS for the alternatives.

RunCam recording decision: the camera has auto-recording enabled, so
recording follows the power rail and no start command is needed. The
runcam module is kept but dormant until the protobufs are reworked to
carry RunCam commands:
- The command executor acts only on vtx_runcam_power; camera_recording
  commands are logged and ignored.
- Landed shutdown just cuts the PD6 rail (no UART stop frame).
- runcam_init() is still called at boot to validate the UART wiring.

Note: telemetry runcam_recording now always reads false -- nothing sets
it while recording is inferred from the power rail. Revisit with the
protobuf rework.

Verified: ubcrocket_polarity firmware builds; state_machine suite
10/10 on qemu_cortex_m3 (landed test updated: no RunCam command sent,
power cut exactly once).
@ivan-lyf
ivan-lyf force-pushed the feat/two-way-radio-communication branch from 2ebc14f to bad9d53 Compare July 26, 2026 18:17
FALCON now applies an uplinked RfdConfig to the rocket-side RFD900x
through an AT command session (team decision: supersedes the stale
ground-local comment in GroundCommand.proto).

Safety design -- a half-applied config must never brick comms:
- config validated (ranges, min<=max) before the modem is touched
- every AT command must be acknowledged with OK before the next one
- every S-register is read back (ATSn?) and compared after writing
- AT&W (EEPROM write) is only sent after all writes verified; ATZ only
  after AT&W is acknowledged; any earlier failure aborts with ATO so
  the modem keeps the config the link was established with
- telemetry TX is suspended during the session so the +++ guard time
  sees a silent serial line

The AT session engine (rfd900x_at.c) is transport-agnostic; the UART
transport (rfd900x.c, rfd-uart alias, USART2 PA2/PA3 @57600 placeholder)
can be swapped for GNSS-board AT-passthrough once the physical path is
confirmed.

Tested with a scripted mock modem (firmware/tests/rfd900x): happy path,
partial/zero-value configs, echo+noise tolerance, validation rejects,
entry timeout, ERROR reply, mid-sequence silence, readback mismatch,
and unacknowledged AT&W -- 12/12 passing on native_sim.
@ivan-lyf
ivan-lyf marked this pull request as ready for review July 26, 2026 18:24

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Untested, do tests on native sim with this in the future if needed

@jasonkwok475
jasonkwok475 merged commit 9621b3b into main Jul 26, 2026
0 of 2 checks passed
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.

2 participants