Skip to content
Open
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
69 changes: 28 additions & 41 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 Down Expand Up @@ -46,55 +43,45 @@ void ComDelay ::comStatusIn_handler(FwIndexType portNum, Fw::Success& condition)
// Read the divider; on invalid/uninit fall back to the default (matches run_handler).
Fw::ParamValid is_valid;
U16 current_divisor = this->paramGet_DIVIDER(is_valid);
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);

if (current_divisor == 0) {
// DIVIDER == 0 means "no delay": forward the status immediately instead of
// latching it for the next rate tick. This removes the rate-group quantization
// (one status per tick) so downlink is paced purely by radio TX-done.
//
// Threading note: ComDelay is passive, so this executes on the CALLER's thread
// (the radio-side comStatus source). That is safe because comStatusOut feeds an
// async input (ComQueue), which only enqueues a message here.
//
// Coherence with run_handler: in passthrough mode we never set
// m_last_status_valid, so this status cannot ALSO be emitted by run_handler
// (no duplication). A status latched earlier under DIVIDER > 0 is still
// consumed by run_handler's compare_exchange as before (no loss) if the
// divider is changed to 0 at runtime.
// Delegate the latch-vs-passthrough decision to the extracted state machine:
// when the (effective) divider is 0, the status is forwarded immediately and
// never latched, so downlink is paced purely by radio TX-done. DIVIDER > 0
// keeps the latched/tick-paced behavior.
//
// Threading note: ComDelay is passive, so a passthrough forward executes on the
// CALLER's thread (the radio-side comStatus source). That is safe because
// comStatusOut feeds an async input (ComQueue), which only enqueues a message.
// In passthrough mode the latch valid flag is never set, so this status cannot
// ALSO be emitted by run_handler (no duplication); a status latched earlier under
// DIVIDER > 0 is still consumed by the tick's compare_exchange (no loss) if the
// divider is changed to 0 at runtime.
bool forward_bit = false;
if (this->m_logic.acceptStatus(condition == Fw::Success::SUCCESS, current_divisor, divider_valid, forward_bit)) {
static_cast<void>(COMDELAY_DIV0_PASSTHROUGH_MARKER[0]); // volatile read keeps the marker in the image
this->comStatusOut_out(0, condition);
} else {
this->m_last_status = condition;
this->m_last_status_valid = true;
Fw::Success forwarded = forward_bit ? Fw::Success::SUCCESS : Fw::Success::FAILURE;
this->comStatusOut_out(0, forwarded);
}
}

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);
bool divider_valid = (is_valid != Fw::ParamValid::INVALID) && (is_valid != Fw::ParamValid::UNINIT);

// 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;
// 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,97 @@
// ======================================================================
// \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;
}

//! Accept an incoming status. When the effective divider (or the default
//! divider, if `dividerValid` is false) is 0, the status is NOT latched:
//! it is returned for immediate forwarding (`outStatus`) and the latch
//! valid flag is never set, so it cannot also be emitted by a later tick.
//! When the effective divider is > 0, the status is latched exactly as
//! latchStatus() does and false is returned.
//!
//! Returns true if the status should be forwarded immediately (passthrough).
bool acceptStatus(bool status, std::uint16_t divider, bool dividerValid, bool& outStatus) {
std::uint16_t current_divisor = dividerValid ? divider : COM_DELAY_DEFAULT_DIVIDER;
if (current_divisor == 0) {
outStatus = status;
return true;
}
this->latchStatus(status);
return false;
}

//! 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 @@ -129,6 +129,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 @@ -162,6 +168,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