Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,6 @@ jobs:
run: |
make zephyr-workspace

- name: Apply carried patches
# west update fetches vanilla usp_zephyr/usp/zephyr modules; the USP
# radio port needs the RF-switch GPIO, Zephyr 4.3 compat, wakeup-race,
# radio-planner failsafe, and CDC-ACM fixes carried in patches/.
run: |
make usp-patches
make usp-core-patches
make zephyr-patches

- name: Setup Zephyr SDK
if: steps.cache-zephyr-sdk.outputs.cache-hit != 'true'
run: |
Expand Down
4 changes: 2 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -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
Expand Down
5 changes: 2 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ repos:
rev: v5.0.0
hooks:
- id: trailing-whitespace
# patches/*.patch are machine-generated diffs applied via `git apply`;
# patches/*.patch are machine-generated diffs applied via patch tooling;
# their context/removed lines must byte-match the real upstream files,
# which can legitimately have trailing whitespace. Stripping it here
# silently breaks patch application (see: 0001-feat-sx126x patch vs
# usp_zephyr's semtech,sx126x-new-common.yaml).
# silently breaks patch application.
exclude: ^patches/
- id: end-of-file-fixer
exclude: ^patches/
Expand Down
92 changes: 4 additions & 88 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,6 @@ help: ## Display this help.
submodules: ## Initialize and update git submodules
@git submodule foreach --recursive 'git checkout -- . && git clean -fd' || true
@git submodule update --init --recursive
@echo "Applying fprime ComAggregator bounded-timeout patch (issue #432)..."
@cd lib/fprime && \
if git apply --check ../../patches/fprime-com-aggregator-bounded-timeout.patch 2>/dev/null; then \
git apply ../../patches/fprime-com-aggregator-bounded-timeout.patch && \
echo "✓ Applied ComAggregator bounded-timeout patch"; \
elif git apply --reverse --check ../../patches/fprime-com-aggregator-bounded-timeout.patch 2>/dev/null; then \
echo "⚠ Patch already applied"; \
else \
echo "❌ Error: Unable to apply ComAggregator patch. Run 'cd lib/fprime && git status' to check."; \
exit 1; \
fi
@echo "Applying fprime sched-tick drop patch (issue #432 class)..."
@cd lib/fprime && \
if git apply --check ../../patches/fprime-sched-tick-drop.patch 2>/dev/null; then \
git apply ../../patches/fprime-sched-tick-drop.patch && \
echo "✓ Applied sched-tick drop patch"; \
elif git apply --reverse --check ../../patches/fprime-sched-tick-drop.patch 2>/dev/null; then \
echo "⚠ Patch already applied"; \
else \
echo "❌ Error: Unable to apply sched-tick drop patch. Run 'cd lib/fprime && git status' to check."; \
exit 1; \
fi

export VIRTUAL_ENV ?= $(shell pwd)/fprime-venv
.PHONY: fprime-venv
Expand Down Expand Up @@ -76,72 +54,10 @@ zephyr-setup: fprime-venv ## Set up Zephyr environment
$(UV) pip install --prerelease=allow -r lib/zephyr-workspace/bootloader/mcuboot/zephyr/requirements.txt; \
}

# USP_ZEPHYR_DIR: west places usp_zephyr at this path (see west.yml).
USP_ZEPHYR_DIR ?= $(shell pwd)/lib/zephyr-workspace/modules/lib/usp_zephyr

.PHONY: usp-patches
usp-patches: ## Apply usp_zephyr patches (RF-switch GPIO + Zephyr 4.3 compat + wakeup-busy race fix)
@if [ ! -d "$(USP_ZEPHYR_DIR)" ]; then \
echo "❌ usp_zephyr not found at $(USP_ZEPHYR_DIR) — run 'west update usp_zephyr usp' first"; \
exit 1; \
fi
@echo "Applying usp_zephyr patches..."
@cd "$(USP_ZEPHYR_DIR)" && \
for p in $(shell pwd)/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch \
$(shell pwd)/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch \
$(shell pwd)/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch \
$(shell pwd)/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch \
$(shell pwd)/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch \
$(shell pwd)/patches/0010-fix-boards-xiao-nrf54l15-full_name-zephyr-4.4-schema.patch; do \
name=$$(basename $$p); \
if git apply --check "$$p" 2>/dev/null; then \
git apply "$$p" && echo "✓ Applied $$name"; \
elif git apply --reverse --check "$$p" 2>/dev/null; then \
echo "⚠ Already applied: $$name"; \
else \
echo "❌ Cannot apply $$name — check usp_zephyr revision"; exit 1; \
fi; \
done

# USP_DIR: the Semtech smtc_rac_lib west module (radio planner lives here).
USP_DIR ?= $(shell pwd)/lib/zephyr-workspace/modules/lib/usp

.PHONY: usp-core-patches
usp-core-patches: ## Apply usp (smtc_rac_lib) patches (radio-planner failsafe unlock exemption)
@cd "$(USP_DIR)" && \
for p in $(shell pwd)/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch; do \
name=$$(basename $$p); \
if git apply --check "$$p" 2>/dev/null; then \
git apply "$$p" && echo "✓ Applied $$name"; \
elif git apply --reverse --check "$$p" 2>/dev/null; then \
echo "⚠ Already applied: $$name"; \
else \
echo "❌ Cannot apply $$name — check usp revision"; exit 1; \
fi; \
done

ZEPHYR_DIR ?= $(shell pwd)/lib/zephyr-workspace/zephyr

.PHONY: zephyr-patches
zephyr-patches: ## Apply Zephyr tree patches (CDC-ACM TX fixes)
@if [ ! -d "$(ZEPHYR_DIR)" ]; then \
echo "zephyr not found at $(ZEPHYR_DIR) — run 'west update' first"; \
exit 1; \
fi
@echo "Applying Zephyr patches..."
@cd "$(ZEPHYR_DIR)" && \
for p in \
$(shell pwd)/patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch \
$(shell pwd)/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch; do \
name=$$(basename $$p); \
if git apply --check "$$p" 2>/dev/null; then \
git apply "$$p" && echo "OK Applied $$name"; \
elif git apply --reverse --check "$$p" 2>/dev/null; then \
echo "Already applied: $$name"; \
else \
echo "Cannot apply $$name — check Zephyr revision"; exit 1; \
fi; \
done
# Carried module patches (usp-patches / usp-core-patches / zephyr-patches and
# the fprime patch steps in `submodules`) were removed 2026-07-26: all module
# fixes now live on the Open-Source-Space-Foundation fork integration branches
# (feat/proves-usp-radio) pinned in west.yml / .gitmodules. See patches/README.md.

##@ Development

Expand Down
36 changes: 13 additions & 23 deletions PROVESFlightControllerReference/Components/ComDelay/ComDelay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@

#include "PROVESFlightControllerReference/Components/ComDelay/ComDelay.hpp"

#include "PROVESFlightControllerReference/Components/ComDelay/FppConstantsAc.hpp"

namespace Components {

// ----------------------------------------------------------------------
// Component construction and destruction
// ----------------------------------------------------------------------

ComDelay ::ComDelay(const char* const compName)
: ComDelayComponentBase(compName), m_last_status_valid(false), m_last_status(Fw::Success::FAILURE) {}
ComDelay ::ComDelay(const char* const compName) : ComDelayComponentBase(compName) {}

ComDelay ::~ComDelay() {}

Expand All @@ -39,33 +36,26 @@ void ComDelay ::parameterUpdated(FwPrmIdType id) {
// ----------------------------------------------------------------------

void ComDelay ::comStatusIn_handler(FwIndexType portNum, Fw::Success& condition) {
this->m_last_status = condition;
this->m_last_status_valid = true;
this->m_logic.latchStatus(condition == Fw::Success::SUCCESS);
}

void ComDelay ::run_handler(FwIndexType portNum, U32 context) {
// On the cycle after the tick count is reset, attempt to output any current com status
if (this->m_tick_count == 0) {
bool expected = true;
// Receive the current "last status" validity flag and atomically exchange it with false. This effectively
// "consumes" a valid status. When valid, the last status is sent out.
bool valid = this->m_last_status_valid.compare_exchange_strong(expected, false);
if (valid) {
this->comStatusOut_out(0, this->m_last_status);
}
}

// Unless there is corruption, the parameter should always be valid via its default value; however, in the interest
// of failing-safe and continuing some sort of communication we default the current_divisor to the default value.
Fw::ParamValid is_valid;
U16 current_divisor = this->paramGet_DIVIDER(is_valid);

// Increment and module the tick count by the divisor
if ((is_valid == Fw::ParamValid::INVALID) || (is_valid == Fw::ParamValid::UNINIT)) {
current_divisor = Components::DEFAULT_DIVIDER;
bool divider_valid = (is_valid != Fw::ParamValid::INVALID) && (is_valid != Fw::ParamValid::UNINIT);

// Delegate to the extracted, host-testable tick/divider/latch state machine. This preserves the exact
// pre-existing behavior (including the U8 tick-counter width): on the cycle the counter is at 0, attempt
// to consume (exactly once) any latched status and emit it, then advance/reset the counter against the
// current divisor (or the default divisor, if the parameter is not currently valid).
bool status_bit = false;
bool should_emit = this->m_logic.tick(current_divisor, divider_valid, status_bit);
if (should_emit) {
Fw::Success condition = status_bit ? Fw::Success::SUCCESS : Fw::Success::FAILURE;
this->comStatusOut_out(0, condition);
}
// Count this new tick, resetting whenever the current count is at or higher than the current divider.
this->m_tick_count = (this->m_tick_count >= current_divisor) ? 0 : this->m_tick_count + 1;
}

} // namespace Components
11 changes: 3 additions & 8 deletions PROVESFlightControllerReference/Components/ComDelay/ComDelay.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
#ifndef Components_ComDelay_HPP
#define Components_ComDelay_HPP

#include <atomic>

#include "PROVESFlightControllerReference/Components/ComDelay/ComDelayComponentAc.hpp"
#include "PROVESFlightControllerReference/Components/ComDelay/ComDelayLogic.hpp"

namespace Components {

Expand Down Expand Up @@ -49,12 +48,8 @@ class ComDelay final : public ComDelayComponentBase {
) override;

private:
//! Count of incoming run ticks
U8 m_tick_count;
//! Stores if the last status is currently valid
std::atomic<bool> m_last_status_valid;
//! Stores the last status
Fw::Success m_last_status;
//! Tick-paced divider/latch state machine (host-testable; see ComDelayLogic.hpp)
ComDelayLogic m_logic;
};

} // namespace Components
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// ======================================================================
// \title ComDelayLogic.hpp
// \brief hpp file for ComDelayLogic class
//
// Host-testable extraction of the ComDelay tick/divider/latch state
// machine. This class holds no F' or Zephyr dependencies so it can be
// compiled and unit tested directly on the host (see
// PROVESFlightControllerReference/test/unit-tests/test_ComDelay_ComDelayLogic.cpp).
//
// The ComDelay component (ComDelay.cpp) delegates to this class; the
// behavior here must remain identical to what the component previously
// implemented inline.
// ======================================================================

#pragma once

#include <atomic>
#include <cstdint>

namespace Components {

//! Default divider value: on a 1Hz input tick, this releases a latched
//! status roughly every 30s (299 + 1 ticks).
constexpr std::uint16_t COM_DELAY_DEFAULT_DIVIDER = 299;

class ComDelayLogic {
public:
ComDelayLogic() : m_tick_count(0), m_last_status_valid(false), m_last_status(false) {}

~ComDelayLogic() = default;

//! Latch an incoming status, overwriting any status not yet consumed.
void latchStatus(bool status) {
this->m_last_status = status;
this->m_last_status_valid = true;
}

//! Advance one tick. If the internal counter is currently at 0, attempt to
//! consume (exactly once) any latched status and report it for emission.
//! The counter is then advanced/reset against `divider` (or the default
//! divider, if `dividerValid` is false).
//!
//! Returns true if a latched status should be emitted this tick, and
//! writes the value to consume into `outStatus`.
bool tick(std::uint16_t divider, bool dividerValid, bool& outStatus) {
bool shouldEmit = false;
if (this->m_tick_count == 0) {
bool expected = true;
// Atomically consume the latched status flag, mirroring the
// production compare_exchange_strong "consume once" semantics.
bool valid = this->m_last_status_valid.compare_exchange_strong(expected, false);
if (valid) {
outStatus = this->m_last_status;
shouldEmit = true;
}
}

std::uint16_t current_divisor = dividerValid ? divider : COM_DELAY_DEFAULT_DIVIDER;
this->m_tick_count = (this->m_tick_count >= current_divisor) ? 0 : this->m_tick_count + 1;

return shouldEmit;
}

//! Test/introspection helper: current tick counter value.
std::uint8_t tickCount() const { return this->m_tick_count; }

//! Test/introspection helper: whether a status is currently latched.
bool hasLatchedStatus() const { return this->m_last_status_valid; }

private:
//! Count of incoming run ticks
std::uint8_t m_tick_count;
//! Stores if the last status is currently valid (not yet consumed)
std::atomic<bool> m_last_status_valid;
//! Stores the last latched status
bool m_last_status;
};

} // namespace Components
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ target_include_directories(proves_router_bypasser PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../../..
)

# ComDelay ComDelayLogic (header-only tick/divider/latch state machine)
add_library(comdelay_logic INTERFACE)
target_include_directories(comdelay_logic INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../../..
)

# Find PSA provider (we use libmbedcrypto) and ensure PSA headers exist
find_path(PSA_CRYPTO_H psa/crypto.h)
find_library(MBEDCRYPTO_LIB mbedcrypto)
Expand Down Expand Up @@ -150,6 +156,7 @@ foreach(test_src ${TEST_SOURCES})
security_deframer_authenticator
rtc_manager_rtc_helper
proves_router_bypasser
comdelay_logic
)

add_test(NAME ${test_name} COMMAND ${test_name})
Expand Down
Loading
Loading