From 0beef0ab4116c80442adf0c782feb0096575b15f Mon Sep 17 00:00:00 2001 From: M Starch Date: Fri, 31 Oct 2025 18:22:52 -0700 Subject: [PATCH 1/8] Add start-up manager --- .../Components/CMakeLists.txt | 1 + .../Components/StartupManager/CMakeLists.txt | 36 +++++++ .../StartupManager/StartupManager.cpp | 97 +++++++++++++++++++ .../StartupManager/StartupManager.fpp | 51 ++++++++++ .../StartupManager/StartupManager.hpp | 64 ++++++++++++ .../Components/StartupManager/docs/sdd.md | 66 +++++++++++++ 6 files changed, 315 insertions(+) create mode 100644 FprimeZephyrReference/Components/StartupManager/CMakeLists.txt create mode 100644 FprimeZephyrReference/Components/StartupManager/StartupManager.cpp create mode 100644 FprimeZephyrReference/Components/StartupManager/StartupManager.fpp create mode 100644 FprimeZephyrReference/Components/StartupManager/StartupManager.hpp create mode 100644 FprimeZephyrReference/Components/StartupManager/docs/sdd.md diff --git a/FprimeZephyrReference/Components/CMakeLists.txt b/FprimeZephyrReference/Components/CMakeLists.txt index 26405d0..302ed0f 100644 --- a/FprimeZephyrReference/Components/CMakeLists.txt +++ b/FprimeZephyrReference/Components/CMakeLists.txt @@ -10,3 +10,4 @@ add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Burnwire/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/BootloaderTrigger/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/AntennaDeployer/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/FsSpace/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/StartupManager/") diff --git a/FprimeZephyrReference/Components/StartupManager/CMakeLists.txt b/FprimeZephyrReference/Components/StartupManager/CMakeLists.txt new file mode 100644 index 0000000..362c49b --- /dev/null +++ b/FprimeZephyrReference/Components/StartupManager/CMakeLists.txt @@ -0,0 +1,36 @@ +#### +# F Prime CMakeLists.txt: +# +# SOURCES: list of source files (to be compiled) +# 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/ +# +#### + +# Module names are derived from the path from the nearest project/library/framework +# root when not specifically overridden by the developer. i.e. The module defined by +# `Ref/SignalGen/CMakeLists.txt` will be named `Ref_SignalGen`. + +register_fprime_library( + AUTOCODER_INPUTS + "${CMAKE_CURRENT_LIST_DIR}/StartupManager.fpp" + SOURCES + "${CMAKE_CURRENT_LIST_DIR}/StartupManager.cpp" +# DEPENDS +# MyPackage_MyOtherModule +) + +### Unit Tests ### +# register_fprime_ut( +# AUTOCODER_INPUTS +# "${CMAKE_CURRENT_LIST_DIR}/StartupManager.fpp" +# SOURCES +# "${CMAKE_CURRENT_LIST_DIR}/test/ut/StartupManagerTestMain.cpp" +# "${CMAKE_CURRENT_LIST_DIR}/test/ut/StartupManagerTester.cpp" +# DEPENDS +# STest # For rules-based testing +# UT_AUTO_HELPERS +# ) diff --git a/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp b/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp new file mode 100644 index 0000000..5ac76e0 --- /dev/null +++ b/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp @@ -0,0 +1,97 @@ +// ====================================================================== +// \title StartupManager.cpp +// \author starchmd +// \brief cpp file for StartupManager component implementation class +// ====================================================================== + +#include "FprimeZephyrReference/Components/StartupManager/StartupManager.hpp" +#include "Os/File.hpp" + +namespace Components { + +// ---------------------------------------------------------------------- +// Component construction and destruction +// ---------------------------------------------------------------------- + +StartupManager ::StartupManager(const char* const compName) : StartupManagerComponentBase(compName) {} + +StartupManager ::~StartupManager() {} + +// ---------------------------------------------------------------------- +// Handler implementations for typed input ports +// ---------------------------------------------------------------------- + +FwSizeType StartupManager ::update_boot_count() { + // Read the boot count file path from parameter and assert that it is either valid or the default value + Fw::ParamValid is_valid; + auto boot_count_file = this->paramGet_BOOT_COUNT_FILE(0, is_valid); + FW_ASSERT(is_valid == Fw::ParamValid::VALID || is_valid == Fw::ParamValid::DEFAULT); + + // Open the boot count file and read the current boot count + FwSizeType boot_count = 0; + Os::File file; + Os::File::Status status = file.open(boot_count_file.toChar(), Os::File::OPEN_READ); + U8 buffer[sizeof(FwSizeType)]; + + // If the file read ok, then we read the boot count. Otherwise, we assume boot count is zero thus making + // this the first boot. + if (status == Os::File::OP_OK) { + FwSizeType size = sizeof(buffer); + status = file.read(buffer, size); + if (status == Os::File::OP_OK) { + Fw::ExternalSerializeBuffer buffer_obj(buffer, sizeof(buffer)); + Fw::SerializeStatus serialization_status = buffer_obj.deserializeTo(boot_count); + if (serialization_status != Fw::SerializeStatus::FW_SERIALIZE_OK) { + boot_count = 0; // Default to zero if deserialization fails + } + } + file.close(); + } + boot_count = boot_count + 1; + + // Open the file for writing the boot count + status = file.open(boot_count_file.toChar(), Os::File::OPEN_CREATE, Os::File::OVERWRITE); + if (status == Os::File::OP_OK) { + Fw::ExternalSerializeBuffer buffer_obj(buffer, sizeof(FwSizeType)); + Fw::SerializeStatus serialize_status = buffer_obj.serializeFrom(boot_count); + // Write only when the serialization was successful + if (serialize_status == Fw::SerializeStatus::FW_SERIALIZE_OK) { + FwSizeType size = sizeof(buffer); + (void)file.write(buffer, size); + } + file.close(); + } + return boot_count; +} + +void StartupManager ::run_handler(FwIndexType portNum, U32 context) { + // On the first call, update the boot count + if (this->m_boot_count == 0) { + this->m_boot_count = this->update_boot_count(); + } + Fw::ParamValid is_valid; + while (this->m_boot_count == 1 && armed) { + armed = this->paramGet_ARMED(is_valid); + } + + char buffer[Fw::TimeIntervalValue::SERIALIZED_SIZE]; + + Fw::ParamString bootCount = + + Os::File file; + + Os::File::Status status = file.open(, Os::File::OPEN_READ); + + this->cmdResponse_out(this->m_stored_opcode, this->m_stored_sequence, Fw::CmdResponse::OK); +} + +// ---------------------------------------------------------------------- +// Handler implementations for commands +// ---------------------------------------------------------------------- + +void StartupManager ::WAIT_FOR_QUIESCENCE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { + this->m_stored_opcode = opCode; + this->m_stored_sequence = cmdSeq; +} + +} // namespace Components diff --git a/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp b/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp new file mode 100644 index 0000000..520d211 --- /dev/null +++ b/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp @@ -0,0 +1,51 @@ +module Components { + @ Manages the start-up sequencing + passive component StartupManager { + @ Check RTC time diff + sync input port run: Svc.Sched + + @ Port for sending sequence dispatches + output port seqRunIn: Svc.CmdSeqIn + + @ Command to wait for system quiescence before proceeding with start-up + sync command WAIT_FOR_QUIESCENCE() + + @ Whether the start-up manager is armed to wait for quiescence + param ARMED: bool default true + + @ Time to wait before allowing start-up to proceed + param QUIESCENCE_TIME: Fw.TimeIntervalValue default {seconds = 45 * 60, useconds = 0} + + @ File storing the quiescence start time + param QUIESCENCE_START_TIME: string default "/quiescence_start.bin" + + @ Path to the start-up sequence file + param STARTUP_SEQUENCE: string default "/startup.bin" + + @ File to store the boot count + param BOOT_COUNT_FILE: string default "/boot_count.bin" + + ############################################################################### + # Standard AC Ports: Required for Channels, Events, Commands, and Parameters # + ############################################################################### + @ Port for requesting the current time + time get port timeCaller + + @ Port to return the value of a parameter + param get port prmGetOut + + @Port to set the value of a parameter + param set port prmSetOut + + @ Port for sending command registrations + command reg port cmdRegOut + + @ Port for receiving commands + command recv port cmdIn + + @ Port for sending command responses + command resp port cmdResponseOut + + + } +} diff --git a/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp b/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp new file mode 100644 index 0000000..38d1989 --- /dev/null +++ b/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp @@ -0,0 +1,64 @@ +// ====================================================================== +// \title StartupManager.hpp +// \author starchmd +// \brief hpp file for StartupManager component implementation class +// ====================================================================== + +#ifndef Components_StartupManager_HPP +#define Components_StartupManager_HPP + +#include "FprimeZephyrReference/Components/StartupManager/StartupManagerComponentAc.hpp" + +namespace Components { + +class StartupManager final : public StartupManagerComponentBase { + public: + // ---------------------------------------------------------------------- + // Component construction and destruction + // ---------------------------------------------------------------------- + + //! Construct StartupManager object + StartupManager(const char* const compName //!< The component name + ); + + //! Destroy StartupManager object + ~StartupManager(); + + // Update and return the boot count + FwSizeType update_boot_count(); + + private: + // ---------------------------------------------------------------------- + // Handler implementations for typed input ports + // ---------------------------------------------------------------------- + + //! Handler implementation for run + //! + //! Check RTC time diff + void run_handler(FwIndexType portNum, //!< The port number + U32 context //!< The call order + ) override; + + private: + // ---------------------------------------------------------------------- + // Handler implementations for commands + // ---------------------------------------------------------------------- + + //! Handler implementation for command WAIT_FOR_QUIESCENCE + //! + //! Command to wait for system quiescence before proceeding with start-up + void WAIT_FOR_QUIESCENCE_cmdHandler(FwOpcodeType opCode, //!< The opcode + U32 cmdSeq //!< The command sequence number + ) override; + + private: + Fw::Time m_quiescence_start; //!< Time of the start of the quiescence wait + FwOpcodeType m_stored_opcode; //!< Stored opcode for delayed response + FwSizeType m_boot_count; //!< Current boot count + U32 m_stored_sequence; //!< Stored sequence number for delayed response + bool m_first; //!< Indicates if first run_handler call +}; + +} // namespace Components + +#endif diff --git a/FprimeZephyrReference/Components/StartupManager/docs/sdd.md b/FprimeZephyrReference/Components/StartupManager/docs/sdd.md new file mode 100644 index 0000000..2b0b194 --- /dev/null +++ b/FprimeZephyrReference/Components/StartupManager/docs/sdd.md @@ -0,0 +1,66 @@ +# Components::StartupManager + +Manages the start-up sequencing + +## Usage Examples +Add usage examples here + +### Diagrams +Add diagrams here + +### Typical Usage +And the typical usage of the component here + +## Class Diagram +Add a class diagram here + +## Port Descriptions +| Name | Description | +|---|---| +|---|---| + +## Component States +Add component states in the chart below +| Name | Description | +|---|---| +|---|---| + +## Sequence Diagrams +Add sequence diagrams here + +## Parameters +| Name | Description | +|---|---| +|---|---| + +## Commands +| Name | Description | +|---|---| +|---|---| + +## Events +| Name | Description | +|---|---| +|---|---| + +## Telemetry +| Name | Description | +|---|---| +|---|---| + +## Unit Tests +Add unit test descriptions in the chart below +| Name | Description | Output | Coverage | +|---|---|---|---| +|---|---|---|---| + +## Requirements +Add requirements in the chart below +| Name | Description | Validation | +|---|---|---| +|---|---|---| + +## Change Log +| Date | Description | +|---|---| +|---| Initial Draft | From 9889928accedbda2521450aa339bc163a3d16a6d Mon Sep 17 00:00:00 2001 From: ineskhou Date: Fri, 31 Oct 2025 18:47:28 -0700 Subject: [PATCH 2/8] Added the FileManager stuff --- .../Top/ReferenceDeploymentPackets.fppi | 13 ++++ .../ReferenceDeployment/Top/topology.fpp | 17 ++++- .../project/config/AcConstants.fpp | 70 +++++++++++++++++++ .../project/config/CMakeLists.txt | 1 + 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 FprimeZephyrReference/project/config/AcConstants.fpp diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi index 43f0bbf..825a0b1 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi +++ b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi @@ -61,6 +61,19 @@ telemetry packets ReferenceDeploymentPackets { ReferenceDeployment.antennaDeployer.LastDistance } + packet FileStatus id 9 group 4{ + + FileHandling.fileUplink.FilesReceived + FileHandling.fileUplink.PacketsReceived + FileHandling.fileDownlink.FilesSent + FileHandling.fileDownlink.PacketsSent + FileHandling.fileManager.CommandsExecuted + FileHandling.fileManager.Errors + FileHandling.fileUplink.Warnings + FileHandling.fileDownlink.Warnings + + } + } omit { CdhCore.cmdDisp.CommandErrors # Only has one library, no custom versions diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp b/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp index ec3bcfb..212c7fe 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp +++ b/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp @@ -17,6 +17,7 @@ module ReferenceDeployment { import CdhCore.Subtopology import ComCcsds.FramingSubtopology import ComCcsdsUart.Subtopology + import FileHandling.Subtopology # ---------------------------------------------------------------------- # Instances used in the topology @@ -56,7 +57,7 @@ module ReferenceDeployment { health connections instance CdhCore.$health time connections instance rtcManager telemetry connections instance CdhCore.tlmSend - param connections instance prmDb + param connections instance FileHandling.prmDb # ---------------------------------------------------------------------- # Telemetry packets (only used when TlmPacketizer is used) @@ -124,6 +125,7 @@ module ReferenceDeployment { rateGroup10Hz.RateGroupMemberOut[0] -> comDriver.schedIn rateGroup10Hz.RateGroupMemberOut[1] -> ComCcsdsUart.aggregator.timeout rateGroup10Hz.RateGroupMemberOut[2] -> ComCcsds.aggregator.timeout + rateGroup10Hz.RateGroupMemberOut[3] -> FileHandling.fileManager.schedIn # Slow rate (1Hz) rate group rateGroupDriver.CycleOut[Ports_RateGroups.rateGroup1Hz] -> rateGroup1Hz.CycleIn @@ -137,6 +139,7 @@ module ReferenceDeployment { rateGroup1Hz.RateGroupMemberOut[7] -> burnwire.schedIn rateGroup1Hz.RateGroupMemberOut[8] -> antennaDeployer.schedIn rateGroup1Hz.RateGroupMemberOut[9] -> fsSpace.run + rateGroup1Hz.RateGroupMemberOut[10] -> FileHandling.fileDownlink.Run } @@ -161,5 +164,17 @@ module ReferenceDeployment { imuManager.magneticFieldGet -> lis2mdlManager.magneticFieldGet imuManager.temperatureGet -> lsm6dsoManager.temperatureGet } + + connections ComCcsds_FileHandling { + # File Downlink <-> ComQueue + FileHandling.fileDownlink.bufferSendOut -> ComCcsds.comQueue.bufferQueueIn[ComCcsds.Ports_ComBufferQueue.FILE] + ComCcsds.comQueue.bufferReturnOut[ComCcsds.Ports_ComBufferQueue.FILE] -> FileHandling.fileDownlink.bufferReturn + + # Router <-> FileUplink + ComCcsds.fprimeRouter.fileOut -> FileHandling.fileUplink.bufferSendIn + FileHandling.fileUplink.bufferSendOut -> ComCcsds.fprimeRouter.fileBufferReturnIn + } + + } } diff --git a/FprimeZephyrReference/project/config/AcConstants.fpp b/FprimeZephyrReference/project/config/AcConstants.fpp new file mode 100644 index 0000000..8eeefb4 --- /dev/null +++ b/FprimeZephyrReference/project/config/AcConstants.fpp @@ -0,0 +1,70 @@ +# ====================================================================== +# AcConstants.fpp +# F Prime configuration constants +# ====================================================================== + +@ Number of rate group member output ports for ActiveRateGroup +constant ActiveRateGroupOutputPorts = 15 + +@ Number of rate group member output ports for PassiveRateGroup +constant PassiveRateGroupOutputPorts = 10 + +@ Used to drive rate groups +constant RateGroupDriverRateGroupPorts = 3 + +@ Used for command and registration ports +constant CmdDispatcherComponentCommandPorts = 30 + +@ Used for uplink/sequencer buffer/response ports +constant CmdDispatcherSequencePorts = 5 + +@ Used for dispatching sequences to command sequencers +constant SeqDispatcherSequencerPorts = 2 + +@ Used for sizing the command splitter input arrays +constant CmdSplitterPorts = CmdDispatcherSequencePorts + +@ Number of static memory allocations +constant StaticMemoryAllocations = 4 + +@ Used to ping active components +constant HealthPingPorts = 25 + +@ Used for broadcasting completed file downlinks +constant FileDownCompletePorts = 1 + +@ Used for number of Fw::Com type ports supported by Svc::ComQueue +constant ComQueueComPorts = 2 + +@ Used for number of Fw::Buffer type ports supported by Svc::ComQueue +constant ComQueueBufferPorts = 1 + +@ Used for maximum number of connected buffer repeater consumers +constant BufferRepeaterOutputPorts = 10 + +@ Size of port array for DpManager +constant DpManagerNumPorts = 5 + +@ Size of processing port array for DpWriter +constant DpWriterNumProcPorts = 5 + +@ The size of a file name string +constant FileNameStringSize = 200 + +@ The size of an assert text string +constant FwAssertTextSize = 256 + +@ The size of a file name in an AssertFatalAdapter event +@ Note: File names in assertion failures are also truncated by +@ the constants FW_ASSERT_TEXT_SIZE and FW_LOG_STRING_MAX_SIZE, set +@ in FpConfig.h. +constant AssertFatalAdapterEventFileSize = FileNameStringSize + +# ---------------------------------------------------------------------- +# Hub connections. Connections on all deployments should mirror these settings. +# ---------------------------------------------------------------------- + +constant GenericHubInputPorts = 10 +constant GenericHubOutputPorts = 10 +constant GenericHubInputBuffers = 10 +constant GenericHubOutputBuffers = 10 diff --git a/FprimeZephyrReference/project/config/CMakeLists.txt b/FprimeZephyrReference/project/config/CMakeLists.txt index cf115d7..1cd4f77 100644 --- a/FprimeZephyrReference/project/config/CMakeLists.txt +++ b/FprimeZephyrReference/project/config/CMakeLists.txt @@ -14,5 +14,6 @@ register_fprime_config( "${CMAKE_CURRENT_LIST_DIR}/LoRaCfg.hpp" "${CMAKE_CURRENT_LIST_DIR}/FpConfig.h" "${CMAKE_CURRENT_LIST_DIR}/TlmPacketizerCfg.hpp" + "${CMAKE_CURRENT_LIST_DIR}/AcConstants.fpp" INTERFACE ) From be0fabc68c96b85748768d0371bb13626e8f60bb Mon Sep 17 00:00:00 2001 From: M Starch Date: Fri, 31 Oct 2025 19:17:01 -0700 Subject: [PATCH 3/8] Final start stuff --- .../StartupManager/StartupManager.cpp | 87 +++++++++++++++---- .../StartupManager/StartupManager.fpp | 12 ++- .../StartupManager/StartupManager.hpp | 6 +- 3 files changed, 85 insertions(+), 20 deletions(-) diff --git a/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp b/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp index 5ac76e0..cf6bd6d 100644 --- a/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp +++ b/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp @@ -24,7 +24,7 @@ StartupManager ::~StartupManager() {} FwSizeType StartupManager ::update_boot_count() { // Read the boot count file path from parameter and assert that it is either valid or the default value Fw::ParamValid is_valid; - auto boot_count_file = this->paramGet_BOOT_COUNT_FILE(0, is_valid); + auto boot_count_file = this->paramGet_BOOT_COUNT_FILE(is_valid); FW_ASSERT(is_valid == Fw::ParamValid::VALID || is_valid == Fw::ParamValid::DEFAULT); // Open the boot count file and read the current boot count @@ -47,7 +47,8 @@ FwSizeType StartupManager ::update_boot_count() { } file.close(); } - boot_count = boot_count + 1; + // Boot count of zero is a flag value, so ensure a minimum boot count of 1 + boot_count = FW_MAX(1, boot_count + 1); // Open the file for writing the boot count status = file.open(boot_count_file.toChar(), Os::File::OPEN_CREATE, Os::File::OVERWRITE); @@ -64,25 +65,80 @@ FwSizeType StartupManager ::update_boot_count() { return boot_count; } -void StartupManager ::run_handler(FwIndexType portNum, U32 context) { - // On the first call, update the boot count - if (this->m_boot_count == 0) { - this->m_boot_count = this->update_boot_count(); - } +Fw::Time StartupManager ::get_quiescence_start() { + // Read the quiescence start time file path from parameter and assert that it is either valid or the default value Fw::ParamValid is_valid; - while (this->m_boot_count == 1 && armed) { - armed = this->paramGet_ARMED(is_valid); - } - char buffer[Fw::TimeIntervalValue::SERIALIZED_SIZE]; + auto time_file = this->paramGet_QUIESCENCE_START_FILE(is_valid); + FW_ASSERT(is_valid == Fw::ParamValid::VALID || is_valid == Fw::ParamValid::DEFAULT); + + // Open the boot count file and read the current boot count + Fw::Time time; + Os::File file; + Os::File::Status status = file.open(time_file.toChar(), Os::File::OPEN_READ); + U8 buffer[Fw::Time::SERIALIZED_SIZE]; + + // If the file read ok, then we read the quiescence start time. + if (status == Os::File::OP_OK) { + FwSizeType size = sizeof(buffer); + status = file.read(buffer, size); + if (status == Os::File::OP_OK) { + Fw::ExternalSerializeBuffer buffer_obj(buffer, sizeof(buffer)); + Fw::SerializeStatus serialization_status = buffer_obj.deserializeTo(time); + if (serialization_status != Fw::SerializeStatus::FW_SERIALIZE_OK) { + time = this->getTime(); // Default to current time if deserialization fails + } + } + (void)file.close(); + } + // Write quiescence start time if read failed + if (status != Os::File::OP_OK) { + FwSizeType size = sizeof(buffer); + time = this->getTime(); + status = file.open(time_file.toChar(), Os::File::OPEN_CREATE, Os::File::OVERWRITE); + if (status == Os::File::OP_OK) { + Fw::ExternalSerializeBuffer buffer_obj(buffer, sizeof(Fw::Time::SERIALIZED_SIZE)); + Fw::SerializeStatus serialize_status = buffer_obj.serializeFrom(time); + if (serialize_status == Fw::SerializeStatus::FW_SERIALIZE_OK) { + (void)file.write(buffer, size); + } + } + (void)file.close(); + } + return time; +} - Fw::ParamString bootCount = +void StartupManager ::run_handler(FwIndexType portNum, U32 context) { + Fw::ParamValid is_valid; - Os::File file; + // On the first call, update the boot count, set the quiescence start time, and dispatch the start-up sequence + if (this->m_boot_count == 0) { + this->m_boot_count = this->update_boot_count(); + this->m_quiescence_start = this->get_quiescence_start(); - Os::File::Status status = file.open(, Os::File::OPEN_READ); + Fw::ParamString first_sequence = this->paramGet_STARTUP_SEQUENCE_FILE(is_valid); + FW_ASSERT(is_valid == Fw::ParamValid::VALID || is_valid == Fw::ParamValid::DEFAULT); + this->runSequence_out(0, first_sequence); + } - this->cmdResponse_out(this->m_stored_opcode, this->m_stored_sequence, Fw::CmdResponse::OK); + // Are we waiting for quiescence? + if (this->m_waiting) { + // Check if the system is armed or if this is not the first boot. In both cases, we skip waiting. + bool armed = this->paramGet_ARMED(is_valid); + FW_ASSERT(is_valid == Fw::ParamValid::VALID || is_valid == Fw::ParamValid::DEFAULT); + + Fw::TimeIntervalValue quiescence_period = this->paramGet_QUIESCENCE_TIME(is_valid); + Fw::Time quiescence_interval(quiescence_period.get_seconds(), quiescence_period.get_useconds()); + FW_ASSERT(is_valid == Fw::ParamValid::VALID || is_valid == Fw::ParamValid::DEFAULT); + Fw::Time end_time = Fw::Time::add(this->m_quiescence_start, quiescence_interval); + + // If not armed or this is not the first boot, we skip waiting + if (!armed || end_time <= this->getTime()) { + this->m_waiting = false; + this->cmdResponse_out(this->m_stored_opcode, this->m_stored_sequence, Fw::CmdResponse::OK); + } + } + this->tlmWrite_BootCount(this->m_boot_count); } // ---------------------------------------------------------------------- @@ -92,6 +148,7 @@ void StartupManager ::run_handler(FwIndexType portNum, U32 context) { void StartupManager ::WAIT_FOR_QUIESCENCE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { this->m_stored_opcode = opCode; this->m_stored_sequence = cmdSeq; + this->m_waiting = true; } } // namespace Components diff --git a/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp b/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp index 520d211..8a2d2d3 100644 --- a/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp +++ b/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp @@ -5,11 +5,14 @@ module Components { sync input port run: Svc.Sched @ Port for sending sequence dispatches - output port seqRunIn: Svc.CmdSeqIn + output port runSequence: Svc.CmdSeqIn @ Command to wait for system quiescence before proceeding with start-up sync command WAIT_FOR_QUIESCENCE() + @ Telemetry for boot count + telemetry BootCount: FwSizeType update on change + @ Whether the start-up manager is armed to wait for quiescence param ARMED: bool default true @@ -17,10 +20,10 @@ module Components { param QUIESCENCE_TIME: Fw.TimeIntervalValue default {seconds = 45 * 60, useconds = 0} @ File storing the quiescence start time - param QUIESCENCE_START_TIME: string default "/quiescence_start.bin" + param QUIESCENCE_START_FILE: string default "/quiescence_start.bin" @ Path to the start-up sequence file - param STARTUP_SEQUENCE: string default "/startup.bin" + param STARTUP_SEQUENCE_FILE: string default "/startup.bin" @ File to store the boot count param BOOT_COUNT_FILE: string default "/boot_count.bin" @@ -46,6 +49,7 @@ module Components { @ Port for sending command responses command resp port cmdResponseOut - + @ Port for sending telemetry channels to downlink + telemetry port tlmOut } } diff --git a/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp b/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp index 38d1989..6de84d9 100644 --- a/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp +++ b/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp @@ -7,6 +7,7 @@ #ifndef Components_StartupManager_HPP #define Components_StartupManager_HPP +#include #include "FprimeZephyrReference/Components/StartupManager/StartupManagerComponentAc.hpp" namespace Components { @@ -27,6 +28,9 @@ class StartupManager final : public StartupManagerComponentBase { // Update and return the boot count FwSizeType update_boot_count(); + // Get the quiescence start time + Fw::Time get_quiescence_start(); + private: // ---------------------------------------------------------------------- // Handler implementations for typed input ports @@ -56,7 +60,7 @@ class StartupManager final : public StartupManagerComponentBase { FwOpcodeType m_stored_opcode; //!< Stored opcode for delayed response FwSizeType m_boot_count; //!< Current boot count U32 m_stored_sequence; //!< Stored sequence number for delayed response - bool m_first; //!< Indicates if first run_handler call + std::atomic m_waiting; //!< Indicates if waiting for quiescence }; } // namespace Components From 90d133bee5e783fe2ab2b5844d7e17eaccfcccff Mon Sep 17 00:00:00 2001 From: M Starch Date: Fri, 31 Oct 2025 19:33:43 -0700 Subject: [PATCH 4/8] Add startup manager --- FprimeZephyrReference/ReferenceDeployment/Top/instances.fpp | 2 ++ FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp | 1 + 2 files changed, 3 insertions(+) diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/instances.fpp b/FprimeZephyrReference/ReferenceDeployment/Top/instances.fpp index 26eebbf..d087207 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/instances.fpp +++ b/FprimeZephyrReference/ReferenceDeployment/Top/instances.fpp @@ -89,4 +89,6 @@ module ReferenceDeployment { instance antennaDeployer: Components.AntennaDeployer base id 0x10029000 instance fsSpace: Components.FsSpace base id 0x10030000 + + instance startupManager: Components.StartupManager base id 0x10031000 } diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp b/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp index 212c7fe..d05b111 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp +++ b/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp @@ -140,6 +140,7 @@ module ReferenceDeployment { rateGroup1Hz.RateGroupMemberOut[8] -> antennaDeployer.schedIn rateGroup1Hz.RateGroupMemberOut[9] -> fsSpace.run rateGroup1Hz.RateGroupMemberOut[10] -> FileHandling.fileDownlink.Run + rateGroup1Hz.RateGroupMemberOut[11] -> startupManager.run } From ccea37978f6a792ea31e877129eec728982806b1 Mon Sep 17 00:00:00 2001 From: ineskhou Date: Fri, 31 Oct 2025 19:34:03 -0700 Subject: [PATCH 5/8] added the Config --- .../Top/ReferenceDeploymentPackets.fppi | 21 +++++++------------ .../Top/ReferenceDeploymentTopologyDefs.hpp | 12 ++++++----- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi index 825a0b1..950d71d 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi +++ b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi @@ -61,19 +61,6 @@ telemetry packets ReferenceDeploymentPackets { ReferenceDeployment.antennaDeployer.LastDistance } - packet FileStatus id 9 group 4{ - - FileHandling.fileUplink.FilesReceived - FileHandling.fileUplink.PacketsReceived - FileHandling.fileDownlink.FilesSent - FileHandling.fileDownlink.PacketsSent - FileHandling.fileManager.CommandsExecuted - FileHandling.fileManager.Errors - FileHandling.fileUplink.Warnings - FileHandling.fileDownlink.Warnings - - } - } omit { CdhCore.cmdDisp.CommandErrors # Only has one library, no custom versions @@ -96,4 +83,12 @@ telemetry packets ReferenceDeploymentPackets { CdhCore.version.CustomVersion08 CdhCore.version.CustomVersion09 CdhCore.version.CustomVersion10 + FileHandling.fileUplink.FilesReceived + FileHandling.fileUplink.PacketsReceived + FileHandling.fileDownlink.FilesSent + FileHandling.fileDownlink.PacketsSent + FileHandling.fileManager.CommandsExecuted + FileHandling.fileManager.Errors + FileHandling.fileUplink.Warnings + FileHandling.fileDownlink.Warnings } diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp index 81064f6..7a3d945 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp +++ b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp @@ -15,6 +15,7 @@ // SubtopologyTopologyDefs includes #include "Svc/Subtopologies/CdhCore/SubtopologyTopologyDefs.hpp" #include "Svc/Subtopologies/ComCcsds/SubtopologyTopologyDefs.hpp" +#include "Svc/Subtopologies/FileHandling/SubtopologyTopologyDefs.hpp" // ComCcsds Enum Includes #include "Svc/Subtopologies/ComCcsds/Ports_ComBufferQueueEnumAc.hpp" @@ -69,11 +70,12 @@ 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* loraDevice; //!< LoRa device path for communication - U32 baudRate; //!< Baud rate for UART communication - CdhCore::SubtopologyState cdhCore; //!< Subtopology state for CdhCore - ComCcsds::SubtopologyState comCcsds; //!< Subtopology state for ComCcsds + const device* uartDevice; //!< UART device path for communication + const device* loraDevice; //!< LoRa device path for communication + U32 baudRate; //!< Baud rate for UART communication + CdhCore::SubtopologyState cdhCore; //!< Subtopology state for CdhCore + ComCcsds::SubtopologyState comCcsds; //!< Subtopology state for ComCcsds + FileHandling::SubtopologyState fileHandling; //!< Subtopology state for FileHandling }; namespace PingEntries = ::PingEntries; From dc3b07ecb194daa882ffe5e5e39202832b3cc66f Mon Sep 17 00:00:00 2001 From: M Starch Date: Fri, 31 Oct 2025 22:27:06 -0700 Subject: [PATCH 6/8] Add command sequencer --- .../Top/ReferenceDeploymentPackets.fppi | 6 ++++ .../Top/ReferenceDeploymentTopology.cpp | 6 ++-- .../Top/ReferenceDeploymentTopologyDefs.hpp | 2 +- .../ReferenceDeployment/Top/instances.fpp | 8 ++--- .../ReferenceDeployment/Top/topology.fpp | 17 ++++++---- .../project/config/CMakeLists.txt | 1 + .../project/config/CdhCoreConfig.fpp | 8 ++--- .../config/CommandDispatcherImplCfg.hpp | 2 +- .../project/config/FileHandlingConfig.fpp | 33 +++++++++++++++++++ lib/fprime-zephyr | 2 +- prj.conf | 2 +- 11 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 FprimeZephyrReference/project/config/FileHandlingConfig.fpp diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi index 950d71d..1cebe81 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi +++ b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi @@ -9,6 +9,7 @@ telemetry packets ReferenceDeploymentPackets { ComCcsdsUart.commsBufferManager.HiBuffs ReferenceDeployment.rateGroup10Hz.RgMaxTime ReferenceDeployment.rateGroup1Hz.RgMaxTime +# ReferenceDeployment.startupManager.BootCount } packet HealthWarnings id 2 group 1 { @@ -83,6 +84,11 @@ telemetry packets ReferenceDeploymentPackets { CdhCore.version.CustomVersion08 CdhCore.version.CustomVersion09 CdhCore.version.CustomVersion10 + ReferenceDeployment.cmdSeq.CS_LoadCommands + ReferenceDeployment.cmdSeq.CS_CancelCommands + ReferenceDeployment.cmdSeq.CS_CommandsExecuted + ReferenceDeployment.cmdSeq.CS_SequencesCompleted + ReferenceDeployment.cmdSeq.CS_Errors FileHandling.fileUplink.FilesReceived FileHandling.fileUplink.PacketsReceived FileHandling.fileDownlink.FilesSent diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp index ccef907..3c8f6bc 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp +++ b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp @@ -50,7 +50,7 @@ U32 rateGroup1HzContext[Svc::ActiveRateGroup::CONNECTION_COUNT_MAX] = {getRateGr * desired, but is extracted here for clarity. */ void configureTopology() { - prmDb.configure("/prmDb.dat"); + FileHandling::prmDb.configure("/prmDb.dat"); // Rate group driver needs a divisor list rateGroupDriver.configure(rateGroupDivisorsSet); // Rate groups require context arrays. @@ -60,6 +60,8 @@ void configureTopology() { gpioDriver.open(ledGpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::OUT); gpioBurnwire0.open(burnwire0Gpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::OUT); gpioBurnwire1.open(burnwire1Gpio, Zephyr::ZephyrGpioDriver::GpioConfiguration::OUT); + + cmdSeq.allocateBuffer(0, mallocator, 5 * 1024); } // Public functions for use in main program are namespaced with deployment name ReferenceDeployment @@ -78,7 +80,6 @@ void setupTopology(const TopologyState& state) { // Project-specific component configuration. Function provided above. May be inlined, if desired. configureTopology(); // Autocoded parameter loading. Function provided by autocoder. - prmDb.readParamFile(); loadParameters(); // Autocoded task kick-off (active components). Function provided by autocoder. startTasks(state); @@ -106,5 +107,6 @@ void teardownTopology(const TopologyState& state) { stopTasks(state); freeThreads(state); tearDownComponents(state); + cmdSeq.deallocateBuffer(mallocator); } }; // namespace ReferenceDeployment diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp index 7a3d945..37abd26 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp +++ b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp @@ -54,7 +54,7 @@ enum { WARN = 3, FATAL = 5 }; namespace ReferenceDeployment_rateGroup1Hz { enum { WARN = 3, FATAL = 5 }; } -namespace ReferenceDeployment_prmDb { +namespace ReferenceDeployment_cmdSeq { enum { WARN = 3, FATAL = 5 }; } } // namespace PingEntries diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/instances.fpp b/FprimeZephyrReference/ReferenceDeployment/Top/instances.fpp index d087207..be24c70 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/instances.fpp +++ b/FprimeZephyrReference/ReferenceDeployment/Top/instances.fpp @@ -30,17 +30,17 @@ module ReferenceDeployment { instance rateGroup10Hz: Svc.ActiveRateGroup base id 0x10001000 \ queue size Default.QUEUE_SIZE \ stack size Default.STACK_SIZE \ - priority 3 + priority 2 instance rateGroup1Hz: Svc.ActiveRateGroup base id 0x10002000 \ queue size Default.QUEUE_SIZE \ stack size Default.STACK_SIZE \ - priority 4 + priority 3 - instance prmDb: Svc.PrmDb base id 0x10003000 \ + instance cmdSeq: Svc.CmdSequencer base id 0x10006000 \ queue size Default.QUEUE_SIZE \ stack size Default.STACK_SIZE \ - priority 5 + priority 14 # ---------------------------------------------------------------------- # Queued component instances diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp b/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp index d05b111..e710850 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp +++ b/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp @@ -31,7 +31,6 @@ module ReferenceDeployment { instance gpioBurnwire0 instance gpioBurnwire1 instance watchdog - instance prmDb instance rtcManager instance imuManager instance lis2mdlManager @@ -45,6 +44,8 @@ module ReferenceDeployment { # For UART sideband communication instance comDriver instance fsSpace + instance cmdSeq + #instance startupManager # ---------------------------------------------------------------------- @@ -85,6 +86,9 @@ module ReferenceDeployment { ComCcsdsUart.fprimeRouter.commandOut -> CdhCore.cmdDisp.seqCmdBuff CdhCore.cmdDisp.seqCmdStatus -> ComCcsdsUart.fprimeRouter.cmdResponseIn + + cmdSeq.comCmdOut -> CdhCore.cmdDisp.seqCmdBuff + CdhCore.cmdDisp.seqCmdStatus -> cmdSeq.cmdResponseIn } connections CommunicationsRadio { @@ -126,6 +130,7 @@ module ReferenceDeployment { rateGroup10Hz.RateGroupMemberOut[1] -> ComCcsdsUart.aggregator.timeout rateGroup10Hz.RateGroupMemberOut[2] -> ComCcsds.aggregator.timeout rateGroup10Hz.RateGroupMemberOut[3] -> FileHandling.fileManager.schedIn + rateGroup10Hz.RateGroupMemberOut[4] -> cmdSeq.schedIn # Slow rate (1Hz) rate group rateGroupDriver.CycleOut[Ports_RateGroups.rateGroup1Hz] -> rateGroup1Hz.CycleIn @@ -140,7 +145,7 @@ module ReferenceDeployment { rateGroup1Hz.RateGroupMemberOut[8] -> antennaDeployer.schedIn rateGroup1Hz.RateGroupMemberOut[9] -> fsSpace.run rateGroup1Hz.RateGroupMemberOut[10] -> FileHandling.fileDownlink.Run - rateGroup1Hz.RateGroupMemberOut[11] -> startupManager.run + #rateGroup1Hz.RateGroupMemberOut[11] -> startupManager.run } @@ -168,12 +173,12 @@ module ReferenceDeployment { connections ComCcsds_FileHandling { # File Downlink <-> ComQueue - FileHandling.fileDownlink.bufferSendOut -> ComCcsds.comQueue.bufferQueueIn[ComCcsds.Ports_ComBufferQueue.FILE] - ComCcsds.comQueue.bufferReturnOut[ComCcsds.Ports_ComBufferQueue.FILE] -> FileHandling.fileDownlink.bufferReturn + FileHandling.fileDownlink.bufferSendOut -> ComCcsdsUart.comQueue.bufferQueueIn[ComCcsds.Ports_ComBufferQueue.FILE] + ComCcsdsUart.comQueue.bufferReturnOut[ComCcsds.Ports_ComBufferQueue.FILE] -> FileHandling.fileDownlink.bufferReturn # Router <-> FileUplink - ComCcsds.fprimeRouter.fileOut -> FileHandling.fileUplink.bufferSendIn - FileHandling.fileUplink.bufferSendOut -> ComCcsds.fprimeRouter.fileBufferReturnIn + ComCcsdsUart.fprimeRouter.fileOut -> FileHandling.fileUplink.bufferSendIn + FileHandling.fileUplink.bufferSendOut -> ComCcsdsUart.fprimeRouter.fileBufferReturnIn } diff --git a/FprimeZephyrReference/project/config/CMakeLists.txt b/FprimeZephyrReference/project/config/CMakeLists.txt index 1cd4f77..5287e6f 100644 --- a/FprimeZephyrReference/project/config/CMakeLists.txt +++ b/FprimeZephyrReference/project/config/CMakeLists.txt @@ -11,6 +11,7 @@ register_fprime_config( "${CMAKE_CURRENT_LIST_DIR}/ComCcsdsConfig.fpp" "${CMAKE_CURRENT_LIST_DIR}/ComCfg.fpp" "${CMAKE_CURRENT_LIST_DIR}/CommandDispatcherImplCfg.hpp" + "${CMAKE_CURRENT_LIST_DIR}/FileHandlingConfig.fpp" "${CMAKE_CURRENT_LIST_DIR}/LoRaCfg.hpp" "${CMAKE_CURRENT_LIST_DIR}/FpConfig.h" "${CMAKE_CURRENT_LIST_DIR}/TlmPacketizerCfg.hpp" diff --git a/FprimeZephyrReference/project/config/CdhCoreConfig.fpp b/FprimeZephyrReference/project/config/CdhCoreConfig.fpp index 40ad28a..36c77be 100644 --- a/FprimeZephyrReference/project/config/CdhCoreConfig.fpp +++ b/FprimeZephyrReference/project/config/CdhCoreConfig.fpp @@ -17,10 +17,10 @@ module CdhCoreConfig { } module Priorities { - constant cmdDisp = 10 - constant $health = 11 - constant events = 12 - constant tlmSend = 13 + constant cmdDisp = 7 + constant $health = 8 + constant events = 9 + constant tlmSend = 10 } } diff --git a/FprimeZephyrReference/project/config/CommandDispatcherImplCfg.hpp b/FprimeZephyrReference/project/config/CommandDispatcherImplCfg.hpp index ab88a46..350367d 100644 --- a/FprimeZephyrReference/project/config/CommandDispatcherImplCfg.hpp +++ b/FprimeZephyrReference/project/config/CommandDispatcherImplCfg.hpp @@ -11,7 +11,7 @@ // Define configuration values for dispatcher enum { - CMD_DISPATCHER_DISPATCH_TABLE_SIZE = 50, // !< The size of the table holding opcodes to dispatch + CMD_DISPATCHER_DISPATCH_TABLE_SIZE = 100, // !< The size of the table holding opcodes to dispatch CMD_DISPATCHER_SEQUENCER_TABLE_SIZE = 10, // !< The size of the table holding commands in progress }; diff --git a/FprimeZephyrReference/project/config/FileHandlingConfig.fpp b/FprimeZephyrReference/project/config/FileHandlingConfig.fpp new file mode 100644 index 0000000..1074f62 --- /dev/null +++ b/FprimeZephyrReference/project/config/FileHandlingConfig.fpp @@ -0,0 +1,33 @@ +module FileHandlingConfig { + #Base ID for the FileHandling Subtopology, all components are offsets from this base ID + constant BASE_ID = 0x05000000 + + module QueueSizes { + constant fileUplink = 10 + constant fileDownlink = 10 + constant fileManager = 10 + constant prmDb = 10 + } + + module StackSizes { + constant fileUplink = 8 * 1024 + constant fileDownlink = 8 * 1024 + constant fileManager = 8 * 1024 + constant prmDb = 8 * 1024 + } + + module Priorities { + constant fileUplink = 11 + constant fileDownlink = 12 + constant fileManager = 13 + constant prmDb = 14 + } + + # File downlink configuration constants + module DownlinkConfig { + constant timeout = 1000 # File downlink timeout in ms + constant cooldown = 1000 # File downlink cooldown in ms + constant cycleTime = 1000 # File downlink cycle time in ms + constant fileQueueDepth = 3 # File downlink queue depth + } +} diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index 11c2a10..c07730a 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit 11c2a109c0225745237c46a0f45902ffc598500b +Subproject commit c07730af640261afee9fc0cd8861c2b075110621 diff --git a/prj.conf b/prj.conf index b1e3567..a422ba5 100644 --- a/prj.conf +++ b/prj.conf @@ -36,7 +36,7 @@ CONFIG_DYNAMIC_THREAD=y CONFIG_KERNEL_MEM_POOL=y CONFIG_DYNAMIC_THREAD_ALLOC=n CONFIG_DYNAMIC_THREAD_PREFER_POOL=y -CONFIG_DYNAMIC_THREAD_POOL_SIZE=15 +CONFIG_DYNAMIC_THREAD_POOL_SIZE=20 # Num threads in the thread pool CONFIG_DYNAMIC_THREAD_STACK_SIZE=8192 # Size of thread stack in thread pool, must be >= Thread Pool size in F' From f28b4edeff0d3ad90f053859fa83d5f7c0b016b1 Mon Sep 17 00:00:00 2001 From: M Starch Date: Fri, 31 Oct 2025 23:03:59 -0700 Subject: [PATCH 7/8] Add back in startup manager --- .../Components/StartupManager/StartupManager.cpp | 7 +++++++ .../Components/StartupManager/StartupManager.fpp | 2 ++ .../Components/StartupManager/StartupManager.hpp | 7 +++++++ .../Top/ReferenceDeploymentPackets.fppi | 2 +- FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp | 7 +++++-- 5 files changed, 22 insertions(+), 3 deletions(-) diff --git a/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp b/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp index cf6bd6d..9e934cf 100644 --- a/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp +++ b/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp @@ -108,6 +108,13 @@ Fw::Time StartupManager ::get_quiescence_start() { return time; } +void StartupManager ::completeSequence_handler(FwIndexType portNum, + FwOpcodeType opCode, + U32 cmdSeq, + const Fw::CmdResponse& response) { + // TODO +} + void StartupManager ::run_handler(FwIndexType portNum, U32 context) { Fw::ParamValid is_valid; diff --git a/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp b/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp index 8a2d2d3..1c4793e 100644 --- a/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp +++ b/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp @@ -7,6 +7,8 @@ module Components { @ Port for sending sequence dispatches output port runSequence: Svc.CmdSeqIn + sync input port completeSequence: Fw.CmdResponse + @ Command to wait for system quiescence before proceeding with start-up sync command WAIT_FOR_QUIESCENCE() diff --git a/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp b/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp index 6de84d9..441f385 100644 --- a/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp +++ b/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp @@ -36,6 +36,13 @@ class StartupManager final : public StartupManagerComponentBase { // Handler implementations for typed input ports // ---------------------------------------------------------------------- + //! Handler implementation for completeSequence + void completeSequence_handler(FwIndexType portNum, //!< The port number + FwOpcodeType opCode, //!< Command Op Code + U32 cmdSeq, //!< Command Sequence + const Fw::CmdResponse& response //!< The command response argument + ) override; + //! Handler implementation for run //! //! Check RTC time diff diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi index 1cebe81..fb80339 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi +++ b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi @@ -9,7 +9,7 @@ telemetry packets ReferenceDeploymentPackets { ComCcsdsUart.commsBufferManager.HiBuffs ReferenceDeployment.rateGroup10Hz.RgMaxTime ReferenceDeployment.rateGroup1Hz.RgMaxTime -# ReferenceDeployment.startupManager.BootCount + ReferenceDeployment.startupManager.BootCount } packet HealthWarnings id 2 group 1 { diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp b/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp index e710850..5aa7e31 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp +++ b/FprimeZephyrReference/ReferenceDeployment/Top/topology.fpp @@ -45,7 +45,7 @@ module ReferenceDeployment { instance comDriver instance fsSpace instance cmdSeq - #instance startupManager + instance startupManager # ---------------------------------------------------------------------- @@ -104,6 +104,9 @@ module ReferenceDeployment { lora.dataReturnOut -> ComCcsds.framer.dataReturnIn lora.comStatusOut -> comDelay.comStatusIn comDelay.comStatusOut ->ComCcsds.framer.comStatusIn + + startupManager.runSequence -> cmdSeq.seqRunIn + cmdSeq.seqDone -> startupManager.completeSequence } connections CommunicationsUart { @@ -145,7 +148,7 @@ module ReferenceDeployment { rateGroup1Hz.RateGroupMemberOut[8] -> antennaDeployer.schedIn rateGroup1Hz.RateGroupMemberOut[9] -> fsSpace.run rateGroup1Hz.RateGroupMemberOut[10] -> FileHandling.fileDownlink.Run - #rateGroup1Hz.RateGroupMemberOut[11] -> startupManager.run + rateGroup1Hz.RateGroupMemberOut[11] -> startupManager.run } From c8f7078ecdc24a15770b1345f1213c2c47577d1b Mon Sep 17 00:00:00 2001 From: M Starch Date: Sat, 1 Nov 2025 13:16:18 -0700 Subject: [PATCH 8/8] Working file persistence --- .../StartupManager/StartupManager.cpp | 185 +++++++++++------- .../StartupManager/StartupManager.fpp | 27 +++ .../StartupManager/StartupManager.hpp | 24 ++- .../Top/ReferenceDeploymentPackets.fppi | 1 + 4 files changed, 161 insertions(+), 76 deletions(-) diff --git a/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp b/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp index 9e934cf..b4c480f 100644 --- a/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp +++ b/FprimeZephyrReference/Components/StartupManager/StartupManager.cpp @@ -21,89 +21,120 @@ StartupManager ::~StartupManager() {} // Handler implementations for typed input ports // ---------------------------------------------------------------------- -FwSizeType StartupManager ::update_boot_count() { - // Read the boot count file path from parameter and assert that it is either valid or the default value - Fw::ParamValid is_valid; - auto boot_count_file = this->paramGet_BOOT_COUNT_FILE(is_valid); - FW_ASSERT(is_valid == Fw::ParamValid::VALID || is_valid == Fw::ParamValid::DEFAULT); - - // Open the boot count file and read the current boot count - FwSizeType boot_count = 0; +//! \brief Template function to read a type T from a file at file_path +//! +//! This will read a type T with size 'size' from the file located at file_path. It will return SUCCESS if +//! the read and deserialization were successful, and FAILURE otherwise. +//! +//! The file will be opened and closed within this function. value will not be modified by this function unless +//! the read operation is successful. +//! +//! \warning this function is only safe to use for types T with size `size` that fit well in stack memory. +//! +//! \param file_path: path to the file to read from +//! \param value: reference to the variable to read into +//! \return Status of the read operation +template +StartupManager::Status read(const Fw::StringBase& file_path, T& value) { + // Create the necessary file and deserializer objects for reading a type from a file + StartupManager::Status return_status = StartupManager::FAILURE; Os::File file; - Os::File::Status status = file.open(boot_count_file.toChar(), Os::File::OPEN_READ); - U8 buffer[sizeof(FwSizeType)]; + U8 data_buffer[BUFFER_SIZE]; + Fw::ExternalSerializeBuffer deserializer(data_buffer, sizeof(data_buffer)); - // If the file read ok, then we read the boot count. Otherwise, we assume boot count is zero thus making - // this the first boot. + // Open the file for reading, and continue only if successful + Os::File::Status status = file.open(file_path.toChar(), Os::File::OPEN_READ); if (status == Os::File::OP_OK) { - FwSizeType size = sizeof(buffer); - status = file.read(buffer, size); - if (status == Os::File::OP_OK) { - Fw::ExternalSerializeBuffer buffer_obj(buffer, sizeof(buffer)); - Fw::SerializeStatus serialization_status = buffer_obj.deserializeTo(boot_count); - if (serialization_status != Fw::SerializeStatus::FW_SERIALIZE_OK) { - boot_count = 0; // Default to zero if deserialization fails - } + FwSizeType size = sizeof(data_buffer); + status = file.read(data_buffer, size); + if (status == Os::File::OP_OK && size == sizeof(data_buffer)) { + // When the read is successful, and the size is correct then the buffer must absolutely contain the + // serialized data and thus it is safe to assert on the deserialization status + deserializer.setBuffLen(size); + Fw::SerializeStatus serialize_status = deserializer.deserializeTo(value); + FW_ASSERT(serialize_status == Fw::SerializeStatus::FW_SERIALIZE_OK, + static_cast(serialize_status)); + return_status = StartupManager::SUCCESS; } - file.close(); } - // Boot count of zero is a flag value, so ensure a minimum boot count of 1 - boot_count = FW_MAX(1, boot_count + 1); + (void)file.close(); + return return_status; +} - // Open the file for writing the boot count - status = file.open(boot_count_file.toChar(), Os::File::OPEN_CREATE, Os::File::OVERWRITE); +//! \brief Template function to write a type T to a file at file_path +//! +//! This will write a type T with size 'size' to the file located at file_path. It will return SUCCESS if +//! the serialization and write were successful, and FAILURE otherwise. +//! +//! The file will be opened and closed within this function. +//! +//! \warning this function is only safe to use for types T with size `size` that fit well in stack memory. +//! +//! \param file_path: path to the file to write to +//! \param value: reference to the variable to write +//! \return Status of the write operation +template +StartupManager::Status write(const Fw::StringBase& file_path, const T& value) { + // Create the necessary file and deserializer objects for reading a type from a file + StartupManager::Status return_status = StartupManager::FAILURE; + Os::File file; + U8 data_buffer[BUFFER_SIZE]; + + // Serialize the value into the data buffer. Since the buffer is created here it is safe to assert on the + // serialization status. + Fw::ExternalSerializeBuffer serializer(data_buffer, sizeof(data_buffer)); + Fw::SerializeStatus serialize_status = serializer.serializeFrom(value); + FW_ASSERT(serialize_status == Fw::SerializeStatus::FW_SERIALIZE_OK, static_cast(serialize_status)); + + // Open the file for writing, and continue only if successful + Os::File::Status status = file.open(file_path.toChar(), Os::File::OPEN_CREATE, Os::File::OVERWRITE); if (status == Os::File::OP_OK) { - Fw::ExternalSerializeBuffer buffer_obj(buffer, sizeof(FwSizeType)); - Fw::SerializeStatus serialize_status = buffer_obj.serializeFrom(boot_count); - // Write only when the serialization was successful - if (serialize_status == Fw::SerializeStatus::FW_SERIALIZE_OK) { - FwSizeType size = sizeof(buffer); - (void)file.write(buffer, size); + FwSizeType size = sizeof(data_buffer); + status = file.write(data_buffer, size); + if (status == Os::File::OP_OK && size == sizeof(data_buffer)) { + return_status = StartupManager::SUCCESS; } - file.close(); } - return boot_count; + (void)file.close(); + return return_status; } -Fw::Time StartupManager ::get_quiescence_start() { - // Read the quiescence start time file path from parameter and assert that it is either valid or the default value +FwSizeType StartupManager ::update_boot_count() { + // Read the boot count file path from parameter and assert that it is either valid or the default value + FwSizeType boot_count = 0; Fw::ParamValid is_valid; + auto boot_count_file = this->paramGet_BOOT_COUNT_FILE(is_valid); + FW_ASSERT(is_valid == Fw::ParamValid::VALID || is_valid == Fw::ParamValid::DEFAULT); + // Open the boot count file and add one to the current boot count ensuring a minimum of 1 in the case + // of read failure. Since read will retain the `0` initial value on read failure, we can ignore the error + // status returned by the read. + (void)read(boot_count_file, boot_count); + boot_count = FW_MAX(1, boot_count + 1); + // Rewrite the updated boot count back to the file, and on failure emit a warning about the inability to + // persist the boot count. + StartupManager::Status status = write(boot_count_file, boot_count); + if (status != StartupManager::SUCCESS) { + this->log_WARNING_LO_BootCountUpdateFailure(); + } + return boot_count; +} + +Fw::Time StartupManager ::update_quiescence_start() { + Fw::ParamValid is_valid; auto time_file = this->paramGet_QUIESCENCE_START_FILE(is_valid); FW_ASSERT(is_valid == Fw::ParamValid::VALID || is_valid == Fw::ParamValid::DEFAULT); - // Open the boot count file and read the current boot count - Fw::Time time; - Os::File file; - Os::File::Status status = file.open(time_file.toChar(), Os::File::OPEN_READ); - U8 buffer[Fw::Time::SERIALIZED_SIZE]; - - // If the file read ok, then we read the quiescence start time. - if (status == Os::File::OP_OK) { - FwSizeType size = sizeof(buffer); - status = file.read(buffer, size); - if (status == Os::File::OP_OK) { - Fw::ExternalSerializeBuffer buffer_obj(buffer, sizeof(buffer)); - Fw::SerializeStatus serialization_status = buffer_obj.deserializeTo(time); - if (serialization_status != Fw::SerializeStatus::FW_SERIALIZE_OK) { - time = this->getTime(); // Default to current time if deserialization fails - } - } - (void)file.close(); - } - // Write quiescence start time if read failed - if (status != Os::File::OP_OK) { - FwSizeType size = sizeof(buffer); - time = this->getTime(); - status = file.open(time_file.toChar(), Os::File::OPEN_CREATE, Os::File::OVERWRITE); - if (status == Os::File::OP_OK) { - Fw::ExternalSerializeBuffer buffer_obj(buffer, sizeof(Fw::Time::SERIALIZED_SIZE)); - Fw::SerializeStatus serialize_status = buffer_obj.serializeFrom(time); - if (serialize_status == Fw::SerializeStatus::FW_SERIALIZE_OK) { - (void)file.write(buffer, size); - } + Fw::Time time = this->getTime(); + // Open the quiescence start time file and read the current time. On read failure, return the current time. + StartupManager::Status status = read(time_file, time); + // On read failure, write the current time to the file for future reads. This only happens on read failure because + // there is a singular quiescence start time for the whole mission. + if (status != StartupManager::SUCCESS) { + status = write(time_file, time); + if (status != StartupManager::SUCCESS) { + this->log_WARNING_LO_QuiescenceFileInitFailure(); } - (void)file.close(); } return time; } @@ -112,7 +143,12 @@ void StartupManager ::completeSequence_handler(FwIndexType portNum, FwOpcodeType opCode, U32 cmdSeq, const Fw::CmdResponse& response) { - // TODO + // Respond to the completion status of the start-up sequence + if (response == Fw::CmdResponse::OK) { + this->log_ACTIVITY_LO_StartupSequenceFinished(); + } else { + this->log_WARNING_LO_StartupSequenceFailed(response); + } } void StartupManager ::run_handler(FwIndexType portNum, U32 context) { @@ -121,30 +157,33 @@ void StartupManager ::run_handler(FwIndexType portNum, U32 context) { // On the first call, update the boot count, set the quiescence start time, and dispatch the start-up sequence if (this->m_boot_count == 0) { this->m_boot_count = this->update_boot_count(); - this->m_quiescence_start = this->get_quiescence_start(); + this->m_quiescence_start = this->update_quiescence_start(); Fw::ParamString first_sequence = this->paramGet_STARTUP_SEQUENCE_FILE(is_valid); FW_ASSERT(is_valid == Fw::ParamValid::VALID || is_valid == Fw::ParamValid::DEFAULT); this->runSequence_out(0, first_sequence); } + // Calculate the quiescence end time based on the quiescence period parameter + Fw::TimeIntervalValue quiescence_period = this->paramGet_QUIESCENCE_TIME(is_valid); + Fw::Time quiescence_interval(quiescence_period.get_seconds(), quiescence_period.get_useconds()); + FW_ASSERT(is_valid == Fw::ParamValid::VALID || is_valid == Fw::ParamValid::DEFAULT); + Fw::Time end_time = Fw::Time::add(this->m_quiescence_start, quiescence_interval); + // Are we waiting for quiescence? if (this->m_waiting) { // Check if the system is armed or if this is not the first boot. In both cases, we skip waiting. bool armed = this->paramGet_ARMED(is_valid); FW_ASSERT(is_valid == Fw::ParamValid::VALID || is_valid == Fw::ParamValid::DEFAULT); - Fw::TimeIntervalValue quiescence_period = this->paramGet_QUIESCENCE_TIME(is_valid); - Fw::Time quiescence_interval(quiescence_period.get_seconds(), quiescence_period.get_useconds()); - FW_ASSERT(is_valid == Fw::ParamValid::VALID || is_valid == Fw::ParamValid::DEFAULT); - Fw::Time end_time = Fw::Time::add(this->m_quiescence_start, quiescence_interval); - // If not armed or this is not the first boot, we skip waiting if (!armed || end_time <= this->getTime()) { this->m_waiting = false; this->cmdResponse_out(this->m_stored_opcode, this->m_stored_sequence, Fw::CmdResponse::OK); } } + this->tlmWrite_QuiescenceEndTime( + Fw::TimeValue(end_time.getTimeBase(), end_time.getContext(), end_time.getSeconds(), end_time.getUSeconds())); this->tlmWrite_BootCount(this->m_boot_count); } diff --git a/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp b/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp index 1c4793e..71c717b 100644 --- a/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp +++ b/FprimeZephyrReference/Components/StartupManager/StartupManager.fpp @@ -7,6 +7,7 @@ module Components { @ Port for sending sequence dispatches output port runSequence: Svc.CmdSeqIn + @ Port for receiving the status of the start-up sequence sync input port completeSequence: Fw.CmdResponse @ Command to wait for system quiescence before proceeding with start-up @@ -15,6 +16,25 @@ module Components { @ Telemetry for boot count telemetry BootCount: FwSizeType update on change + @ Telemetry for quiescence end time + telemetry QuiescenceEndTime: Fw.TimeValue update on change + + @ Event emitted when failing to update the boot count file + event BootCountUpdateFailure() severity warning low \ + format "Failed to update boot count file" + + @ Event emitted when the quiescence file was not updated + event QuiescenceFileInitFailure() severity warning low \ + format "Failed to initialize quiescence start time file" + + @ Event emitted when the start-up sequence succeeds + event StartupSequenceFinished() severity activity low \ + format "Start-up sequence finished successfully" + + @ Event emitted when the start-up sequence fails + event StartupSequenceFailed(response: Fw.CmdResponse @< Response code + ) severity warning low format "Start-up sequence failed with response code {}" + @ Whether the start-up manager is armed to wait for quiescence param ARMED: bool default true @@ -53,5 +73,12 @@ module Components { @ Port for sending telemetry channels to downlink telemetry port tlmOut + + @ Port for sending textual representation of events + text event port logTextOut + + @ Port for sending events to downlink + event port logOut + } } diff --git a/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp b/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp index 441f385..b0f91d7 100644 --- a/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp +++ b/FprimeZephyrReference/Components/StartupManager/StartupManager.hpp @@ -14,6 +14,10 @@ namespace Components { class StartupManager final : public StartupManagerComponentBase { public: + enum Status { + SUCCESS, + FAILURE, + }; // ---------------------------------------------------------------------- // Component construction and destruction // ---------------------------------------------------------------------- @@ -25,11 +29,25 @@ class StartupManager final : public StartupManagerComponentBase { //! Destroy StartupManager object ~StartupManager(); - // Update and return the boot count + //! \brief read and increment the boot count + //! + //! Reads the boot count from the boot count file, increments it, and writes it back to the file. If the read + //! fails, the boot count will be initialized to 1. If the write fails, a warning will be emitted. + //! + //! \warning this function will modify the boot count file on disk. + //! + //! \return The updated boot count FwSizeType update_boot_count(); - // Get the quiescence start time - Fw::Time get_quiescence_start(); + //! \brief get and possibly initialize the quiescence start time + //! + //! Reads the quiescence start time from the quiescence start time file. If the read fails, the current time is + //! written to the file and returned. + //! + //! \warning this function will modify the quiescence start time file on disk if it does not already exist. + //! + //! \return The quiescence start time + Fw::Time update_quiescence_start(); private: // ---------------------------------------------------------------------- diff --git a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi index fb80339..b230eef 100644 --- a/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi +++ b/FprimeZephyrReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi @@ -10,6 +10,7 @@ telemetry packets ReferenceDeploymentPackets { ReferenceDeployment.rateGroup10Hz.RgMaxTime ReferenceDeployment.rateGroup1Hz.RgMaxTime ReferenceDeployment.startupManager.BootCount + ReferenceDeployment.startupManager.QuiescenceEndTime } packet HealthWarnings id 2 group 1 {