diff --git a/.gitignore b/.gitignore index dba5eec9..29643194 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,9 @@ yamcs/yamcs-runtime/ /circuit-python-passthrough/firmware.uf2 /circuit-python-passthrough/lib/ /circuit-python-passthrough/tools/ + +# Radio config symlinks (generated by CMake at configure time) +PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances.fppi +PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology.fppi +PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived.fppi +PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio.fppi diff --git a/.gitmodules b/.gitmodules index 5ea7e8a5..f3ae0fef 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ [submodule "lib/fprime"] path = lib/fprime - url = https://github.com/nasa/fprime.git + url = https://github.com/Open-Source-Space-Foundation/fprime.git [submodule "lib/zephyr-workspace/zephyr"] path = lib/zephyr-workspace/zephyr - url = https://github.com/zephyrproject-rtos/zephyr.git + url = https://github.com/Open-Source-Space-Foundation/zephyr.git [submodule "lib/fprime-zephyr"] path = lib/fprime-zephyr url = https://github.com/Open-Source-Space-Foundation/fprime-zephyr.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 988edf77..51e5dae8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,11 @@ repos: rev: v5.0.0 hooks: - id: trailing-whitespace + # patches/*.patch must byte-match upstream, including trailing + # whitespace, so whitespace hooks must not touch them. + exclude: ^patches/ - id: end-of-file-fixer + exclude: ^patches/ - id: check-yaml exclude: ^mkdocs\.yml$ - id: check-json diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 00000000..18bf5c83 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,31 @@ +# CONTEXT: PROVES Flight Radio + +Glossary for the flight-software radio domain. Terms here are canonical; use them in code, docs, commands, and telemetry names. + +## Radio paths + +- **USP Radio Path**: the radio stack built on Semtech's Unified Software Platform (USP). Applies to SX126x-class boards (FCB v5e onward). Multi-modulation capable. +- **Legacy Radio Path**: the existing loramac-node-backed Zephyr `drivers/lora` stack wrapped by the `Zephyr::LoRa` component. Applies to SX127x-class boards (FCB v5/v5c/v5d). LoRa modulation only. +- **USP (Unified Software Platform)**: Semtech's radio software platform (radio drivers + RAL + radio access arbitration + LoRa Basics Modem). `usp_zephyr` is its Zephyr integration module. +- **LBM (LoRa Basics Modem)**: Semtech's modem library bundled inside USP. Its LoRaWAN stack is unused; PROVES flies raw CCSDS point-to-point. +- **RAL (Radio Abstraction Layer)**: USP's chip-agnostic radio API. The seam the flight component talks to, and the seam mocked in unit tests. + +## Link configuration + +- **Link Profile**: a complete, named radio configuration: modulation plus every parameter needed for two radios to interoperate (e.g. for GFSK: bitrate, deviation, BT, sync word, CRC, preamble). Identified by index into the Profile Table. Profiles are switched atomically; individual RF parameters are never commanded piecemeal in operations. +- **Profile Table**: the versioned, checked-in list of Link Profiles shared verbatim by flight and ground builds. Both ends must be built from the same table version for a profile index to mean the same thing. +- **TX Profile / RX Profile**: the Link Profile currently applied to the transmit and receive directions independently. The link is asymmetric by design (e.g. robust LoRa uplink, high-rate GFSK downlink). +- **Boot-Default Profile**: the profile each direction starts in at boot, and the profile RX Auto-Revert falls back to. Chosen for maximum link robustness, not throughput. +- **RX Auto-Revert**: safety mechanism: after an RX Profile change, if no valid frame is received within the commanded revert window, the RX Profile reverts to the Boot-Default Profile. Receiving a valid frame on the new profile confirms it. + +## Modulations + +- **CW (Continuous Wave)**: unmodulated carrier transmission for beacons, range testing, and RF debug. A test mode, not a Link Profile. +- **GFSK**: Gaussian FSK packet modulation; the high-throughput downlink option on the USP Radio Path. Capped at 75 kbps, with fdev of 25 kHz, by the Band Constraint. +- **Band Constraint**: IARU coordination limits PROVES UHF emissions to 125 kHz occupied bandwidth or less. Every Link Profile must satisfy it. +- **LR-FHSS**: long-range frequency-hopping modulation. SX126x can transmit but never receive it, so it is out of scope until gateway-grade or LR20xx receive hardware exists in the ground segment. + +## Ground segment + +- **GRC (Ground Radio Controller)**: the station-local Zephyr radio box (SX1262) that terminates the RF link. Must consume the same Profile Table as flight. +- **RadioHead Header**: the 4-byte `[destination, source, identifier, flags]` prefix the RadioHead LoRa ecosystem (adafruit_rfm9x, legacy `Zephyr::LoRa`) puts on every LoRa packet. The USP Radio Path radiates raw F´ frames; the `RADIOHEAD_COMPAT` parameter on `UspRadio` (default: enabled) prepends/strips this header so USP boards interoperate with RadioHead peers such as the CI CircuitPython passthrough board. Both ends of a link must agree; GFSK profiles are always raw. diff --git a/Makefile b/Makefile index bb3e819f..8c398e48 100644 --- a/Makefile +++ b/Makefile @@ -176,6 +176,8 @@ SYSBUILD_PATH ?= $(shell pwd)/lib/zephyr-workspace/zephyr/samples/sysbuild/with_ .PHONY: build-mcuboot build-mcuboot: submodules zephyr fprime-venv @cp $(shell pwd)/bootloader/sysbuild.conf $(SYSBUILD_PATH)/sysbuild.conf + @mkdir -p $(SYSBUILD_PATH)/sysbuild + @cp $(shell pwd)/bootloader/sysbuild/mcuboot.conf $(SYSBUILD_PATH)/sysbuild/mcuboot.conf $(UV_RUN) $(shell pwd)/tools/bin/build-with-proves $(SYSBUILD_PATH) --sysbuild mv $(shell pwd)/build/with_mcuboot/zephyr/zephyr.uf2 $(shell pwd)/mcuboot.uf2 diff --git a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp index 8a46afb1..a5d62351 100644 --- a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp +++ b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp @@ -12,8 +12,11 @@ #include #include +#include #include +#include + namespace Components { // ---------------------------------------------------------------------- @@ -24,9 +27,21 @@ FatalHandler ::FatalHandler(const char* const compName) : FatalHandlerComponentB FatalHandler ::~FatalHandler() {} +void FatalHandler::reboot() { + sys_reboot(SYS_REBOOT_WARM); + + // Only reached if the warm reboot returns. + sys_reboot(SYS_REBOOT_COLD); +} + void FatalHandler::FatalReceive_handler(const FwIndexType portNum, FwEventIdType Id) { Fw::Logger::log("FATAL %" PRI_FwEventIdType " handled.\n", Id); + // Stop petting the external watchdog. this->stopWatchdog_out(0); + // Short delay so the FATAL log can drain, then force a reboot so the reset + // does not depend on the external watchdog. + Os::Task::delay(Fw::TimeInterval(0, 1000)); // 1 ms + this->reboot(); } } // namespace Components diff --git a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp index 98ddac2e..4d69b58f 100644 --- a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp +++ b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp @@ -42,6 +42,9 @@ class FatalHandler final : public FatalHandlerComponentBase { void FatalReceive_handler(const FwIndexType portNum, /*!< The port number*/ FwEventIdType Id /*!< The ID of the FATAL event*/ ); + + //! Reboot the device via Zephyr sys_reboot() + void reboot(); }; } // namespace Components diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp index 478a1fc3..18357fb9 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp @@ -21,7 +21,10 @@ const struct device* ina219Sys = DEVICE_DT_GET(DT_NODELABEL(ina219_0)); const struct device* ina219Sol = DEVICE_DT_GET(DT_NODELABEL(ina219_1)); const struct device* serial = DEVICE_DT_GET(DT_NODELABEL(cdc_acm_uart0)); +// The USP build (v5e) has no Zephyr lora device node; the radio is opened by RalSessionImpl. +#ifndef CONFIG_LORA_BASICS_MODEM_DRIVERS const struct device* lora = DEVICE_DT_GET(DT_NODELABEL(lora0)); +#endif // const struct device* spi0 = DEVICE_DT_GET(DT_NODELABEL(spi0)); const struct device* peripheral_uart = DEVICE_DT_GET(DT_NODELABEL(uart0)); const struct device* peripheral_uart1 = DEVICE_DT_GET(DT_NODELABEL(uart1)); @@ -76,7 +79,9 @@ int main(int argc, char* argv[]) { // Flight Control Board device bindings inputs.ina219SysDevice = ina219Sys; inputs.ina219SolDevice = ina219Sol; +#ifndef CONFIG_LORA_BASICS_MODEM_DRIVERS inputs.loraDevice = lora; +#endif inputs.uartDevice = serial; inputs.lsm6dsoDevice = lsm6dso; inputs.lis2mdlDevice = lis2mdl; diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt b/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt index e0af332f..3aaf5b58 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt @@ -5,11 +5,33 @@ # AUTOCODER_INPUTS: list of files to be passed to the autocoders # DEPENDS: list of libraries that this module depends on # -# More information in the F´ CMake API documentation: -# https://fprime.jpl.nasa.gov/latest/docs/reference/api/cmake/API/ -# +# Per-board radio selection: +# CONFIG_LORA_BASICS_MODEM_DRIVERS set (v5e): Radio*.fppi -> Radio*_Usp.fppi (Zephyr::UspRadio) +# CONFIG_LORA_BASICS_MODEM_DRIVERS unset (v5c/v5d): Radio*.fppi -> Radio*_Lora.fppi (Zephyr::LoRa) +# FPP `include` resolves relative to the including file, so the links are +# created in the source tree at configure time. COPY_ON_ERROR falls back to a +# file copy where symlinks are not supported. The links are gitignored. #### +if(DEFINED CONFIG_LORA_BASICS_MODEM_DRIVERS) + set(RADIO_SUFFIX "Usp") +else() + set(RADIO_SUFFIX "Lora") +endif() + +# Instance and topology snippets. +foreach(_kind "Instances" "Topology") + set(_src "${CMAKE_CURRENT_LIST_DIR}/Radio${_kind}_${RADIO_SUFFIX}.fppi") + set(_dst "${CMAKE_CURRENT_LIST_DIR}/Radio${_kind}.fppi") + file(CREATE_LINK "${_src}" "${_dst}" SYMBOLIC COPY_ON_ERROR) +endforeach() +# Packet snippets (they reference the per-board instance names). +foreach(_kind "BytesReceived" "Radio") + set(_src "${CMAKE_CURRENT_LIST_DIR}/RadioPackets${_kind}_${RADIO_SUFFIX}.fppi") + set(_dst "${CMAKE_CURRENT_LIST_DIR}/RadioPackets${_kind}.fppi") + file(CREATE_LINK "${_src}" "${_dst}" SYMBOLIC COPY_ON_ERROR) +endforeach() + register_fprime_module( AUTOCODER_INPUTS "${CMAKE_CURRENT_LIST_DIR}/instances.fpp" diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fppi new file mode 100644 index 00000000..8a13b0f4 --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fppi @@ -0,0 +1,7 @@ +# Radio instances for v5c / v5d (Zephyr LoRa driver). +# Included by instances.fpp inside module ReferenceDeployment { }. +# Selected by Top/CMakeLists.txt per board. + + instance lora: Zephyr.LoRa base id 0x1001F000 + + instance loraRetry: Svc.ComRetry base id 0x10063000 diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fppi new file mode 100644 index 00000000..dbbb151f --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fppi @@ -0,0 +1,10 @@ +# Radio instances for v5e (Semtech USP driver). +# Included by instances.fpp inside module ReferenceDeployment { }. +# Selected by Top/CMakeLists.txt per board. +# +# UspRadio is an active component. Priority 11 is above the rate groups. + + instance uspRadio: Zephyr.UspRadio base id 0x1001F000 \ + queue size Default.QUEUE_SIZE * 2 \ + stack size Default.STACK_SIZE \ + priority 11 diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Lora.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Lora.fppi new file mode 100644 index 00000000..c38ffbcf --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Lora.fppi @@ -0,0 +1 @@ + lora.BytesReceived diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Usp.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Usp.fppi new file mode 100644 index 00000000..a0ed55ee --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Usp.fppi @@ -0,0 +1 @@ + uspRadio.BytesReceived diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Lora.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Lora.fppi new file mode 100644 index 00000000..236e4f90 --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Lora.fppi @@ -0,0 +1,5 @@ + packet Radio id 8 group 2 { + lora.LastRssi + lora.LastSnr + lora.BytesSent + } diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Usp.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Usp.fppi new file mode 100644 index 00000000..cc0c99b7 --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Usp.fppi @@ -0,0 +1,9 @@ + packet Radio id 8 group 2 { + uspRadio.LastRssi + uspRadio.LastSnr + uspRadio.BytesSent + uspRadio.TxProfile + uspRadio.RxProfile + uspRadio.RxReverts + uspRadio.RxDropped + } diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fppi new file mode 100644 index 00000000..601d2807 --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fppi @@ -0,0 +1,57 @@ +# Radio connections for v5c / v5d (Zephyr LoRa driver). +# Included by topology.fpp inside topology ReferenceDeployment { }. +# Selected by Top/CMakeLists.txt per board. + + instance lora + instance loraRetry + + connections CommunicationsRadio { + lora.allocate -> ComCcsdsLora.commsBufferManager.bufferGetCallee + lora.deallocate -> ComCcsdsLora.commsBufferManager.bufferSendIn + + # ComDriver <-> FrameAccumulator (Uplink) + lora.dataOut -> ComCcsdsLora.frameAccumulator.dataIn + ComCcsdsLora.frameAccumulator.dataReturnOut -> lora.dataReturnIn + + # ComStub <-> ComDriver (Downlink) with ComRetry shim + ComCcsdsLora.framer.dataOut -> loraRetry.dataIn + loraRetry.dataOut -> lora.dataIn + + lora.dataReturnOut -> loraRetry.dataReturnIn + loraRetry.dataReturnOut -> ComCcsdsLora.framer.dataReturnIn + + lora.comStatusOut -> loraRetry.comStatusIn + loraRetry.comStatusOut -> downlinkDelay.comStatusIn + downlinkDelay.comStatusOut -> ComCcsdsLora.framer.comStatusIn + + # Startup and sequence wiring (same in RadioTopology_Usp.fppi) + startupManager.runSequence -> cmdSeq.seqRunIn + + # StartupManager receives sequence status from CmdSeq + cmdSeq.seqStartOut -> startupManager.startupsequenceStarted + cmdSeq.seqDone -> startupManager.startupCompleteSequence + + # StartupManager receives sequence status from PayloadSeq + payloadSeq.seqStartOut -> startupManager.payloadSequenceStarted + payloadSeq.seqDone -> startupManager.payloadCompleteSequence + + # StartupManager receives sequence status from SafeModeSeq + # seqDone is owned by ModeManager; completion is forwarded via sequenceDoneNotify + safeModeSeq.seqStartOut -> startupManager.safeModeSequenceStarted + + # StartupManager drives radio TX enable/disable around quiescence + startupManager.enableTransmit -> lora.enableTransmit + startupManager.disableTransmit -> lora.disableTransmit + + # --- Radio ever enabled this boot? --- + lora.loraFirstStart -> startupManager.loraFirstStart + + modeManager.runSequence -> safeModeSeq.seqRunIn + safeModeSeq.seqDone -> modeManager.completeSequence + modeManager.sequenceDoneNotify -> startupManager.safeModeCompleteSequence + + # RTC time change cancels running sequences + rtcManager.cancelSequences[0] -> cmdSeq.seqCancelIn + rtcManager.cancelSequences[1] -> payloadSeq.seqCancelIn + rtcManager.cancelSequences[2] -> safeModeSeq.seqCancelIn + } diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fppi new file mode 100644 index 00000000..8c05a5e9 --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fppi @@ -0,0 +1,59 @@ +# Radio connections for v5e (Semtech USP driver). +# Included by topology.fpp inside topology ReferenceDeployment { }. +# Selected by Top/CMakeLists.txt per board. +# +# UspRadio is active, so its queue provides back-pressure and no ComRetry shim is needed. + + instance uspRadio + + connections CommunicationsRadio { + uspRadio.allocate -> ComCcsdsLora.commsBufferManager.bufferGetCallee + uspRadio.deallocate -> ComCcsdsLora.commsBufferManager.bufferSendIn + + # UspRadio <-> FrameAccumulator (Uplink) + uspRadio.dataOut -> ComCcsdsLora.frameAccumulator.dataIn + ComCcsdsLora.frameAccumulator.dataReturnOut -> uspRadio.dataReturnIn + + # UspRadio <-> Framer (Downlink) + ComCcsdsLora.framer.dataOut -> uspRadio.dataIn + uspRadio.dataReturnOut -> ComCcsdsLora.framer.dataReturnIn + uspRadio.comStatusOut -> downlinkDelay.comStatusIn + downlinkDelay.comStatusOut -> ComCcsdsLora.framer.comStatusIn + + # Startup and sequence wiring (same in RadioTopology_Lora.fppi) + startupManager.runSequence -> cmdSeq.seqRunIn + + # StartupManager receives sequence status from CmdSeq + cmdSeq.seqStartOut -> startupManager.startupsequenceStarted + cmdSeq.seqDone -> startupManager.startupCompleteSequence + + # StartupManager receives sequence status from PayloadSeq + payloadSeq.seqStartOut -> startupManager.payloadSequenceStarted + payloadSeq.seqDone -> startupManager.payloadCompleteSequence + + # StartupManager receives sequence status from SafeModeSeq + # seqDone is owned by ModeManager; completion is forwarded via sequenceDoneNotify + safeModeSeq.seqStartOut -> startupManager.safeModeSequenceStarted + + # StartupManager drives radio TX enable/disable around quiescence + startupManager.enableTransmit -> uspRadio.enableTransmit + startupManager.disableTransmit -> uspRadio.disableTransmit + + # Both ports are Fw.Signal, so the names may differ. + uspRadio.radioFirstStart -> startupManager.loraFirstStart + + modeManager.runSequence -> safeModeSeq.seqRunIn + safeModeSeq.seqDone -> modeManager.completeSequence + modeManager.sequenceDoneNotify -> startupManager.safeModeCompleteSequence + + # RTC time change cancels running sequences + rtcManager.cancelSequences[0] -> cmdSeq.seqCancelIn + rtcManager.cancelSequences[1] -> payloadSeq.seqCancelIn + rtcManager.cancelSequences[2] -> safeModeSeq.seqCancelIn + } + + + connections RadioRateGroup { + # 1 Hz tick for the revert deadline and telemetry flush. Slot 20 is the first unused rate-group slot. + rateGroup1Hz.RateGroupMemberOut[20] -> uspRadio.run + } diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi index 5ca221ec..44733d25 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi @@ -29,7 +29,9 @@ telemetry packets ReferenceDeploymentPackets { ComCcsdsLora.tcSecurityDeframer.CurrentSequenceNumber ComCcsdsUart.tcSecurityDeframer.CurrentSequenceNumber - lora.BytesReceived + # Radio bytes received: per-board instance name. + # Radio BytesReceived channel. Selected by Top/CMakeLists.txt per board. + include "RadioPacketsBytesReceived.fppi" } @@ -45,13 +47,8 @@ telemetry packets ReferenceDeploymentPackets { imuManager.MagnetometerSamplingFrequency } - packet Radio id 8 group 2 { - lora.LastRssi - lora.LastSnr - lora.BytesSent -# sband.LastRssi -# sband.LastSnr - } + # Radio packet. Selected by Top/CMakeLists.txt per board. + include "RadioPacketsRadio.fppi" packet PowerMonitor id 11 group 2 { ina219SysManager.Current diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp index e54eedff..1b8670d1 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp @@ -9,11 +9,20 @@ // #include // Necessary project-specified types +#include #include #include #include +#ifdef CONFIG_LORA_BASICS_MODEM_DRIVERS +#include "fprime-zephyr/Drv/UspRadio/RalSessionImpl.hpp" +#include "fprime-zephyr/Drv/UspRadio/UspRadio.hpp" + +// USP radio session: carrier frequency in Hz, TX power in dBm. +static Zephyr::RalSessionImpl s_ralSession(437400000U, 10); +#endif // CONFIG_LORA_BASICS_MODEM_DRIVERS + static const struct gpio_dt_spec ledGpio = GPIO_DT_SPEC_GET(DT_NODELABEL(led0), gpios); static const struct gpio_dt_spec burnwire0Gpio = GPIO_DT_SPEC_GET(DT_NODELABEL(burnwire0), gpios); static const struct gpio_dt_spec burnwire1Gpio = GPIO_DT_SPEC_GET(DT_NODELABEL(burnwire1), gpios); @@ -121,9 +130,17 @@ void setupTopology(const TopologyState& state) { // Autocoded task kick-off (active components). Function provided by autocoder. startTasks(state); - // We have a pipeline for both the LoRa and UART drive to allow for ground harness debugging an - // for over-the-air communications. + // The radio instance (uspRadio or lora) is selected by Top/CMakeLists.txt per board. + // Both radios boot with TX disabled; the startup sequence enables TX later. +#ifdef CONFIG_LORA_BASICS_MODEM_DRIVERS + // startRadio() initialises the injected session, applies the boot profile and starts RX. + uspRadio.configure(s_ralSession); + if (!uspRadio.startRadio(Zephyr::UspTransmitState::DISABLED)) { + Fw::Logger::log("[Topology] UspRadio startRadio() failed -- radio inactive\n"); + } +#else lora.start(state.loraDevice, Zephyr::TransmitState::DISABLED); +#endif comDriver.configure(state.uartDevice, state.baudRate); // static struct spi_cs_control cs_ctrl = { diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp index 8d5be24a..7ef06580 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp @@ -111,9 +111,11 @@ namespace ReferenceDeployment { * autocoder. The contents are entirely up to the definition of the project. This deployment uses subtopologies. */ struct TopologyState { - const device* uartDevice; //!< UART device path for communication - const device* spi0Device; //!< Spi device path for s-band LoRa module - const device* loraDevice; //!< LoRa device path for communication + const device* uartDevice; //!< UART device path for communication + const device* spi0Device; //!< Spi device path for s-band LoRa module +#ifndef CONFIG_LORA_BASICS_MODEM_DRIVERS + const device* loraDevice; //!< LoRa device path for communication (not present on the USP build) +#endif ComCcsdsLora::SubtopologyState comCcsdsLora; //!< Subtopology state for ComCcsdsLora // ComCcsdsSband::SubtopologyState comCcsdsSband; //!< Subtopology state for ComCcsdsSband U32 baudRate; //!< Baud rate for UART communication diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp index c1a541eb..0f46fec3 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp @@ -103,7 +103,8 @@ module ReferenceDeployment { instance downlinkDelay: Components.ComDelay base id 0x1001E000 - instance lora: Zephyr.LoRa base id 0x1001F000 + # Radio instances (lora + loraRetry, or uspRadio). Selected by Top/CMakeLists.txt per board. + include "RadioInstances.fppi" instance comSplitterEvents: Svc.ComSplitter base id 0x10020000 @@ -215,7 +216,7 @@ module ReferenceDeployment { instance fileUplinkCollector: Utilities.BufferCollector base id 0x10060000 instance telemetryDelay: Utilities.RateDelay base id 0x10061000 - instance loraRetry: Svc.ComRetry base id 0x10063000 + # loraRetry is declared in RadioInstances.fppi (LoRa path only). instance downlinkRepeater: Utilities.BufferRepeater base id 0x10064000 diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp index 9063988a..60aaf0b7 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp @@ -30,8 +30,7 @@ module ReferenceDeployment { instance rateGroup1Hz instance rateGroupDriver instance timer - instance lora - instance loraRetry + # Radio instances are listed in RadioTopology.fppi. instance gpioWatchdog instance gpioBurnwire0 instance gpioBurnwire1 @@ -191,57 +190,9 @@ module ReferenceDeployment { # comDelaySband.comStatusOut -> ComCcsdsSband.framer.comStatusIn #} - connections CommunicationsRadio { - lora.allocate -> ComCcsdsLora.commsBufferManager.bufferGetCallee - lora.deallocate -> ComCcsdsLora.commsBufferManager.bufferSendIn - - # ComDriver <-> FrameAccumulator (Uplink) - lora.dataOut -> ComCcsdsLora.frameAccumulator.dataIn - ComCcsdsLora.frameAccumulator.dataReturnOut -> lora.dataReturnIn - - # ComStub <-> ComDriver (Downlink) - ComCcsdsLora.framer.dataOut -> loraRetry.dataIn - loraRetry.dataOut -> lora.dataIn - - lora.dataReturnOut -> loraRetry.dataReturnIn - loraRetry.dataReturnOut -> ComCcsdsLora.framer.dataReturnIn - - lora.comStatusOut -> loraRetry.comStatusIn - loraRetry.comStatusOut -> downlinkDelay.comStatusIn - downlinkDelay.comStatusOut ->ComCcsdsLora.framer.comStatusIn - - startupManager.runSequence -> cmdSeq.seqRunIn - - # StartupManager receives sequence status from CmdSeq - cmdSeq.seqStartOut -> startupManager.startupsequenceStarted - cmdSeq.seqDone -> startupManager.startupCompleteSequence - - # StartupManager receives sequence status from PayloadSeq - payloadSeq.seqStartOut -> startupManager.payloadSequenceStarted - payloadSeq.seqDone -> startupManager.payloadCompleteSequence - - # StartupManager receives sequence status from SafeModeSeq - # seqDone is owned by ModeManager; completion is forwarded via sequenceDoneNotify - safeModeSeq.seqStartOut -> startupManager.safeModeSequenceStarted - - # StartupManager drives LoRa TX enable/disable around quiescence - startupManager.enableTransmit -> lora.enableTransmit - startupManager.disableTransmit -> lora.disableTransmit - - # --- Radio ever enabled this boot? --- - lora.loraFirstStart -> startupManager.loraFirstStart - - modeManager.runSequence -> safeModeSeq.seqRunIn - safeModeSeq.seqDone -> modeManager.completeSequence - modeManager.sequenceDoneNotify -> startupManager.safeModeCompleteSequence - - # RTC time change cancels running sequences - rtcManager.cancelSequences[0] -> cmdSeq.seqCancelIn - rtcManager.cancelSequences[1] -> payloadSeq.seqCancelIn - rtcManager.cancelSequences[2] -> safeModeSeq.seqCancelIn - - - } + # Radio instances, CommunicationsRadio connections, and the startup-sequence + # and RTC cancel-sequence wiring. Selected by Top/CMakeLists.txt per board. + include "RadioTopology.fppi" connections CommunicationsUart { # ComDriver buffer allocations diff --git a/PROVESFlightControllerReference/test/int/conftest.py b/PROVESFlightControllerReference/test/int/conftest.py index 933ebcee..6fc2c547 100644 --- a/PROVESFlightControllerReference/test/int/conftest.py +++ b/PROVESFlightControllerReference/test/int/conftest.py @@ -156,7 +156,7 @@ def _enable_radio(fprime_test_api: IntegrationTestAPI) -> None: args=[20], ) fprime_test_api.send_command( - command="ReferenceDeployment.lora.TRANSMIT", args=["ENABLED"] + command="ReferenceDeployment.uspRadio.TRANSMIT", args=["ENABLED"] ) @@ -220,7 +220,7 @@ def stop_radio( return fprime_test_api_session.send_command( - command="ReferenceDeployment.lora.TRANSMIT", args=["DISABLED"] + command="ReferenceDeployment.uspRadio.TRANSMIT", args=["DISABLED"] ) diff --git a/PROVESFlightControllerReference/test/int/radio_test.py b/PROVESFlightControllerReference/test/int/radio_test.py index e7701ed9..fa2c36af 100644 --- a/PROVESFlightControllerReference/test/int/radio_test.py +++ b/PROVESFlightControllerReference/test/int/radio_test.py @@ -15,9 +15,9 @@ pytestmark = [pytest.mark.uart_only] downlinkDelay = "ReferenceDeployment.downlinkDelay" -lora = "ReferenceDeployment.lora" +radio = "ReferenceDeployment.uspRadio" -LORA_ERROR_EVENTS = ("SendFailed", "ConfigurationFailed", "AllocationFailed") +RADIO_ERROR_EVENTS = ("SendFailed", "ConfigurationFailed", "AllocationFailed") # Continuous-wave burst duration (seconds) for the CW regression test. Kept short so # the command stays within the GDS command-completion timeout while still exercising @@ -36,28 +36,28 @@ def setup_test(fprime_test_api: IntegrationTestAPI, start_gds): yield proves_send_and_assert_command( fprime_test_api, - f"{lora}.TRANSMIT", + f"{radio}.TRANSMIT", ["DISABLED"], ) def test_01_transmit_enabled(fprime_test_api: IntegrationTestAPI, start_gds): - """Enabling transmit must not produce any LoRa error/warning events.""" + """Enabling transmit must not produce any radio error/warning events.""" start: TimeType = TimeType().set_datetime( datetime.now(), time_base=TimeType.TimeBase("TB_DONT_CARE") ) proves_send_and_assert_command( fprime_test_api, - f"{lora}.TRANSMIT", + f"{radio}.TRANSMIT", ["ENABLED"], ) time.sleep(10) - for evt in LORA_ERROR_EVENTS: - result = fprime_test_api.await_event(f"{lora}.{evt}", start=start, timeout=0) - assert result is None, f"Unexpected {lora}.{evt}: {result}" + for evt in RADIO_ERROR_EVENTS: + result = fprime_test_api.await_event(f"{radio}.{evt}", start=start, timeout=0) + assert result is None, f"Unexpected {radio}.{evt}: {result}" def test_02_continuous_wave_repeated(fprime_test_api: IntegrationTestAPI, start_gds): @@ -75,7 +75,7 @@ def test_02_continuous_wave_repeated(fprime_test_api: IntegrationTestAPI, start_ # returned EXECUTION_ERROR, so send_and_assert_command would already fail here. proves_send_and_assert_command( fprime_test_api, - f"{lora}.CONTINUOUS_WAVE", + f"{radio}.CONTINUOUS_WAVE", [CW_SECONDS], ) @@ -89,30 +89,30 @@ def test_02_continuous_wave_repeated(fprime_test_api: IntegrationTestAPI, start_ ) proves_send_and_assert_command( fprime_test_api, - f"{lora}.CONTINUOUS_WAVE", + f"{radio}.CONTINUOUS_WAVE", [CW_SECONDS], ) time.sleep(CW_SECONDS + 2) # The repeated CW must not have logged a modem configuration failure. result = fprime_test_api.await_event( - f"{lora}.ConfigurationFailed", start=start, timeout=0 + f"{radio}.ConfigurationFailed", start=start, timeout=0 ) assert result is None, ( - f"Unexpected {lora}.ConfigurationFailed after repeated CW: {result}" + f"Unexpected {radio}.ConfigurationFailed after repeated CW: {result}" ) # The radio must still be usable for normal transmission after the CW bursts; - # a wedged modem would surface as LoRa error events here. + # a wedged modem would surface as radio error events here. start = TimeType().set_datetime( datetime.now(), time_base=TimeType.TimeBase("TB_DONT_CARE") ) proves_send_and_assert_command( fprime_test_api, - f"{lora}.TRANSMIT", + f"{radio}.TRANSMIT", ["ENABLED"], ) time.sleep(10) - for evt in LORA_ERROR_EVENTS: - result = fprime_test_api.await_event(f"{lora}.{evt}", start=start, timeout=0) - assert result is None, f"Unexpected {lora}.{evt} after repeated CW: {result}" + for evt in RADIO_ERROR_EVENTS: + result = fprime_test_api.await_event(f"{radio}.{evt}", start=start, timeout=0) + assert result is None, f"Unexpected {radio}.{evt} after repeated CW: {result}" diff --git a/PROVESFlightControllerReference/test/int/rf_profile_matrix_test.py b/PROVESFlightControllerReference/test/int/rf_profile_matrix_test.py new file mode 100644 index 00000000..3d7a8461 --- /dev/null +++ b/PROVESFlightControllerReference/test/int/rf_profile_matrix_test.py @@ -0,0 +1,395 @@ +""" +rf_profile_matrix_test.py: + +Integration tests for the USP radio link-profile commands: SET_TX_PROFILE, +SET_RX_PROFILE, CONTINUOUS_WAVE, and the first TX after a profile switch. + +Test groups: + - TX and RX profile sweeps through every LinkProfileId and back to P0. + - CONTINUOUS_WAVE with restore to RX. + - Profile switch as the first command after an idle window. + - Profile switch after a long idle window. + - GFSK/GMSK first TX after a profile switch. + - Two-board profile pairing against a ground radio (marked two_board_rf; + skipped unless the USP_GROUND_* env vars below are set). + +Env vars: + RF_PROFILE_HAMMER_CYCLES post-wake switch cycles (default 5) + RF_WEDGE_KILL_CYCLES first-TX-after-switch repetitions per profile + (default 2) + RF_WEDGE_IDLE_S idle seconds before the first-TX-after-switch + profile switch (default 1) + RF_PROFILE_WAKE_IDLE_S idle seconds before each post-wake switch (default 3) + RF_PROFILE_LONG_IDLE_S idle seconds for the post-idle test (default 90) + USP_GROUND_DATA_TTY ground radio data-CDC device; downlinked RF frames + appear here as raw bytes + USP_GROUND_CMD shell command template that sets the ground radio RX + profile; "{profile}" is replaced with the numeric + LinkProfileId + USP_GROUND_UPLINK_CMD shell command template that makes the ground radio + transmit at least one RF frame; "{profile}" is + replaced with the numeric LinkProfileId +""" + +import os +import subprocess +import time +from datetime import datetime + +import pytest +from common import cmdDispatch, proves_send_and_assert_command +from fprime_gds.common.models.serialize.time_type import TimeType +from fprime_gds.common.testing_fw.api import IntegrationTestAPI + +# A profile switch drops any in-flight RF link, so this module only runs when +# the GDS is connected over UART. +pytestmark = [pytest.mark.uart_only] + +downlinkDelay = "ReferenceDeployment.downlinkDelay" +radio = "ReferenceDeployment.uspRadio" +tlmSend = "CdhCore.tlmSend" + +# TlmPacketizer packet id (Health). SEND_PKT with this id forces a downlink +# frame, and so a radio TX while TRANSMIT is ENABLED. +HEALTH_PACKET_ID = 2 + +# Events that indicate the radio rejected or failed a reconfiguration. +PROFILE_ERROR_EVENTS = ("ConfigurationFailed", "InvalidProfile") +RADIO_ERROR_EVENTS = ("SendFailed", "ConfigurationFailed", "AllocationFailed") + +# LinkProfileId sweep order. P0 is the boot default, so the sweep ends by +# restoring it. +PROFILE_SWEEP = [ + "P1_LORA_SF10", + "P2_LORA_SF5", + "P3_GFSK_38K", + "P4_GFSK_75K", + "P5_GMSK_83K", + "P0_LORA_SF8", +] +BOOT_PROFILE = "P0_LORA_SF8" + +# Numeric LinkProfileId values for the ground-side command template. +PROFILE_IDS = { + "P0_LORA_SF8": 0, + "P1_LORA_SF10": 1, + "P2_LORA_SF5": 2, + "P3_GFSK_38K": 3, + "P4_GFSK_75K": 4, + "P5_GMSK_83K": 5, +} + +# Continuous-wave burst duration (seconds). Kept short so the command completes +# within the GDS command-completion timeout. +CW_SECONDS = 5 + +# RX auto-revert is disabled (revert_s=0) for all single-board switches. The +# tests restore P0 explicitly; an auto-revert mid-test would race the +# ProfileChanged assertions. +NO_REVERT = 0 + +HAMMER_CYCLES = int(os.environ.get("RF_PROFILE_HAMMER_CYCLES", "5")) +WEDGE_KILL_CYCLES = int(os.environ.get("RF_WEDGE_KILL_CYCLES", "2")) +WEDGE_IDLE_S = float(os.environ.get("RF_WEDGE_IDLE_S", "1")) +WAKE_IDLE_S = float(os.environ.get("RF_PROFILE_WAKE_IDLE_S", "3")) +LONG_IDLE_S = float(os.environ.get("RF_PROFILE_LONG_IDLE_S", "90")) + +GROUND_DATA_TTY = os.environ.get("USP_GROUND_DATA_TTY") +GROUND_CMD = os.environ.get("USP_GROUND_CMD") +GROUND_UPLINK_CMD = os.environ.get("USP_GROUND_UPLINK_CMD") + +GROUND_READ_WINDOW_S = 20.0 + + +def _now_start() -> TimeType: + return TimeType().set_datetime( + datetime.now(), time_base=TimeType.TimeBase("TB_DONT_CARE") + ) + + +def _assert_no_profile_errors( + fprime_test_api: IntegrationTestAPI, start: TimeType, context: str +) -> None: + for evt in PROFILE_ERROR_EVENTS: + result = fprime_test_api.await_event(f"{radio}.{evt}", start=start, timeout=0) + assert result is None, f"Unexpected {radio}.{evt} {context}: {result}" + + +def _switch_profile( + fprime_test_api: IntegrationTestAPI, direction: str, profile: str +) -> None: + """Send SET_TX_PROFILE or SET_RX_PROFILE and assert the deferred apply + completed: ProfileChanged is emitted and no profile error events follow.""" + start = _now_start() + if direction == "TX": + proves_send_and_assert_command( + fprime_test_api, f"{radio}.SET_TX_PROFILE", [profile] + ) + else: + proves_send_and_assert_command( + fprime_test_api, f"{radio}.SET_RX_PROFILE", [profile, NO_REVERT] + ) + # The command handler defers the apply to the component thread. The switch + # is complete only once ProfileChanged is emitted. + result = fprime_test_api.await_event( + f"{radio}.ProfileChanged", start=start, timeout=10 + ) + assert result is not None, ( + f"No {radio}.ProfileChanged after SET_{direction}_PROFILE({profile})" + ) + _assert_no_profile_errors( + fprime_test_api, start, f"after SET_{direction}_PROFILE({profile})" + ) + + +@pytest.fixture(autouse=True) +def setup_test(fprime_test_api: IntegrationTestAPI, start_gds): + """Set the downlink divider before each test. After each test, disable + transmit and restore both profiles to the boot default.""" + proves_send_and_assert_command( + fprime_test_api, + f"{downlinkDelay}.DIVIDER_PRM_SET", + [20], + ) + yield + proves_send_and_assert_command( + fprime_test_api, + f"{radio}.TRANSMIT", + ["DISABLED"], + ) + proves_send_and_assert_command( + fprime_test_api, f"{radio}.SET_TX_PROFILE", [BOOT_PROFILE] + ) + proves_send_and_assert_command( + fprime_test_api, f"{radio}.SET_RX_PROFILE", [BOOT_PROFILE, NO_REVERT] + ) + + +def test_01_tx_profile_sweep(fprime_test_api: IntegrationTestAPI, start_gds): + """Sweep SET_TX_PROFILE through every profile in PROFILE_SWEEP, ending at P0. + Assert each switch emits ProfileChanged with no error events and the board + stays commandable.""" + for profile in PROFILE_SWEEP: + _switch_profile(fprime_test_api, "TX", profile) + # The command path is still alive after the switch. + proves_send_and_assert_command(fprime_test_api, f"{cmdDispatch}.CMD_NO_OP") + + +def test_02_rx_profile_sweep(fprime_test_api: IntegrationTestAPI, start_gds): + """Sweep SET_RX_PROFILE through every profile in PROFILE_SWEEP, ending at P0. + Assert each switch emits ProfileChanged with no error events and the board + stays commandable.""" + for profile in PROFILE_SWEEP: + _switch_profile(fprime_test_api, "RX", profile) + proves_send_and_assert_command(fprime_test_api, f"{cmdDispatch}.CMD_NO_OP") + + +def test_03_continuous_wave_restore_to_rx( + fprime_test_api: IntegrationTestAPI, start_gds +): + """Run CONTINUOUS_WAVE, then assert the receiver can still be reconfigured + (RX profile switch away from P0 and back) and that TRANSMIT ENABLED + produces no radio error events.""" + proves_send_and_assert_command( + fprime_test_api, + f"{radio}.CONTINUOUS_WAVE", + [CW_SECONDS], + ) + # Wait out the CW duration so the asynchronous restore to RX completes. + time.sleep(CW_SECONDS + 2) + + # The receiver must be reconfigurable after CW. + _switch_profile(fprime_test_api, "RX", "P2_LORA_SF5") + _switch_profile(fprime_test_api, "RX", BOOT_PROFILE) + + # The TX path must also be intact: enabling transmit must produce no radio error events. + start = _now_start() + proves_send_and_assert_command(fprime_test_api, f"{radio}.TRANSMIT", ["ENABLED"]) + time.sleep(10) + for evt in RADIO_ERROR_EVENTS: + result = fprime_test_api.await_event(f"{radio}.{evt}", start=start, timeout=0) + assert result is None, f"Unexpected {radio}.{evt} after CW restore: {result}" + + +def test_04_post_wake_profile_switch_hammer( + fprime_test_api: IntegrationTestAPI, start_gds +): + """Idle for RF_PROFILE_WAKE_IDLE_S, then send an RX profile switch as the + first command; repeat RF_PROFILE_HAMMER_CYCLES times alternating P2 and P0. + Assert every switch completes with ProfileChanged and no error events.""" + for cycle in range(HAMMER_CYCLES): + # No commands during the idle window, so the switch below is the first SPI command sequence after the modem sleeps. + time.sleep(WAKE_IDLE_S) + profile = "P2_LORA_SF5" if cycle % 2 == 0 else BOOT_PROFILE + try: + _switch_profile(fprime_test_api, "RX", profile) + except AssertionError as exc: + raise AssertionError( + f"Post-wake profile switch failed on cycle {cycle + 1}/" + f"{HAMMER_CYCLES}: {exc}" + ) from exc + + +def test_05_post_idle_profile_switch(fprime_test_api: IntegrationTestAPI, start_gds): + """With transmit disabled, idle for RF_PROFILE_LONG_IDLE_S, then send an RX + profile switch as the first command. Assert the switch completes and the + board is commandable afterwards.""" + proves_send_and_assert_command(fprime_test_api, f"{radio}.TRANSMIT", ["DISABLED"]) + time.sleep(LONG_IDLE_S) + _switch_profile(fprime_test_api, "RX", "P1_LORA_SF10") + _switch_profile(fprime_test_api, "RX", BOOT_PROFILE) + proves_send_and_assert_command(fprime_test_api, f"{cmdDispatch}.CMD_NO_OP") + + +def _force_tx_and_await_advance( + fprime_test_api: IntegrationTestAPI, + floor: int | None, + context: str, + timeout: float = 45.0, +) -> int: + """Send SEND_PKT to force a downlink frame, wait for uspRadio.BytesSent to + pass ``floor``, and return the new value. BytesSent increments only when a + radio transmission completes, so an increase proves the TX went out.""" + proves_send_and_assert_command( + fprime_test_api, f"{tlmSend}.SEND_PKT", [HEALTH_PACKET_ID, "REALTIME"] + ) + deadline = time.monotonic() + timeout + last_seen = floor + while time.monotonic() < deadline: + result = fprime_test_api.await_telemetry(f"{radio}.BytesSent", timeout=5) + if result is not None: + val = int(result.get_val()) + last_seen = val + if floor is None or val > floor: + return val + raise AssertionError( + f"uspRadio.BytesSent did not advance past {floor} within {timeout}s " + f"{context} (last seen: {last_seen})" + ) + + +@pytest.mark.parametrize("target_profile", ["P4_GFSK_75K", "P5_GMSK_83K"]) +def test_08_gfsk_wedge_kill_recipe( + fprime_test_api: IntegrationTestAPI, start_gds, target_profile +): + """Force a TX at P0, idle for RF_WEDGE_IDLE_S, switch the TX profile to a + GFSK/GMSK profile, force a TX immediately, then switch back to P0 and + force a TX again; repeat RF_WEDGE_KILL_CYCLES times. Assert + uspRadio.BytesSent advances after every forced TX and no radio error + events are logged.""" + proves_send_and_assert_command(fprime_test_api, f"{radio}.TRANSMIT", ["ENABLED"]) + try: + for cycle in range(WEDGE_KILL_CYCLES): + ctx = f"(cycle {cycle + 1}/{WEDGE_KILL_CYCLES}, {target_profile})" + + # Force a TX at P0 to establish the BytesSent baseline. + baseline = _force_tx_and_await_advance( + fprime_test_api, None, f"at P0 baseline {ctx}" + ) + + # Idle before the switch. + time.sleep(WEDGE_IDLE_S) + + # Switch the TX profile from P0 to the target profile. + start = _now_start() + _switch_profile(fprime_test_api, "TX", target_profile) + + # Force the first TX after the switch. + baseline = _force_tx_and_await_advance( + fprime_test_api, baseline, f"on first TX after P0->{ctx}" + ) + for evt in RADIO_ERROR_EVENTS: + result = fprime_test_api.await_event( + f"{radio}.{evt}", start=start, timeout=0 + ) + assert result is None, ( + f"Unexpected {radio}.{evt} after switch to {target_profile} " + f"{ctx}: {result}" + ) + + # Return to P0 and force a TX again. + start = _now_start() + _switch_profile(fprime_test_api, "TX", BOOT_PROFILE) + _force_tx_and_await_advance( + fprime_test_api, baseline, f"after return to P0 {ctx}" + ) + for evt in RADIO_ERROR_EVENTS: + result = fprime_test_api.await_event( + f"{radio}.{evt}", start=start, timeout=0 + ) + assert result is None, ( + f"Unexpected {radio}.{evt} after return to P0 {ctx}: {result}" + ) + finally: + proves_send_and_assert_command( + fprime_test_api, f"{radio}.TRANSMIT", ["DISABLED"] + ) + + +# --------------------------------------------------------------------------- +# Two-board tests: require a ground radio. Skipped unless the USP_GROUND_* env +# vars are set. +# --------------------------------------------------------------------------- + + +def _require_ground(*env_vars: str) -> None: + missing = [v for v in env_vars if not os.environ.get(v)] + if missing: + pytest.skip("two-board ground radio not configured: set " + ", ".join(missing)) + + +def _set_ground_rx_profile(profile: str) -> None: + cmd = GROUND_CMD.format(profile=PROFILE_IDS[profile]) + subprocess.run(cmd, shell=True, check=True, timeout=60) + + +@pytest.mark.two_board_rf +def test_06_two_board_pairing_downlink(fprime_test_api: IntegrationTestAPI, start_gds): + """For each profile, set the ground radio RX profile and the flight TX + profile to match, enable transmit, and assert raw RF bytes arrive on the + ground radio data CDC. Requires USP_GROUND_CMD and USP_GROUND_DATA_TTY.""" + _require_ground("USP_GROUND_CMD", "USP_GROUND_DATA_TTY") + import serial + + for profile in PROFILE_SWEEP: + _set_ground_rx_profile(profile) + _switch_profile(fprime_test_api, "TX", profile) + proves_send_and_assert_command( + fprime_test_api, f"{radio}.TRANSMIT", ["ENABLED"] + ) + try: + with serial.Serial(GROUND_DATA_TTY, baudrate=115200, timeout=1.0) as ser: + ser.reset_input_buffer() + deadline = time.monotonic() + GROUND_READ_WINDOW_S + rx = bytearray() + while time.monotonic() < deadline and len(rx) == 0: + chunk = ser.read(256) + if chunk: + rx.extend(chunk) + finally: + proves_send_and_assert_command( + fprime_test_api, f"{radio}.TRANSMIT", ["DISABLED"] + ) + assert len(rx) > 0, ( + f"No RF bytes reached the ground radio at profile pairing {profile}" + ) + + +@pytest.mark.two_board_rf +def test_07_two_board_pairing_uplink(fprime_test_api: IntegrationTestAPI, start_gds): + """For each profile, set the flight RX profile to match the ground TX + profile, trigger a ground transmission with USP_GROUND_UPLINK_CMD, and + assert uspRadio.LastRssi updates (it is set on every received frame). + Requires USP_GROUND_UPLINK_CMD.""" + _require_ground("USP_GROUND_UPLINK_CMD") + + for profile in PROFILE_SWEEP: + _switch_profile(fprime_test_api, "RX", profile) + fprime_test_api.clear_histories() + cmd = GROUND_UPLINK_CMD.format(profile=PROFILE_IDS[profile]) + subprocess.run(cmd, shell=True, check=True, timeout=120) + result = fprime_test_api.await_telemetry(f"{radio}.LastRssi", timeout=30) + assert result is not None, ( + f"Flight radio saw no RF frame (no LastRssi update) at profile " + f"pairing {profile}" + ) diff --git a/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt b/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt index 389ccaf6..5f229426 100644 --- a/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt +++ b/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt @@ -5,6 +5,54 @@ enable_testing() add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime/googletest googletest-build) +# --- UspRadio host-side tests --- +# These headers have no F', Zephyr or USP dependencies. LINK_PROFILES_USE_HOST_TYPES +# makes LinkProfiles.hpp, ProfilePolicy.hpp and RadioHeadShim.hpp use +# types instead of F' types. test_LinkProfiles defines it in its own source. + +# LinkProfiles.hpp (header-only). +add_executable(test_LinkProfiles + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/test/ut/test_LinkProfiles.cpp +) +target_include_directories(test_LinkProfiles PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr +) +target_link_libraries(test_LinkProfiles gtest_main) +add_test(NAME test_LinkProfiles COMMAND test_LinkProfiles) + +# ProfilePolicy.cpp plus its test. The define applies to both translation units. +add_executable(test_ProfilePolicy + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/test/ut/test_ProfilePolicy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/ProfilePolicy.cpp +) +target_include_directories(test_ProfilePolicy PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr +) +target_compile_definitions(test_ProfilePolicy PRIVATE LINK_PROFILES_USE_HOST_TYPES) +target_link_libraries(test_ProfilePolicy gtest_main) +add_test(NAME test_ProfilePolicy COMMAND test_ProfilePolicy) + +# RadioHeadShim.hpp (header-only). +add_executable(test_RadioHeadShim + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/test/ut/test_RadioHeadShim.cpp +) +target_include_directories(test_RadioHeadShim PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr +) +target_compile_definitions(test_RadioHeadShim PRIVATE LINK_PROFILES_USE_HOST_TYPES) +target_link_libraries(test_RadioHeadShim gtest_main) +add_test(NAME test_RadioHeadShim COMMAND test_RadioHeadShim) + +# TxOutcomePolicy.hpp (header-only, plain int/bool types, no define needed). +add_executable(test_TxOutcomePolicy + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/test/ut/test_TxOutcomePolicy.cpp +) +target_include_directories(test_TxOutcomePolicy PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr +) +target_link_libraries(test_TxOutcomePolicy gtest_main) +add_test(NAME test_TxOutcomePolicy COMMAND test_TxOutcomePolicy) + # --- Helper Libraries --- # DetumbleManager BDot diff --git a/boards/bronco_space/proves_flight_control_board_v5e/board.yml b/boards/bronco_space/proves_flight_control_board_v5e/board.yml index c51775a8..52e22ac7 100644 --- a/boards/bronco_space/proves_flight_control_board_v5e/board.yml +++ b/boards/bronco_space/proves_flight_control_board_v5e/board.yml @@ -1,6 +1,6 @@ board: name: proves_flight_control_board_v5e full_name: PROVES Flight Control Board v5e - vendor: The Spacecraft Company LLC + vendor: Bronco Space socs: - name: rp2350a diff --git a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts index 96ae3fd4..fd7d71a1 100644 --- a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts +++ b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts @@ -2,31 +2,59 @@ * Copyright (c) 2024 Andrew Featherstone * * SPDX-License-Identifier: Apache-2.0 + * + * PROVES Flight Control Board v5e: SX1262 (E22-400M30S) on SPI1 via the USP driver. + * + * Everything else is inherited from proves_flight_control_board_v5.dtsi, + * including storage_partition, which smtc_modem_hal_storage.c requires. */ /dts-v1/; #include #include -#include #include "../proves_flight_control_board_v5/proves_flight_control_board_v5.dtsi" -/* v5e uses an SX126x-based LoRa module. Override inherited v5 SX1276 settings. */ -&lora0 { - compatible = "semtech,sx1262"; - reset-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>; - busy-gpios = <&gpio0 13 (GPIO_ACTIVE_HIGH)>; - dio1-gpios = <&gpio0 14 (GPIO_ACTIVE_HIGH)>; - tx-enable-gpios = <&gpio0 21 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; - rx-enable-gpios = <&gpio0 22 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; - dio3-tcxo-voltage = ; - tcxo-power-startup-delay-ms = <10>; - rx-boosted; - label = "E22-400M30S"; +#include + +/* Remove the v5 SX1276 node; the USP SX1262 node below takes its SPI1 CS0 slot. */ +/delete-node/ &lora0; + +/* USP (smtc_sw_platform_helper.c) gets its transceiver from DT_CHOSEN(zephyr_lorawan_transceiver). */ +/ { + chosen { + zephyr,lorawan-transceiver = &lora0_usp; + }; +}; - /delete-property/ dio-gpios; - /delete-property/ power-amplifier-output; +&spi1 { + /* + * USP SX1262 node for the E22-400M30S module. + * + * compatible "semtech,sx1262-new" is the USP binding. It keeps the + * in-tree Zephyr lora driver (semtech,sx1262) from binding this node. + * tx/rx-enable-gpios drive the module RF switch. They need the + * RF-switch support in the Open-Source-Space-Foundation usp_zephyr fork. + * The TCXO is on DIO3 at 1.8 V (dio3-as-tcxo-control, tcxo-voltage) + * with a 10 ms wakeup time. reg-mode is LDO: the E22-400M30S has no + * DC-DC inductor. + */ + lora0_usp: sx1262_usp@0 { + compatible = "semtech,sx1262-new"; + reg = <0>; + spi-max-frequency = <125000>; + reset-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>; + busy-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; + dio1-gpios = <&gpio0 14 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>; + dio3-as-tcxo-control; + tcxo-voltage = ; + tcxo-wakeup-time = <10>; + reg-mode = ; + rx-boosted; + tx-enable-gpios = <&gpio0 21 GPIO_ACTIVE_HIGH>; + rx-enable-gpios = <&gpio0 22 GPIO_ACTIVE_HIGH>; + }; }; /* v5e moves the MCP23017 from i2c1 to i2c0. */ diff --git a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig index 58dab132..d48c820b 100644 --- a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig +++ b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig @@ -36,9 +36,14 @@ CONFIG_INA219=y CONFIG_TMP112=y CONFIG_VEML6031=y -# Radio -CONFIG_LORA=y -CONFIG_LORA_SX126X=y +# Radio: USP path (SX1262, E22-400M30S). +# CONFIG_LORA stays off: LORA_BASICS_MODEM_DRIVERS depends on !LORA. +CONFIG_LORA_BASICS_MODEM_DRIVERS=y +CONFIG_LORA_BASICS_MODEM_DRIVERS_RAL_RALF=y +CONFIG_LORA_BASICS_MODEM_DRIVERS_EVENT_TRIGGER_GLOBAL_THREAD=y +CONFIG_USP=y +# Provides zephyr_usp_initialization_wait() and the smtc_rac run loop. +CONFIG_USP_MAIN_THREAD=y # RTC CONFIG_RTC=y diff --git a/bootloader/sysbuild/mcuboot.conf b/bootloader/sysbuild/mcuboot.conf new file mode 100644 index 00000000..9628cc01 --- /dev/null +++ b/bootloader/sysbuild/mcuboot.conf @@ -0,0 +1,4 @@ +# Sysbuild per-image Kconfig fragment (sysbuild/.conf) for MCUboot. +# MCUboot shares the board defconfig but has no CONFIG_LOG, so usp_zephyr does +# not link. Disabling LORA_BASICS_MODEM_DRIVERS also disables USP. +CONFIG_LORA_BASICS_MODEM_DRIVERS=n diff --git a/docs/adr/0001-semtech-usp-for-sx126x-radio-path.md b/docs/adr/0001-semtech-usp-for-sx126x-radio-path.md new file mode 100644 index 00000000..439f1d83 --- /dev/null +++ b/docs/adr/0001-semtech-usp-for-sx126x-radio-path.md @@ -0,0 +1,51 @@ +# 0001: Adopt Semtech USP (RAL layer) for the SX126x radio path; retain loramac-node for SX127x + +Date: 2026-07-04 +Status: Accepted + +## Context + +The UHF radio component (`Zephyr::LoRa` in lib/fprime-zephyr) wraps Zephyr's classic +`drivers/lora` API backed by loramac-node. That API can only express LoRa modulation, +loramac-node is in maintenance mode upstream, and the project wants GFSK (high-rate +downlink) and a clean CW path. + +Three integration options existed: + +1. **Semtech `usp_zephyr` module, component talks to RAL/RAC directly.** Full + modulation menu (LoRa/GFSK/LR-FHSS/CW). Supports SX126x/LR11xx/LR20xx only, no + SX127x, no SX128x. Validated on Zephyr 4.2. +2. **In-tree Zephyr 4.3 LBM backend** (`drivers/lora/lora_basics_modem`, needs the + `lora-basics-modem` west module). Zero component change, covers SX126x *and* SX127x, + but sits behind the standard `lora_modem_config` API, so no new modulations. +3. **Two-phase**: option 2 first for all boards, option 1 later. Touches every board + twice. + +FCB v5/v5c/v5d carry SX1276 (SX127x), unsupported by USP. FCB v5e carries SX1262. +The S-band component (SX1280 via RadioLib) is unsupported by USP. + +## Decision + +- New F´ component (`Zephyr::UspRadio`, sibling of `Zephyr::LoRa` in lib/fprime-zephyr) + targets **USP's RAL directly** via the `usp_zephyr` west module, bypassing the standard + Zephyr `drivers/lora` API. +- **SX127x boards keep the Legacy Radio Path unchanged** (`Zephyr::LoRa` + loramac-node). + Board/topology config selects which component is instantiated; no ifdef sharing of one + implementation. +- The new component keeps the existing Svc.Com + Svc.BufferAllocation port surface and + the `TRANSMIT` / `CONTINUOUS_WAVE` command names and arguments verbatim, adding + profile commands on top (see ADR 0002). +- S-band stays on RadioLib. +- Component is **active (queued)** using the SBand deferred-handler pattern: all + RAL/SPI work runs on the component thread, callbacks only enqueue. + +## Consequences + +- Modulation features land only on v5e+; v5c/v5d get no new radio capability. +- Two radio components coexist in lib/fprime-zephyr; ground dictionaries differ per + board revision. +- We take on Zephyr 4.3-vs-4.2 validation risk for usp_zephyr ourselves. +- Devicetree must ensure exactly one driver binds the `semtech,sx1262` node when both + the in-tree Zephyr driver and USP's driver are present in the tree. +- The standard Zephyr LoRa shell/API tooling does not see the USP radio; all operations + go through F´ commands. diff --git a/docs/adr/0002-link-profile-table.md b/docs/adr/0002-link-profile-table.md new file mode 100644 index 00000000..df22d335 --- /dev/null +++ b/docs/adr/0002-link-profile-table.md @@ -0,0 +1,42 @@ +# 0002: Versioned Link Profile table, split TX/RX selection, RX auto-revert + +Date: 2026-07-04 +Status: Accepted + +## Context + +GFSK introduces many coupled RF parameters (bitrate, deviation, BT, sync word, CRC, +preamble). A single mismatched field between spacecraft and ground kills the link, +and a bad RX configuration on the spacecraft strands it (deaf to commands). +Free-form per-parameter F´ params (the current `CODING_RATE`/`DATA_RATE`/`BANDWIDTH_*` +approach, extended) would make mismatches easy and atomic switches impossible. + +The dominant operational use case is asymmetric: keep the uplink (spacecraft RX) on +robust LoRa while switching only the downlink (spacecraft TX) to GFSK for bulk data. +The existing component already has separate TX/RX bandwidth params. + +## Decision + +- Radio configuration is selected only by **Link Profile index** into a **versioned + Profile Table** checked into one shared artifact consumed by both the flight build + and the GRC build. +- Selection is **per direction**: `SET_TX_PROFILE(profile)` and + `SET_RX_PROFILE(profile, revert_s)`. +- `SET_TX_PROFILE` is unguarded: worst case is a lost downlink until the next command. +- `SET_RX_PROFILE` is guarded by **RX Auto-Revert**: if no valid frame is received + within `revert_s`, RX reverts to the Boot-Default Profile. A valid frame received on + the new profile confirms it. There is no separate confirm command. +- Individual RF parameters are not commandable in operations. The active TX and RX + profile indices are telemetered. A lab-only raw-config path was considered and + deferred: experiments rebuild the table instead. + +## Consequences + +- Adding or changing a profile is a coordinated flight+ground release (table version + bump), not an on-orbit parameter tweak. +- The uplink can be experimented with safely; a failed RX experiment reverts within + the revert window. +- The legacy `Zephyr::LoRa` params remain only on SX127x boards; the new component does + not carry them. +- Profile indices become part of ops vocabulary and sequences; renumbering existing + entries is forbidden (append-only table). diff --git a/lib/fprime b/lib/fprime index 8a62e455..baf163f3 160000 --- a/lib/fprime +++ b/lib/fprime @@ -1 +1 @@ -Subproject commit 8a62e455a90b6d4f498c332d45d65a2a819988d8 +Subproject commit baf163f3ba52ecfabaa39b4aa5847a3cecfb2ae6 diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index 60d395ed..9e3bedcf 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit 60d395edfcca61843045962d1c262674e815008b +Subproject commit 9e3bedcf76ee0dca4e5d67c1e80a46708a48108e diff --git a/lib/zephyr-workspace/zephyr b/lib/zephyr-workspace/zephyr index 1f6485ec..3838a280 160000 --- a/lib/zephyr-workspace/zephyr +++ b/lib/zephyr-workspace/zephyr @@ -1 +1 @@ -Subproject commit 1f6485eca25431b5ff27ce9a754218c9e559bbbb +Subproject commit 3838a2802c916accdfa671de77633ee45b69441c diff --git a/patches/README.md b/patches/README.md index 09cbaf39..b5cbdaa1 100644 --- a/patches/README.md +++ b/patches/README.md @@ -1,17 +1,26 @@ # Patches Directory -This directory contains patches that are automatically applied to git submodules during the build process. +This directory holds one patch: `fprime-yamcs-noapp-path.patch`. -## fprime-gds-version.patch +It patches the pip-installed `fprime-yamcs` package's `fprime_yamcs/__main__.py`, +fixing `--no-app` path handling: `parsed_args.dictionary` is passed as a `Path` +where the callee expects one, and the venv `bin` directory is prepended to `PATH` +so the Java subprocesses can find the fprime-yamcs console scripts. -This patch updates the `fprime-gds` version requirement in `lib/fprime/requirements.txt` from 4.1.0 to 4.1.1a2. +**Application:** applied automatically by `make fprime-venv` (and therefore by +`make`); skipped if already applied. -**Why:** The project requires fprime-gds 4.1.1a2 for specific features: -- file-uplink-cooldown argument -- file-uplink-chunk-size argument +`*.patch` files keep trailing whitespace: pre-commit excludes `patches/` because +the patch context must byte-match the file it patches. -The patch is automatically applied by the `make submodules` target to ensure version consistency and eliminate the version mismatch warning. +## Former patches -**Application:** This patch is applied automatically when running `make submodules` (or `make` which includes that target). +The following module patches were migrated to `Open-Source-Space-Foundation` fork +integration branches pinned in `west.yml` and `.gitmodules`. -**Note:** After applying this patch, `git status` will show `lib/fprime` as modified. This is expected and should **not** be committed. The patched state is reapplied automatically on each `make submodules` run. +| Former patch | Module | Integration PR | +|---|---|---| +| 0001 RF-switch GPIO, 0002 Zephyr-4.3 Kconfig, 0003 LR_FHSS path, 0006 wakeup settle, 0008 RAC mutex, 0010 board.yml schema | usp_zephyr | Open-Source-Space-Foundation/usp_zephyr#7 | +| 0009 radio-planner failsafe unlock exemption | usp | Open-Source-Space-Foundation/usp#3 | +| 0005 + 0007 CDC-ACM TX fixes | zephyr | Open-Source-Space-Foundation/zephyr#3 | +| fprime-com-aggregator-bounded-timeout, fprime-sched-tick-drop | fprime | Open-Source-Space-Foundation/fprime#5 | diff --git a/pytest.ini b/pytest.ini index 07ca9309..5e0b868b 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,6 +7,7 @@ markers = requires_antenna: marks tests that require the antenna board to be plugged in and the burnwire capacitor installed; skip on a bare flight controller requires_battery: marks tests that require the battery board connected with power flowing from the battery terminals; skip on a bare flight controller requires_watchdog_jumper: marks tests that require the JP6 watchdog jumper to be bridged so the watchdog can reset the MCU; skip when JP6 is open + two_board_rf: marks tests that require a second (ground) USP radio board; skip unless the USP_GROUND_* environment variables are set filterwarnings = ignore::DeprecationWarning:yamcs\..* ignore::DeprecationWarning:google\.protobuf\..* diff --git a/west.yml b/west.yml index 73f68164..23f1bbda 100644 --- a/west.yml +++ b/west.yml @@ -31,6 +31,7 @@ manifest: - mcuboot # Bootloader support - fatfs # FatFS (file system) support - hal_st # Required for certain sensors + - segger # SEGGER RTT (debug capture; inert without CONFIG_USE_SEGGER_RTT) - name: loramac-node revision: fb00b383072518c918e2258b0916c996f2d4eebe @@ -87,6 +88,21 @@ manifest: revision: f4ead3bf4a6dab3a07d7b5f5315795c073db568d path: lib/zephyr-workspace/modules/fatfs + # USP radio stack (Semtech Unified Software Platform) Zephyr module, v5e boards. + # Open-Source-Space-Foundation fork, integration branch feat/proves-usp-radio. + - name: usp_zephyr + url: https://github.com/Open-Source-Space-Foundation/usp_zephyr + revision: 0bf3e208124d43c55fcb7f91bae6a9ac9812ee71 + path: lib/zephyr-workspace/modules/lib/usp_zephyr + + # USP core library (LBM + RAL), used by usp_zephyr. Path follows the + # usp_zephyr west.yml convention (modules/lib/usp). + # Open-Source-Space-Foundation fork, integration branch feat/proves-usp-radio. + - name: usp + url: https://github.com/Open-Source-Space-Foundation/usp + revision: 8a60e4a16092f1bfa030b18494da267d8ca6a3ba + path: lib/zephyr-workspace/modules/lib/usp + self: path: . west-commands: west-commands.yml