From b0716ad6059371e8c4023dde5abb2f66ea403ef1 Mon Sep 17 00:00:00 2001 From: Zimri Leisher Date: Tue, 17 Sep 2024 19:55:09 -0500 Subject: [PATCH 1/5] Add sequence dispatcher component (#2731) * Add sequence dispatcher component * Add seq start port to cmd sequencer * Update author names and some include paths * Get fully compiling, move consts/enums to correct places, check for connections on init * Add spelling exceptions * Get unit tests almost compiling... * Fix string type in port, call component init in test * Fix unit test compilation errors and assertions * Switch back to using StringBase * Switch to FwIndexType, remove textLogIn * UpperCamel events, add warning for unexpected seq start * remove init method, add check for connected to getNextAvailableIdx * Update sdd, change event from low to high, static cast a portnum * Add state diagram, add more warnings, fix wrong header types, use assert instead of warning for runSeq --------- Co-authored-by: Zimri Leisher --- .github/actions/spelling/expect.txt | 3 + Svc/CMakeLists.txt | 1 + Svc/CmdSequencer/CmdSequencer.fpp | 3 + Svc/CmdSequencer/CmdSequencerImpl.cpp | 11 +- Svc/CmdSequencer/CmdSequencerImpl.hpp | 9 +- Svc/CmdSequencer/Sequence.cpp | 7 + Svc/Seq/Seq.fpp | 2 +- Svc/SeqDispatcher/CMakeLists.txt | 24 +++ Svc/SeqDispatcher/SeqDispatcher.cpp | 186 ++++++++++++++++++ Svc/SeqDispatcher/SeqDispatcher.fpp | 55 ++++++ Svc/SeqDispatcher/SeqDispatcher.hpp | 95 +++++++++ Svc/SeqDispatcher/SeqDispatcherCommands.fppi | 9 + Svc/SeqDispatcher/SeqDispatcherEvents.fppi | 38 ++++ Svc/SeqDispatcher/SeqDispatcherTelemetry.fppi | 8 + Svc/SeqDispatcher/docs/sdd.md | 51 +++++ .../docs/seq_dispatcher_model.png | Bin 0 -> 37139 bytes .../test/ut/SeqDispatcherTestMain.cpp | 21 ++ .../test/ut/SeqDispatcherTester.cpp | 92 +++++++++ .../test/ut/SeqDispatcherTester.hpp | 79 ++++++++ config/AcConstants.fpp | 3 + docs/UsersGuide/dev/configuring-fprime.md | 2 +- 21 files changed, 695 insertions(+), 4 deletions(-) create mode 100644 Svc/SeqDispatcher/CMakeLists.txt create mode 100644 Svc/SeqDispatcher/SeqDispatcher.cpp create mode 100644 Svc/SeqDispatcher/SeqDispatcher.fpp create mode 100644 Svc/SeqDispatcher/SeqDispatcher.hpp create mode 100644 Svc/SeqDispatcher/SeqDispatcherCommands.fppi create mode 100644 Svc/SeqDispatcher/SeqDispatcherEvents.fppi create mode 100644 Svc/SeqDispatcher/SeqDispatcherTelemetry.fppi create mode 100644 Svc/SeqDispatcher/docs/sdd.md create mode 100644 Svc/SeqDispatcher/docs/seq_dispatcher_model.png create mode 100644 Svc/SeqDispatcher/test/ut/SeqDispatcherTestMain.cpp create mode 100644 Svc/SeqDispatcher/test/ut/SeqDispatcherTester.cpp create mode 100644 Svc/SeqDispatcher/test/ut/SeqDispatcherTester.hpp diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index a55382cce5..d608eeac1f 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -152,6 +152,7 @@ CMDPACKET CMDREG cmds CMDSEQ +cmdsequencer cnt cntx cobj @@ -548,6 +549,7 @@ lammertbies LASTLOG LBLOCK LCHILD +leisher lemstarch lestarch levelname @@ -1212,4 +1214,5 @@ xsh xsltproc xxxx yacgen +zimri zmq \ No newline at end of file diff --git a/Svc/CMakeLists.txt b/Svc/CMakeLists.txt index 37de2e0110..2ab95efc03 100644 --- a/Svc/CMakeLists.txt +++ b/Svc/CMakeLists.txt @@ -42,6 +42,7 @@ add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/PassiveRateGroup") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/PolyDb/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/PrmDb/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/RateGroupDriver/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/SeqDispatcher/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/StaticMemory/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/TlmChan/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/TlmPacketizer/") diff --git a/Svc/CmdSequencer/CmdSequencer.fpp b/Svc/CmdSequencer/CmdSequencer.fpp index 52df683856..df55efaa74 100644 --- a/Svc/CmdSequencer/CmdSequencer.fpp +++ b/Svc/CmdSequencer/CmdSequencer.fpp @@ -85,6 +85,9 @@ module Svc { @ Schedule in port async input port schedIn: Svc.Sched + @ Notifies that a sequence has started running + output port seqStartOut: Svc.CmdSeqIn + # ---------------------------------------------------------------------- # Commands # ---------------------------------------------------------------------- diff --git a/Svc/CmdSequencer/CmdSequencerImpl.cpp b/Svc/CmdSequencer/CmdSequencerImpl.cpp index 7e1c84d25b..88e2018a62 100644 --- a/Svc/CmdSequencer/CmdSequencerImpl.cpp +++ b/Svc/CmdSequencer/CmdSequencerImpl.cpp @@ -122,6 +122,9 @@ namespace Svc { // Check the step mode. If it is auto, start the sequence if (AUTO == this->m_stepMode) { this->m_runMode = RUNNING; + if(this->isConnected_seqStartOut_OutputPort(0)) { + this->seqStartOut_out(0, this->m_sequence->getStringFileName()); + } this->performCmd_Step(); } @@ -159,7 +162,7 @@ namespace Svc { //! Handler for input port seqRunIn void CmdSequencerComponentImpl::seqRunIn_handler( NATIVE_INT_TYPE portNum, - Fw::String &filename + const Fw::StringBase& filename ) { if (!this->requireRunMode(STOPPED)) { @@ -190,6 +193,9 @@ namespace Svc { // Check the step mode. If it is auto, start the sequence if (AUTO == this->m_stepMode) { this->m_runMode = RUNNING; + if(this->isConnected_seqStartOut_OutputPort(0)) { + this->seqStartOut_out(0, this->m_sequence->getStringFileName()); + } this->performCmd_Step(); } @@ -359,6 +365,9 @@ namespace Svc { this->m_runMode = RUNNING; this->performCmd_Step(); this->log_ACTIVITY_HI_CS_CmdStarted(this->m_sequence->getLogFileName()); + if(this->isConnected_seqStartOut_OutputPort(0)) { + this->seqStartOut_out(0, this->m_sequence->getStringFileName()); + } this->cmdResponse_out(opcode, cmdSeq, Fw::CmdResponse::OK); } diff --git a/Svc/CmdSequencer/CmdSequencerImpl.hpp b/Svc/CmdSequencer/CmdSequencerImpl.hpp index 6e595000b4..655cebd4f3 100644 --- a/Svc/CmdSequencer/CmdSequencerImpl.hpp +++ b/Svc/CmdSequencer/CmdSequencerImpl.hpp @@ -235,6 +235,10 @@ namespace Svc { //! \return The log file name Fw::LogStringArg& getLogFileName(); + //! Get the normal string file name + //! \return The normal string file name + Fw::String& getStringFileName(); + //! Get the sequence header const Header& getHeader() const; @@ -277,6 +281,9 @@ namespace Svc { //! Copy of file name for events Fw::LogStringArg m_logFileName; + //! Copy of file name for ports + Fw::String m_stringFileName; + //! Serialize buffer to hold the binary sequence data Fw::ExternalSerializeBuffer m_buffer; @@ -582,7 +589,7 @@ namespace Svc { //! Handler for input port seqRunIn void seqRunIn_handler( NATIVE_INT_TYPE portNum, //!< The port number - Fw::String &filename //!< The sequence file + const Fw::StringBase& filename //!< The sequence file ); //! Handler for ping port diff --git a/Svc/CmdSequencer/Sequence.cpp b/Svc/CmdSequencer/Sequence.cpp index 235de84c13..30cc2ccb50 100644 --- a/Svc/CmdSequencer/Sequence.cpp +++ b/Svc/CmdSequencer/Sequence.cpp @@ -112,6 +112,7 @@ namespace Svc { { this->m_fileName = fileName; this->m_logFileName = fileName; + this->m_stringFileName = fileName; } Fw::CmdStringArg& CmdSequencerComponentImpl::Sequence :: @@ -126,5 +127,11 @@ namespace Svc { return this->m_logFileName; } + Fw::String& CmdSequencerComponentImpl::Sequence :: + getStringFileName() + { + return this->m_stringFileName; + } + } diff --git a/Svc/Seq/Seq.fpp b/Svc/Seq/Seq.fpp index 66a5d9888c..1caf1ce037 100644 --- a/Svc/Seq/Seq.fpp +++ b/Svc/Seq/Seq.fpp @@ -2,7 +2,7 @@ module Svc { @ Port to request a sequence be run port CmdSeqIn( - ref filename: Fw.String @< The sequence file + filename: string size 240 @< The sequence file ) @ Port to cancel a sequence diff --git a/Svc/SeqDispatcher/CMakeLists.txt b/Svc/SeqDispatcher/CMakeLists.txt new file mode 100644 index 0000000000..bb92108919 --- /dev/null +++ b/Svc/SeqDispatcher/CMakeLists.txt @@ -0,0 +1,24 @@ +#### +# F prime CMakeLists.txt: +# +# SOURCE_FILES: combined list of source and autocoding files +# MOD_DEPS: (optional) module dependencies +# UT_SOURCE_FILES: list of source files for unit tests +# +#### +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/SeqDispatcher.fpp" + "${CMAKE_CURRENT_LIST_DIR}/SeqDispatcher.cpp" +) + +register_fprime_module() + +### UTS ### +set(UT_AUTO_HELPERS ON) + +set(UT_SOURCE_FILES + "${FPRIME_FRAMEWORK_PATH}/Svc/SeqDispatcher/SeqDispatcher.fpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/SeqDispatcherTester.cpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/SeqDispatcherTestMain.cpp" +) +register_fprime_ut() diff --git a/Svc/SeqDispatcher/SeqDispatcher.cpp b/Svc/SeqDispatcher/SeqDispatcher.cpp new file mode 100644 index 0000000000..c55bda62c0 --- /dev/null +++ b/Svc/SeqDispatcher/SeqDispatcher.cpp @@ -0,0 +1,186 @@ +// ====================================================================== +// \title SeqDispatcher.cpp +// \author zimri.leisher +// \brief cpp file for SeqDispatcher component implementation class +// ====================================================================== + +#include + +namespace Svc { + +// ---------------------------------------------------------------------- +// Construction, initialization, and destruction +// ---------------------------------------------------------------------- + +SeqDispatcher ::SeqDispatcher(const char* const compName) + : SeqDispatcherComponentBase(compName) {} + +SeqDispatcher ::~SeqDispatcher() {} + +FwIndexType SeqDispatcher::getNextAvailableSequencerIdx() { + for (FwIndexType i = 0; i < SeqDispatcherSequencerPorts; i++) { + if (this->isConnected_seqRunOut_OutputPort(i) && + this->m_entryTable[i].state == SeqDispatcher_CmdSequencerState::AVAILABLE) { + return i; + } + } + return -1; +} + +void SeqDispatcher::runSequence(FwIndexType sequencerIdx, + const Fw::StringBase& fileName, + Fw::Wait block) { + // this function is only designed for internal usage + // we can guarantee it cannot be called with input that would fail + FW_ASSERT(sequencerIdx >= 0 && sequencerIdx < SeqDispatcherSequencerPorts, + sequencerIdx); + FW_ASSERT(this->isConnected_seqRunOut_OutputPort(sequencerIdx)); + FW_ASSERT(this->m_entryTable[sequencerIdx].state == + SeqDispatcher_CmdSequencerState::AVAILABLE, + this->m_entryTable[sequencerIdx].state); + + if (block == Fw::Wait::NO_WAIT) { + this->m_entryTable[sequencerIdx].state = + SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_NO_BLOCK; + } else { + this->m_entryTable[sequencerIdx].state = + SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_BLOCK; + } + + this->m_sequencersAvailable--; + this->tlmWrite_sequencersAvailable(this->m_sequencersAvailable); + this->m_entryTable[sequencerIdx].sequenceRunning = fileName; + + this->m_dispatchedCount++; + this->tlmWrite_dispatchedCount(this->m_dispatchedCount); + this->seqRunOut_out(sequencerIdx, + this->m_entryTable[sequencerIdx].sequenceRunning); +} + +void SeqDispatcher::seqStartIn_handler( + NATIVE_INT_TYPE portNum, //!< The port number + const Fw::StringBase& fileName //!< The sequence file name +) { + FW_ASSERT(portNum >= 0 && portNum < SeqDispatcherSequencerPorts, portNum); + if (this->m_entryTable[portNum].state == + SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_BLOCK || + this->m_entryTable[portNum].state == + SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_NO_BLOCK) { + // we were aware of this sequencer running a sequence + if (this->m_entryTable[portNum].sequenceRunning != fileName) { + // uh oh. entry table is wrong + // let's just update it to be correct. nothing we can do about + // it except raise a warning and update our state + this->log_WARNING_HI_ConflictingSequenceStarted(static_cast(portNum), fileName, this->m_entryTable[portNum].sequenceRunning); + this->m_entryTable[portNum].sequenceRunning = fileName; + } + } else { + // we were not aware that this sequencer was running. ground must have + // directly commanded that specific sequencer + + // warn because this may be unintentional + this->log_WARNING_LO_UnexpectedSequenceStarted(static_cast(portNum), fileName); + + // update the state + this->m_entryTable[portNum].state = + SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_NO_BLOCK; + this->m_entryTable[portNum].sequenceRunning = fileName; + this->m_sequencersAvailable--; + this->tlmWrite_sequencersAvailable(this->m_sequencersAvailable); + } +} + +void SeqDispatcher::seqDoneIn_handler( + NATIVE_INT_TYPE portNum, //!< The port number + FwOpcodeType opCode, //!< Command Op Code + U32 cmdSeq, //!< Command Sequence + const Fw::CmdResponse& response //!< The command response argument +) { + FW_ASSERT(portNum >= 0 && portNum < SeqDispatcherSequencerPorts, portNum); + if (this->m_entryTable[portNum].state != + SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_BLOCK && + this->m_entryTable[portNum].state != + SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_NO_BLOCK) { + // this sequencer was not running a sequence that we were aware of. + + // we should have caught this in seqStartIn and updated the state + // accordingly, but somehow we didn't? very sad and shouldn't happen + + // anyways, don't have to do anything cuz now that this seq we didn't know + // about is done, the sequencer is available again (which is its current + // state in our internal entry table already) + this->log_WARNING_LO_UnknownSequenceFinished(static_cast(portNum)); + } else { + // ok, a sequence has finished that we knew about + if (this->m_entryTable[portNum].state == + SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_BLOCK) { + // we need to give a cmd response cuz some other sequence is being blocked + // by this + this->cmdResponse_out(this->m_entryTable[portNum].opCode, + this->m_entryTable[portNum].cmdSeq, response); + + if (response == Fw::CmdResponse::EXECUTION_ERROR) { + // dispatched sequence errored + this->m_errorCount++; + this->tlmWrite_errorCount(this->m_errorCount); + } + } + } + + // all command responses mean the sequence is no longer running + // so component should be available + this->m_entryTable[portNum].state = SeqDispatcher_CmdSequencerState::AVAILABLE; + this->m_entryTable[portNum].sequenceRunning = ""; + this->m_sequencersAvailable++; + this->tlmWrite_sequencersAvailable(this->m_sequencersAvailable); +} + +//! Handler for input port seqRunIn +void SeqDispatcher::seqRunIn_handler(NATIVE_INT_TYPE portNum, + const Fw::StringBase& fileName) { + FwIndexType idx = this->getNextAvailableSequencerIdx(); + // no available sequencers + if (idx == -1) { + this->log_WARNING_HI_NoAvailableSequencers(); + return; + } + + this->runSequence(idx, fileName, Fw::Wait::NO_WAIT); +} +// ---------------------------------------------------------------------- +// Command handler implementations +// ---------------------------------------------------------------------- + +void SeqDispatcher ::RUN_cmdHandler(const FwOpcodeType opCode, + const U32 cmdSeq, + const Fw::CmdStringArg& fileName, + Fw::Wait block) { + FwIndexType idx = this->getNextAvailableSequencerIdx(); + // no available sequencers + if (idx == -1) { + this->log_WARNING_HI_NoAvailableSequencers(); + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR); + return; + } + + this->runSequence(idx, fileName, block); + + if (block == Fw::Wait::NO_WAIT) { + // return instantly + this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); + } else { + // otherwise don't return a response yet. just save the opCode and cmdSeq + // so we can return a response later + this->m_entryTable[idx].opCode = opCode; + this->m_entryTable[idx].cmdSeq = cmdSeq; + } +} + +void SeqDispatcher::LOG_STATUS_cmdHandler( + const FwOpcodeType opCode, /*!< The opcode*/ + const U32 cmdSeq) { /*!< The command sequence number*/ + for(FwIndexType idx = 0; idx < SeqDispatcherSequencerPorts; idx++) { + this->log_ACTIVITY_LO_LogSequencerStatus(static_cast(idx), this->m_entryTable[idx].state, Fw::LogStringArg(this->m_entryTable[idx].sequenceRunning)); + } +} +} // end namespace components diff --git a/Svc/SeqDispatcher/SeqDispatcher.fpp b/Svc/SeqDispatcher/SeqDispatcher.fpp new file mode 100644 index 0000000000..3fc6d9702f --- /dev/null +++ b/Svc/SeqDispatcher/SeqDispatcher.fpp @@ -0,0 +1,55 @@ +module Svc { + @ Dispatches command sequences to available command sequencers + active component SeqDispatcher { + + enum CmdSequencerState { + AVAILABLE = 0 + RUNNING_SEQUENCE_BLOCK = 1 + RUNNING_SEQUENCE_NO_BLOCK = 2 + } + + include "SeqDispatcherCommands.fppi" + include "SeqDispatcherTelemetry.fppi" + include "SeqDispatcherEvents.fppi" + + @ Dispatches a sequence to the first available command sequencer + async input port seqRunIn: Svc.CmdSeqIn + + output port seqRunOut: [SeqDispatcherSequencerPorts] Svc.CmdSeqIn + + @ Called by a command sequencer whenever it has finished any sequence + async input port seqDoneIn: [SeqDispatcherSequencerPorts] Fw.CmdResponse + + @ Called by cmdsequencer whenever it starts any sequence + async input port seqStartIn: [SeqDispatcherSequencerPorts] Svc.CmdSeqIn + + match seqRunOut with seqDoneIn + + match seqRunOut with seqStartIn + + ############################################################################### + # Standard AC Ports: Required for Channels, Events, Commands, and Parameters # + ############################################################################### + @ Port for requesting the current time + time get port timeCaller + + @ 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 + + @ Port for sending textual representation of events + text event port logTextOut + + @ Port for sending events to downlink + event port logOut + + @ Port for sending telemetry channels to downlink + telemetry port tlmOut + + } +} \ No newline at end of file diff --git a/Svc/SeqDispatcher/SeqDispatcher.hpp b/Svc/SeqDispatcher/SeqDispatcher.hpp new file mode 100644 index 0000000000..9c8b9f6806 --- /dev/null +++ b/Svc/SeqDispatcher/SeqDispatcher.hpp @@ -0,0 +1,95 @@ +// ====================================================================== +// \title SeqDispatcher.hpp +// \author zimri.leisher +// \brief hpp file for SeqDispatcher component implementation class +// ====================================================================== + +#ifndef SeqDispatcher_HPP +#define SeqDispatcher_HPP + +#include "Svc/SeqDispatcher/SeqDispatcherComponentAc.hpp" +#include "Svc/SeqDispatcher/SeqDispatcher_CmdSequencerStateEnumAc.hpp" +#include "FppConstantsAc.hpp" +#include "Fw/Types/WaitEnumAc.hpp" +#include "Fw/Types/StringBase.hpp" + +namespace Svc { + +class SeqDispatcher : public SeqDispatcherComponentBase { + public: + // ---------------------------------------------------------------------- + // Construction, initialization, and destruction + // ---------------------------------------------------------------------- + + //! Construct object SeqDispatcher + //! + SeqDispatcher(const char* const compName /*!< The component name*/ + ); + + //! Destroy object SeqDispatcher + //! + ~SeqDispatcher(); + + PROTECTED: + + //! Handler for input port seqDoneIn + void + seqDoneIn_handler(NATIVE_INT_TYPE portNum, //!< The port number + FwOpcodeType opCode, //!< Command Op Code + U32 cmdSeq, //!< Command Sequence + const Fw::CmdResponse& response //!< The command response argument + ); + + //! Handler for input port seqStartIn + void seqStartIn_handler(NATIVE_INT_TYPE portNum, //!< The port number + const Fw::StringBase& fileName //!< The sequence file + ); + + //! Handler for input port seqRunIn + void seqRunIn_handler(NATIVE_INT_TYPE portNum, //!< The port number + const Fw::StringBase& fileName //!< The sequence file + ); + + PRIVATE: + + // number of sequences dispatched (successful or otherwise) + U32 m_dispatchedCount = 0; + // number of errors from dispatched sequences (CmdResponse::EXECUTION_ERROR) + U32 m_errorCount = 0; + // number of sequencers in state AVAILABLE + U32 m_sequencersAvailable = SeqDispatcherSequencerPorts; + + struct DispatchEntry { + FwOpcodeType opCode; //!< opcode of entry + U32 cmdSeq; + // store the state of each sequencer + SeqDispatcher_CmdSequencerState state; + // store the sequence currently running for each sequencer + Fw::String sequenceRunning = ""; + } m_entryTable[SeqDispatcherSequencerPorts]; //!< table of dispatch + //!< entries + + FwIndexType getNextAvailableSequencerIdx(); + + void runSequence(FwIndexType sequencerIdx, + const Fw::StringBase& fileName, + Fw::Wait block); + + // ---------------------------------------------------------------------- + // Command handler implementations + // ---------------------------------------------------------------------- + + //! Implementation for RUN command handler + //! + void RUN_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ + const U32 cmdSeq, /*!< The command sequence number*/ + const Fw::CmdStringArg& fileName, /*!< The name of the sequence file*/ + Fw::Wait block); + + void LOG_STATUS_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ + const U32 cmdSeq); /*!< The command sequence number*/ +}; + +} // end namespace components + +#endif diff --git a/Svc/SeqDispatcher/SeqDispatcherCommands.fppi b/Svc/SeqDispatcher/SeqDispatcherCommands.fppi new file mode 100644 index 0000000000..378c2f6ec7 --- /dev/null +++ b/Svc/SeqDispatcher/SeqDispatcherCommands.fppi @@ -0,0 +1,9 @@ +@ Dispatches a sequence to the first available sequencer +async command RUN( + fileName: string size 240 @< The name of the sequence file + $block: Fw.Wait @< Return command status when complete or not + ) \ + opcode 0 + +@ Logs via Events the state of each connected command sequencer +async command LOG_STATUS() opcode 1 \ No newline at end of file diff --git a/Svc/SeqDispatcher/SeqDispatcherEvents.fppi b/Svc/SeqDispatcher/SeqDispatcherEvents.fppi new file mode 100644 index 0000000000..9b92eca254 --- /dev/null +++ b/Svc/SeqDispatcher/SeqDispatcherEvents.fppi @@ -0,0 +1,38 @@ +event InvalidSequencer( + idx: U16 +) \ + severity warning high \ + format "Invalid sequence index {}" + +event NoAvailableSequencers() \ + severity warning high \ + format "No available cmd sequencers to dispatch a sequence to" + +event UnknownSequenceFinished( + idx: U16 +) \ + severity warning low \ + format "Sequencer {} completed a sequence with no matching start notification" + +event ConflictingSequenceStarted( + idx: U16, + newSequence: string size 240, + sequenceInInternalState: string size 240 +) \ + severity warning high \ + format "Sequencer {} started a sequence {} while still running {}" + +event UnexpectedSequenceStarted( + idx: U16, + newSequence: string size 240 +) \ + severity warning low \ + format "Sequencer {} was externally commanded to start a sequence {}" + +event LogSequencerStatus( + idx: U16 + state: CmdSequencerState + filename: string size 240 +) \ + severity activity low \ + format "Sequencer {} with state {} is running file {}" \ No newline at end of file diff --git a/Svc/SeqDispatcher/SeqDispatcherTelemetry.fppi b/Svc/SeqDispatcher/SeqDispatcherTelemetry.fppi new file mode 100644 index 0000000000..3728c9a063 --- /dev/null +++ b/Svc/SeqDispatcher/SeqDispatcherTelemetry.fppi @@ -0,0 +1,8 @@ +@ Number of sequences dispatched +telemetry dispatchedCount: U32 +@ Number of sequences dispatched that returned an error. Note: if a sequence +@ was run in non-blocking mode, even if the sequence errors out, this error +@ count will never increase +telemetry errorCount: U32 +@ Number of sequencers in an available state +telemetry sequencersAvailable: U32 \ No newline at end of file diff --git a/Svc/SeqDispatcher/docs/sdd.md b/Svc/SeqDispatcher/docs/sdd.md new file mode 100644 index 0000000000..86fa11160f --- /dev/null +++ b/Svc/SeqDispatcher/docs/sdd.md @@ -0,0 +1,51 @@ +# components::SeqDispatcher + +Dispatches command sequences to available command sequencers, allowing the spacecraft controllers to run multiple sequences at once without having to manually manage which `CmdSequencer`s those sequences run on. + +### Usage +* Call the `RUN` command just like you would call it on a `CmdSequencer` +* If any connected `CmdSequencer` is available, it will route the sequence to the first one it finds +* `RUN` can be made blocking or non-blocking, just like `CmdSequencer`'s `RUN` + +## State diagram +![State diagram of the SeqDispatcher](seq_dispatcher_model.png "SeqDispatcher model") + +## Port Descriptions +|Type| Name | Description | +|async input|seqRunIn|Equivalent to the RUN cmd, dispatches a sequence to the first available sequencer| +|output|seqRunOut|This is used by the SeqDispatcher to send sequence run calls to sequencers| +|async input|seqDoneIn|Called by a command sequencer whenever it has finished any sequence| +|async input|seqStartIn|Called by a command sequencer whenever it starts any sequence| + +## Commands +| Name | Description | +|RUN|Dispatches a sequence to the first available sequencer| +|LOG_STATUS|Logs via Events the state of each connected command sequencer| + +## Events +| Name | Description | +|InvalidSequencer|The given sequencer index is invalid for an unspecified reason| +|NoAvailableSequencers|There are no available sequencers to dispatch a sequence to| +|UnknownSequenceFinished|We received a call to seqDoneIn that didn't have a corresponding seqStartIn call| +|UnexpectedSequenceStarted|We received a call to seqStartIn but we didn't receive a call to seqDoneIn before that| +|LogSequencerStatus|Shows the current state and sequence filename for a particular sequencer. Produced by the LOG_STATUS command| + + + +## Telemetry +| Name | Description | +|dispatchedCount|Number of sequences dispatched| +|errorCount|Number of sequences dispatched that returned an error. Note: if a sequence was run in non-blocking mode, even if the sequence errors out, this error count will never increase| +|sequencersAvailable|Number of sequencers ready to run a sequence| + +## Unit Tests +Add unit test descriptions in the chart below +| Name | Description | +|testDispatch|Tests the basic dispatch functionality of the `SeqDispatcher`| +|testLogStatus|Tests the LOG_STATUS command| + +## Requirements +Add requirements in the chart below +| Name | Description | Validation | +|---|---|---| +|---|---|---| \ No newline at end of file diff --git a/Svc/SeqDispatcher/docs/seq_dispatcher_model.png b/Svc/SeqDispatcher/docs/seq_dispatcher_model.png new file mode 100644 index 0000000000000000000000000000000000000000..a85d7c7f455925a166b8daf3e5c43dd8e072066e GIT binary patch literal 37139 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 + this->initComponents(); +} + +SeqDispatcherTester ::~SeqDispatcherTester() {} + +// ---------------------------------------------------------------------- +// Tests +// ---------------------------------------------------------------------- + +void SeqDispatcherTester ::testDispatch() { + // test that it fails when we dispatch too many sequences + for (int i = 0; i < SeqDispatcherSequencerPorts; i++) { + sendCmd_RUN(0, 0, Fw::String("test"), Fw::Wait::WAIT); + this->component.doDispatch(); + // no response cuz blocking + ASSERT_CMD_RESPONSE_SIZE(0); + ASSERT_EVENTS_SIZE(0); + } + ASSERT_TLM_sequencersAvailable(SeqDispatcherSequencerPorts - 1, 0); + this->clearHistory(); + // all sequencers should be busy + sendCmd_RUN(0, 0, Fw::String("test"), Fw::Wait::WAIT); + this->component.doDispatch(); + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, SeqDispatcher::OPCODE_RUN, 0, + Fw::CmdResponse::EXECUTION_ERROR); + + this->clearHistory(); + + this->invoke_to_seqDoneIn(0, 0, 0, Fw::CmdResponse::OK); + this->component.doDispatch(); + ASSERT_EVENTS_SIZE(0); + // we should have gotten a cmd response now + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, SeqDispatcher::OPCODE_RUN, 0, Fw::CmdResponse::OK); + + this->clearHistory(); + // ok now we should be able to send another sequence + // let's test non blocking now + sendCmd_RUN(0, 0, Fw::String("test"), Fw::Wait::NO_WAIT); + this->component.doDispatch(); + + // should immediately return + ASSERT_EVENTS_SIZE(0); + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, SeqDispatcher::OPCODE_RUN, 0, Fw::CmdResponse::OK); + this->clearHistory(); + + // ok now check that if a sequence errors on block it will return error + this->invoke_to_seqDoneIn(1, 0, 0, Fw::CmdResponse::EXECUTION_ERROR); + this->component.doDispatch(); + ASSERT_EVENTS_SIZE(0); + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, SeqDispatcher::OPCODE_RUN, 0, Fw::CmdResponse::EXECUTION_ERROR); + +} + +void SeqDispatcherTester::testLogStatus() { + this->sendCmd_RUN(0,0, Fw::String("test"), Fw::Wait::WAIT); + this->component.doDispatch(); + this->sendCmd_LOG_STATUS(0,0); + this->component.doDispatch(); + ASSERT_EVENTS_SIZE(SeqDispatcherSequencerPorts); + ASSERT_EVENTS_LogSequencerStatus(0, 0, SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_BLOCK, "test"); +} + +void SeqDispatcherTester::seqRunOut_handler( + FwIndexType portNum, //!< The port number + const Fw::StringBase& filename //!< The sequence file +) { + this->pushFromPortEntry_seqRunOut(filename); +} + +} // end namespace components diff --git a/Svc/SeqDispatcher/test/ut/SeqDispatcherTester.hpp b/Svc/SeqDispatcher/test/ut/SeqDispatcherTester.hpp new file mode 100644 index 0000000000..f1f9aee162 --- /dev/null +++ b/Svc/SeqDispatcher/test/ut/SeqDispatcherTester.hpp @@ -0,0 +1,79 @@ +// ====================================================================== +// \title SeqDispatcher/test/ut/Tester.hpp +// \author zimri.leisher +// \brief hpp file for SeqDispatcher test harness implementation class +// ====================================================================== + +#ifndef TESTER_HPP +#define TESTER_HPP + +#include "SeqDispatcherGTestBase.hpp" +#include "Svc/SeqDispatcher/SeqDispatcher.hpp" + +namespace Svc{ + +class SeqDispatcherTester : public SeqDispatcherGTestBase { + // ---------------------------------------------------------------------- + // Construction and destruction + // ---------------------------------------------------------------------- + + public: + // Maximum size of histories storing events, telemetry, and port outputs + static const NATIVE_INT_TYPE MAX_HISTORY_SIZE = 10; + // Instance ID supplied to the component instance under test + static const NATIVE_INT_TYPE TEST_INSTANCE_ID = 0; + // Queue depth supplied to component instance under test + static const NATIVE_INT_TYPE TEST_INSTANCE_QUEUE_DEPTH = 10; + + //! Construct object SeqDispatcherTester + //! + SeqDispatcherTester(); + + //! Destroy object SeqDispatcherTester + //! + ~SeqDispatcherTester(); + + public: + // ---------------------------------------------------------------------- + // Tests + // ---------------------------------------------------------------------- + + void testDispatch(); + void testLogStatus(); + + private: + // ---------------------------------------------------------------------- + // Handlers for typed from ports + // ---------------------------------------------------------------------- + + void seqRunOut_handler( + FwIndexType portNum, //!< The port number + const Fw::StringBase& filename //!< The sequence file + ); + + private: + // ---------------------------------------------------------------------- + // Helper methods + // ---------------------------------------------------------------------- + + //! Connect ports + //! + void connectPorts(); + + //! Initialize components + //! + void initComponents(); + + private: + // ---------------------------------------------------------------------- + // Variables + // ---------------------------------------------------------------------- + + //! The component under test + //! + SeqDispatcher component; +}; + +} // end namespace components + +#endif diff --git a/config/AcConstants.fpp b/config/AcConstants.fpp index 2f0f10706c..3ddcff26e6 100644 --- a/config/AcConstants.fpp +++ b/config/AcConstants.fpp @@ -18,6 +18,9 @@ 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 diff --git a/docs/UsersGuide/dev/configuring-fprime.md b/docs/UsersGuide/dev/configuring-fprime.md index 6464fb8732..dc9ee6ea55 100644 --- a/docs/UsersGuide/dev/configuring-fprime.md +++ b/docs/UsersGuide/dev/configuring-fprime.md @@ -53,7 +53,7 @@ number of components. | CmdDispatcherSequencePorts | Number of incoming ports to command dispatcher, e.g. uplink and command sequencer | 5 | Positive integer | | RateGroupDriverRateGroupPorts | Number of rate group driver output ports. Limits total number of different rate groups | 3 | Positive integer | | HealthPingPorts | Number of health ping output ports. Limits number of components attached to health component | 25 | Positive integer | - +| SeqDispatcherSequencerPorts | Number of CmdSequencers that the SeqDispatcher can dispatch sequences to | 2 | Positive integer ## FpConfig.h From 17879e56acd6b8100f9cd1d00cb56bff7b040de3 Mon Sep 17 00:00:00 2001 From: Shivaly-Reddy <164100839+Shivaly-Reddy@users.noreply.github.com> Date: Tue, 17 Sep 2024 18:00:34 -0700 Subject: [PATCH 2/5] Remove versions from SystemResources and updates to Version component (#2866) * FP-2826: Remove version features from SystemResources * FP-2826: When version custom command is sent, regardless of verbosity at minimum events are sent * FP-2826: Update comments for clarification * FP-2826: Fixed spell check issues * FP-2826: Update SDD to reflect verbosity implementational change * FP-2826: Fixes based on git check failures --- Ref/Top/RefPackets.xml | 7 +- Svc/SystemResources/CMakeLists.txt | 1 - Svc/SystemResources/SystemResources.cpp | 18 ---- Svc/SystemResources/SystemResources.fpp | 26 ------ Svc/SystemResources/SystemResources.hpp | 6 -- Svc/SystemResources/docs/sdd.md | 7 +- .../test/ut/SystemResourcesTestMain.cpp | 5 -- .../test/ut/SystemResourcesTester.cpp | 17 +--- .../test/ut/SystemResourcesTester.hpp | 4 - Svc/Version/Version.cpp | 82 ++++++++++--------- Svc/Version/docs/sdd.md | 10 +-- Svc/Version/test/ut/VersionTester.cpp | 24 +++++- 12 files changed, 77 insertions(+), 130 deletions(-) diff --git a/Ref/Top/RefPackets.xml b/Ref/Top/RefPackets.xml index a8e5204119..898a72bf16 100644 --- a/Ref/Top/RefPackets.xml +++ b/Ref/Top/RefPackets.xml @@ -72,12 +72,7 @@ - - - - - - + diff --git a/Svc/SystemResources/CMakeLists.txt b/Svc/SystemResources/CMakeLists.txt index 6fb9cfa7ea..c4a34de837 100644 --- a/Svc/SystemResources/CMakeLists.txt +++ b/Svc/SystemResources/CMakeLists.txt @@ -12,7 +12,6 @@ set(SOURCE_FILES ) set(MOD_DEPS Os - version ) register_fprime_module() ### UTs ### diff --git a/Svc/SystemResources/SystemResources.cpp b/Svc/SystemResources/SystemResources.cpp index 1c2bd04299..f0c01884aa 100644 --- a/Svc/SystemResources/SystemResources.cpp +++ b/Svc/SystemResources/SystemResources.cpp @@ -12,7 +12,6 @@ #include //isnan() #include -#include #include namespace Svc { @@ -73,7 +72,6 @@ void SystemResources ::run_handler(const NATIVE_INT_TYPE portNum, U32 tick_time_ Cpu(); Mem(); PhysMem(); - Version(); } } @@ -88,15 +86,6 @@ void SystemResources ::ENABLE_cmdHandler(const FwOpcodeType opCode, this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); } -void SystemResources ::VERSION_cmdHandler(const FwOpcodeType opCode, const U32 cmdSeq) { - Fw::LogStringArg version_string(Project::Version::FRAMEWORK_VERSION); - this->log_ACTIVITY_LO_FRAMEWORK_VERSION(version_string); - - version_string = Project::Version::PROJECT_VERSION; - this->log_ACTIVITY_LO_PROJECT_VERSION(version_string); - this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); -} - F32 SystemResources::compCpuUtil(Os::SystemResources::CpuTicks current, Os::SystemResources::CpuTicks previous) { F32 util = 100.0f; // Prevent divide by zero on fast-sample @@ -151,11 +140,4 @@ void SystemResources::PhysMem() { } } -void SystemResources::Version() { - Fw::TlmString version_string(Project::Version::FRAMEWORK_VERSION); - this->tlmWrite_FRAMEWORK_VERSION(version_string); - - version_string= Project::Version::PROJECT_VERSION; - this->tlmWrite_PROJECT_VERSION(version_string); -} } // end namespace Svc diff --git a/Svc/SystemResources/SystemResources.fpp b/Svc/SystemResources/SystemResources.fpp index fa7d271dd7..1c2d0f4ad5 100644 --- a/Svc/SystemResources/SystemResources.fpp +++ b/Svc/SystemResources/SystemResources.fpp @@ -42,26 +42,6 @@ module Svc { ) \ opcode 0 - @ Report version as EVR - guarded command VERSION \ - opcode 1 - - @ Version of the git repository. - event FRAMEWORK_VERSION( - version: string size 40 @< version string - ) \ - severity activity low \ - id 0 \ - format "Framework Version: [{}]" - - @ Version of the git repository. - event PROJECT_VERSION( - version: string size 40 @< version string - ) \ - severity activity low \ - id 1 \ - format "Project Version: [{}]" - @ Total system memory in KB telemetry MEMORY_TOTAL: U64 id 0 \ format "{} KB" @@ -129,12 +109,6 @@ module Svc { @ System's CPU Percentage telemetry CPU_15: F32 id 20 format "{.2f} percent" - @ Software framework version - telemetry FRAMEWORK_VERSION: string size 40 id 21 - - @ Software project version - telemetry PROJECT_VERSION: string size 40 id 22 - } } diff --git a/Svc/SystemResources/SystemResources.hpp b/Svc/SystemResources/SystemResources.hpp index 7807a7e067..5a8a599a4c 100644 --- a/Svc/SystemResources/SystemResources.hpp +++ b/Svc/SystemResources/SystemResources.hpp @@ -67,17 +67,11 @@ class SystemResources : public SystemResourcesComponentBase { SystemResourceEnabled enable /*!< whether or not system resource telemetry is enabled*/ ); - //! Implementation for VERSION command handler - //! Report version as EVR - void VERSION_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ - const U32 cmdSeq /*!< The command sequence number*/ - ); private: void Cpu(); void Mem(); void PhysMem(); - void Version(); F32 compCpuUtil(Os::SystemResources::CpuTicks current, Os::SystemResources::CpuTicks previous); diff --git a/Svc/SystemResources/docs/sdd.md b/Svc/SystemResources/docs/sdd.md index db0160c203..37b2757a1b 100644 --- a/Svc/SystemResources/docs/sdd.md +++ b/Svc/SystemResources/docs/sdd.md @@ -3,10 +3,9 @@ The system resources component downlinks information about the running F´ system. This information includes: -1. System and Software version -2. Free Memory -3. CPU load -4. Disk space +1. Free Memory +2. CPU load +3. Disk space These items are downlinked as telemetry channels in response to a rate group port invocation. diff --git a/Svc/SystemResources/test/ut/SystemResourcesTestMain.cpp b/Svc/SystemResources/test/ut/SystemResourcesTestMain.cpp index 36c992659d..37558da3f1 100644 --- a/Svc/SystemResources/test/ut/SystemResourcesTestMain.cpp +++ b/Svc/SystemResources/test/ut/SystemResourcesTestMain.cpp @@ -14,11 +14,6 @@ TEST(OffNominal, Disabled) { tester.test_disable_enable(); } -TEST(Nominal, Events) { - Svc::SystemResourcesTester tester; - tester.test_version_evr(); -} - int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/Svc/SystemResources/test/ut/SystemResourcesTester.cpp b/Svc/SystemResources/test/ut/SystemResourcesTester.cpp index 476a66d6a6..fe9b1adb12 100644 --- a/Svc/SystemResources/test/ut/SystemResourcesTester.cpp +++ b/Svc/SystemResources/test/ut/SystemResourcesTester.cpp @@ -11,7 +11,6 @@ // ====================================================================== #include "SystemResourcesTester.hpp" -#include "versions/version.hpp" #define INSTANCE 0 #define MAX_HISTORY_SIZE 100 @@ -111,13 +110,7 @@ void SystemResourcesTester ::test_tlm(bool enabled) { ASSERT_TLM_NON_VOLATILE_FREE_SIZE(0); ASSERT_TLM_NON_VOLATILE_TOTAL_SIZE(0); } - ASSERT_TLM_FRAMEWORK_VERSION_SIZE((enabled) ? 1 : 0); - ASSERT_TLM_PROJECT_VERSION_SIZE((enabled) ? 1 : 0); - if (enabled) { - ASSERT_TLM_FRAMEWORK_VERSION(0, Project::Version::FRAMEWORK_VERSION); - ASSERT_TLM_PROJECT_VERSION(0, Project::Version::PROJECT_VERSION); - } - ASSERT_TLM_SIZE((enabled) ? (count + 3) : 0); // CPU count channels + avg + 2 ver + ASSERT_TLM_SIZE((enabled) ? (count + 1) : 0); // CPU count channels + avg + 2 ver break; } } @@ -130,14 +123,6 @@ void SystemResourcesTester ::test_disable_enable() { this->test_tlm(true); } -void SystemResourcesTester ::test_version_evr() { - this->sendCmd_VERSION(0, 0); - ASSERT_EVENTS_FRAMEWORK_VERSION_SIZE(1); - ASSERT_EVENTS_FRAMEWORK_VERSION(0, Project::Version::FRAMEWORK_VERSION); - ASSERT_EVENTS_PROJECT_VERSION_SIZE(1); - ASSERT_EVENTS_PROJECT_VERSION(0, Project::Version::FRAMEWORK_VERSION); -} - // ---------------------------------------------------------------------- // Helper methods // ---------------------------------------------------------------------- diff --git a/Svc/SystemResources/test/ut/SystemResourcesTester.hpp b/Svc/SystemResources/test/ut/SystemResourcesTester.hpp index 6c3fcff571..392d70eddd 100644 --- a/Svc/SystemResources/test/ut/SystemResourcesTester.hpp +++ b/Svc/SystemResources/test/ut/SystemResourcesTester.hpp @@ -45,10 +45,6 @@ class SystemResourcesTester : public SystemResourcesGTestBase { //! void test_disable_enable(); - //! Test version EVR - //! - void test_version_evr(); - private: // ---------------------------------------------------------------------- // Helper methods diff --git a/Svc/Version/Version.cpp b/Svc/Version/Version.cpp index c659ce688d..70f94f449d 100644 --- a/Svc/Version/Version.cpp +++ b/Svc/Version/Version.cpp @@ -112,7 +112,7 @@ void Version ::VERSION_cmdHandler(FwOpcodeType opCode, U32 cmdSeq, Svc::VersionT // ---------------------------------------------------------------------- // implementations for internal functions // ---------------------------------------------------------------------- -// functions to log tlm on versions +// Send both events and tlm for framework version void Version ::fwVersion_tlm() { Fw::LogStringArg fw_event = (Project::Version::FRAMEWORK_VERSION); this->log_ACTIVITY_LO_FrameworkVersion(fw_event); @@ -120,6 +120,7 @@ void Version ::fwVersion_tlm() { this->tlmWrite_FrameworkVersion(fw_tlm); } +// Send both events and tlm for project version void Version ::projectVersion_tlm() { Fw::LogStringArg proj_event = Project::Version::PROJECT_VERSION; this->log_ACTIVITY_LO_ProjectVersion(proj_event); @@ -127,6 +128,7 @@ void Version ::projectVersion_tlm() { this->tlmWrite_ProjectVersion(proj_tlm); } +// Send both events and tlm for library versions void Version ::libraryVersion_tlm() { // Process libraries array @@ -173,6 +175,7 @@ void Version ::libraryVersion_tlm() { } } +// Send all events and tlm (if verbosity is enabled) for custom versions void Version ::customVersion_tlm_all() { for (U8 i = 0; (m_enable == true) && (m_num_custom_elements != 0) && (i < Svc::VersionCfg::VersionEnum::NUM_CONSTANTS); i++) { @@ -180,52 +183,55 @@ void Version ::customVersion_tlm_all() { } } +// Send events and tlm (if verbosity is enabled) for custom versions void Version ::customVersion_tlm(VersionSlot custom_slot) { // Process custom version TLM only if verbosity is enabled and there are any valid writes to it; // it doesn't necessarily have to be consecutive - if ((this->verId_db[custom_slot].getversion_value() != "no_ver") && m_enable == true && + if ((this->verId_db[custom_slot].getversion_value() != "no_ver") && (this->m_num_custom_elements > 0)) { // Write TLM for valid writes - // Emit Events/TLM on library versions + // Emit Events/TLM on custom versions this->log_ACTIVITY_LO_CustomVersions(this->verId_db[custom_slot].getversion_enum(), this->verId_db[custom_slot].getversion_value()); + if (m_enable == true) { //Send TLM only if verbosity is enabled // Write to TLM - switch (custom_slot) { - case VER_SLOT_00: - this->tlmWrite_CustomVersion01(verId_db[custom_slot]); - break; - case VER_SLOT_01: - this->tlmWrite_CustomVersion02(verId_db[custom_slot]); - break; - case VER_SLOT_02: - this->tlmWrite_CustomVersion03(verId_db[custom_slot]); - break; - case VER_SLOT_03: - this->tlmWrite_CustomVersion04(verId_db[custom_slot]); - break; - case VER_SLOT_04: - this->tlmWrite_CustomVersion05(verId_db[custom_slot]); - break; - case VER_SLOT_05: - this->tlmWrite_CustomVersion06(verId_db[custom_slot]); - break; - case VER_SLOT_06: - this->tlmWrite_CustomVersion07(verId_db[custom_slot]); - break; - case VER_SLOT_07: - this->tlmWrite_CustomVersion08(verId_db[custom_slot]); - break; - case VER_SLOT_08: - this->tlmWrite_CustomVersion09(verId_db[custom_slot]); - break; - case VER_SLOT_09: - this->tlmWrite_CustomVersion10(verId_db[custom_slot]); - break; - default: - // There are only 10 custom slots available - FW_ASSERT(0, custom_slot); - break; + switch (custom_slot) { + case VER_SLOT_00: + this->tlmWrite_CustomVersion01(verId_db[custom_slot]); + break; + case VER_SLOT_01: + this->tlmWrite_CustomVersion02(verId_db[custom_slot]); + break; + case VER_SLOT_02: + this->tlmWrite_CustomVersion03(verId_db[custom_slot]); + break; + case VER_SLOT_03: + this->tlmWrite_CustomVersion04(verId_db[custom_slot]); + break; + case VER_SLOT_04: + this->tlmWrite_CustomVersion05(verId_db[custom_slot]); + break; + case VER_SLOT_05: + this->tlmWrite_CustomVersion06(verId_db[custom_slot]); + break; + case VER_SLOT_06: + this->tlmWrite_CustomVersion07(verId_db[custom_slot]); + break; + case VER_SLOT_07: + this->tlmWrite_CustomVersion08(verId_db[custom_slot]); + break; + case VER_SLOT_08: + this->tlmWrite_CustomVersion09(verId_db[custom_slot]); + break; + case VER_SLOT_09: + this->tlmWrite_CustomVersion10(verId_db[custom_slot]); + break; + default: + // There are only 10 custom slots available + FW_ASSERT(0, custom_slot); + break; + } } } } diff --git a/Svc/Version/docs/sdd.md b/Svc/Version/docs/sdd.md index 6fe1b50bd1..1388c2bd07 100644 --- a/Svc/Version/docs/sdd.md +++ b/Svc/Version/docs/sdd.md @@ -9,18 +9,18 @@ Tracks versions for framework,project, libraries and user defined project specif |SVC-VERSION-001|`Svc::Version` upon startup shall generate an event and a telemetry channel with version for framework.| This is to provide transparency on framework version being used| |SVC-VERSION-002|`Svc::Version` upon startup shall generate an event and a telemetry channel with version for project | This is to provide transparency on project version being used| |SVC-VERSION-003|`Svc::Version` upon startup shall generate events and telemetry channels (upto 10) with versions for library.| Transparency on different library versions| -|SVC-VERSION-004|`Svc::Version` upon startup shall make verbosity on custom versions configurable.| Transparency on different library versions| +|SVC-VERSION-004|`Svc::Version` upon startup shall make verbosity on custom versions configurable.| Verbosity will enable/disable the channel generation but will not effect the event generation| |SVC-VERSION-005|`Svc::Version` shall provide a ground command to request events and telemetry on framework version| Accessibility on demand| |SVC-VERSION-006|`Svc::Version` shall provide a ground command to request events and telemetry on project version| Accessibility on demand| |SVC-VERSION-007|`Svc::Version` shall provide a ground command to request events and telemetry channels (upto 10) on library versions| Accessibility on demand| -|SVC-VERSION-008|`Svc::Version` shall provide a ground command to request events and telemetry channels (upto 10) on custom versions| Accessibility on demand| +|SVC-VERSION-008|`Svc::Version` shall provide a ground command to request events and telemetry channels (upto 10) on custom versions| Accessibility on demand. Verbosity configuration will determine the channel generation| |SVC-VERSION-009|`Svc::Version` shall provide a ground command to enable/disable verbosity on custom versions| Accessibility on demand| |SVC-VERSION-010|`Svc::Version` shall provide a telemetry channel on framework version| Accessibility to versions being used| |SVC-VERSION-011|`Svc::Version` shall provide a telemetry channel on project version| Accessibility to versions being used| |SVC-VERSION-012|`Svc::Version` shall provide upto 10 telemetry channels on library versions| Accessibility to versions being used| -|SVC-VERSION-013|`Svc::Version` shall provide upto 10 telemetry channels on custom versions| Accessibility to versions being used| -|SVC-VERSION-014|`Svc::Version` shall provide an interface for other components to set custom versions.| Enables projects to set hardware and FPGA versions, say, as needed. Also generates Events/TLM| -|SVC-VERSION-015|`Svc::Version` shall provide an interface for other components to get custom versions.| Also generates Events/TLM| +|SVC-VERSION-013|`Svc::Version` shall provide upto 10 telemetry channels on custom versions| Accessibility to versions being used. Only accessible if verbosity is enabled| +|SVC-VERSION-014|`Svc::Version` shall provide an interface for other components to set custom versions.| Enables projects to set hardware and FPGA versions, say, as needed. Also generates Events/TLM based on verbosity configuration| +|SVC-VERSION-015|`Svc::Version` shall provide an interface for other components to get custom versions.| Also generates Events/TLM based on verbosity configuration| ## Emitting Versions on Start-Up diff --git a/Svc/Version/test/ut/VersionTester.cpp b/Svc/Version/test/ut/VersionTester.cpp index 7fa4f40f01..edc114128d 100644 --- a/Svc/Version/test/ut/VersionTester.cpp +++ b/Svc/Version/test/ut/VersionTester.cpp @@ -85,13 +85,35 @@ void VersionTester ::test_enable() { this->sendCmd_ENABLE(0, cmd_seq, VersionEnabled::DISABLED); ASSERT_CMD_RESPONSE(0, 0, 9, Fw::CmdResponse::OK); VersionTester::test_setVer(false); - ASSERT_EVENTS_CustomVersions_SIZE(0); + //When verbosity is disabled, events are still generated but not EHAs + ASSERT_EVENTS_CustomVersions_SIZE(10); + ASSERT_TLM_CustomVersion01_SIZE(0); + ASSERT_TLM_CustomVersion02_SIZE(0); + ASSERT_TLM_CustomVersion03_SIZE(0); + ASSERT_TLM_CustomVersion04_SIZE(0); + ASSERT_TLM_CustomVersion05_SIZE(0); + ASSERT_TLM_CustomVersion06_SIZE(0); + ASSERT_TLM_CustomVersion07_SIZE(0); + ASSERT_TLM_CustomVersion08_SIZE(0); + ASSERT_TLM_CustomVersion09_SIZE(0); + ASSERT_TLM_CustomVersion10_SIZE(0); cmd_seq = 9; this->sendCmd_ENABLE(0, cmd_seq, VersionEnabled::ENABLED); ASSERT_CMD_RESPONSE(0, 0, 9, Fw::CmdResponse::OK); VersionTester::test_setVer(true); + //When verbosity is enabled, events and EHAs are generated ASSERT_EVENTS_CustomVersions_SIZE(10); + ASSERT_TLM_CustomVersion01_SIZE(1); + ASSERT_TLM_CustomVersion02_SIZE(1); + ASSERT_TLM_CustomVersion03_SIZE(1); + ASSERT_TLM_CustomVersion04_SIZE(1); + ASSERT_TLM_CustomVersion05_SIZE(1); + ASSERT_TLM_CustomVersion06_SIZE(1); + ASSERT_TLM_CustomVersion07_SIZE(1); + ASSERT_TLM_CustomVersion08_SIZE(1); + ASSERT_TLM_CustomVersion09_SIZE(1); + ASSERT_TLM_CustomVersion10_SIZE(1); } void VersionTester ::test_versions() { From 3b6e3c5d5b97541a41d8024f4803b7a5d5c33017 Mon Sep 17 00:00:00 2001 From: Jack White Date: Wed, 18 Sep 2024 03:02:20 +0200 Subject: [PATCH 3/5] Improvement of Doxycomments in the CCSDS FDP checksum class (#2855) * White: Reinstated a formerly broken link in the contributions readme with the new URL * White: Updated several Doxycomments for clarity in the CFDP Checksum class. * whitej: Switched block comment to collection line comments in CFDP Checksum Doxycomments --------- Co-authored-by: Jack White --- CFDP/Checksum/Checksum.hpp | 61 ++++++++++++++++++++++++++++++-------- CONTRIBUTING.md | 2 +- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/CFDP/Checksum/Checksum.hpp b/CFDP/Checksum/Checksum.hpp index 711c3af6de..d12357354a 100644 --- a/CFDP/Checksum/Checksum.hpp +++ b/CFDP/Checksum/Checksum.hpp @@ -18,8 +18,38 @@ namespace CFDP { //! \class Checksum - //! \brief Class representing a CFDP checksum + //! \brief Class representing a 32-bit checksum as mandated by the CCSDS File + //! Delivery Protocol. //! + //! This checksum is calculated by update of an existing 32-bit value + //! with the "next" 32-bit string drawn from the file data. Beginning + //! at the start of the file, a 4-byte window moves up the file by four + //! bytes per update. The update itself replaces the existing checksum + //! with the byte-wise sum of the existing checksum and the file data + //! contained in the window. Overflows in the addition are permitted + //! and the carry discarded. + //! + //! If an update is to be made beginning at an offset into the file + //! which is not aligned to a 4-byte boundary, the window is treated + //! as beginning at the last 4-byte boundary, but is left-zero-padded. + //! Similarly, where the file data for an update ends on an unaligned + //! byte, the window extends up to the next boundary and is + //! right-zero-padded. + //! + //! ## Example + //! + //! For buffer 0xDE 0xAD 0xBE 0xEF 0xCA 0xFE and initial zero checksum: + //! + //! ------------------------------------ Update 1 + //! Window 0xDE 0xAD 0xBE 0xEF + //! Checksum 0xDEADBEEF + //! + //! ------------------------------------ Update 2 + //! Window 0xCA 0xFE + //! Checksum 0xDEADBEEF+ + //! 0xCAFE0000 + //! ---------- + //! 0xA8ABBEEF <- Final value class Checksum { public: @@ -34,16 +64,16 @@ namespace CFDP { // Construction and destruction // ---------------------------------------------------------------------- - //! Construct a fresh Checksum object + //! Construct a fresh Checksum object. Checksum(); - //! Construct a Checksum object and initialize it with a value + //! Construct a Checksum object and initialize it with a value. Checksum(const U32 value); - //! Copy a Checksum object + //! Copy a Checksum object. Checksum(const Checksum &original); - //! Destroy a Checksum object + //! Destroy a Checksum object. ~Checksum(); public: @@ -52,20 +82,25 @@ namespace CFDP { // Public instance methods // ---------------------------------------------------------------------- - //! Assign checksum to this + //! Assign checksum to this. Checksum& operator=(const Checksum& checksum); - //! Compare checksum and this for equality + //! Compare checksum and this for equality. bool operator==(const Checksum& checksum) const; - //! Compare checksum and this for inequality + //! Compare checksum and this for inequality. bool operator!=(const Checksum& checksum) const; - //! Update the checksum value by accumulating the words in the data - void update( - const U8 *const data, //!< The data - const U32 offset, //!< The offset of the start of the data, relative to the start of the file - const U32 length //!< The length of the data in bytes + //! Update the checksum value by accumulating words in the given data. + //! + //! \important The data and data-length passed to this method are specifically + //! those over which the update is made, rather than the entire + //! file. Typically, therefore, `data` will be a pointer to the + //! byte given by the offset, e.g. `&file_buffer[offset]`. + //! + void update(const U8* const data, //!< Beginning of the data over which to update. + const U32 offset, //!< Offset into the file at which the data begins. + const U32 length //!< Length of the update data in bytes. ); //! Get the checksum value diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 57c348b9f1..408a242e16 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -74,7 +74,7 @@ Contributors to the [fprime](https://github.com/nasa/fprime) repository should u F´ follows a standard git flow development model. Developers should start with a [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) of one of the F´ repositories and then develop according to [git flow](https://docs.github.com/en/get-started/quickstart/github-flow). Remember to add an -upstream remote to your fork such that you may fetch the latest changes. +[upstream remote](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/configuring-a-remote-repository-for-a-fork) to your fork such that you may fetch the latest changes. For each contribution, developers should first fetch the latest changes from upstream. Then create a new branch off `devel` and submit back to F´ using a pull request as described above. From 6a921a1ef1df30d16874aff549ba5672029b7484 Mon Sep 17 00:00:00 2001 From: David Kessler Date: Tue, 17 Sep 2024 21:09:32 -0400 Subject: [PATCH 4/5] Fix ComQueue Does Not Deallocate Buffers on Overflow (#2853) * Added Fw.BufferSend deallocate port to `ComQueue` * Updated FPP to v2.2.0a2 * Change `buffQueueIn` port from `drop` to `hook` * Added queue overflow hook method implementation * Added return status to `ComQueue::enqueue()` * `Fw::Buffer` is now deallocated on queue overflow * Enabled `UT_AUTO_HELPERS` in `ComQueue` UT build * Updated `ComQueue` UTs * Explicitly discard `enqueue()` return status * Replaced `overflowhook()` call with `deallocate()` * Fixed comment style * Renamed UT test case * Added internal queue overflow UT * Added assertion on `overflowHook()` argument --- Svc/ComQueue/CMakeLists.txt | 2 + Svc/ComQueue/ComQueue.cpp | 32 +++++-- Svc/ComQueue/ComQueue.fpp | 7 +- Svc/ComQueue/ComQueue.hpp | 12 ++- Svc/ComQueue/docs/sdd.md | 1 + Svc/ComQueue/test/ut/ComQueueTestMain.cpp | 9 +- Svc/ComQueue/test/ut/ComQueueTester.cpp | 108 ++++++++++++---------- Svc/ComQueue/test/ut/ComQueueTester.hpp | 22 ++++- requirements.txt | 26 +++--- 9 files changed, 142 insertions(+), 77 deletions(-) diff --git a/Svc/ComQueue/CMakeLists.txt b/Svc/ComQueue/CMakeLists.txt index c1733c668b..5e3d657fd3 100644 --- a/Svc/ComQueue/CMakeLists.txt +++ b/Svc/ComQueue/CMakeLists.txt @@ -23,4 +23,6 @@ set(UT_SOURCE_FILES "${CMAKE_CURRENT_LIST_DIR}/test/ut/ComQueueTestMain.cpp" "${CMAKE_CURRENT_LIST_DIR}/test/ut/ComQueueTester.cpp" ) + +set(UT_AUTO_HELPERS ON) register_fprime_ut() diff --git a/Svc/ComQueue/ComQueue.cpp b/Svc/ComQueue/ComQueue.cpp index a1fb1c40c2..1a0ff4d4c8 100644 --- a/Svc/ComQueue/ComQueue.cpp +++ b/Svc/ComQueue/ComQueue.cpp @@ -131,7 +131,7 @@ void ComQueue::configure(QueueConfigurationTable queueConfig, void ComQueue::comQueueIn_handler(const NATIVE_INT_TYPE portNum, Fw::ComBuffer& data, U32 context) { // Ensure that the port number of comQueueIn is consistent with the expectation FW_ASSERT(portNum >= 0 && portNum < COM_PORT_COUNT, portNum); - this->enqueue(portNum, QueueType::COM_QUEUE, reinterpret_cast(&data), sizeof(Fw::ComBuffer)); + (void)this->enqueue(portNum, QueueType::COM_QUEUE, reinterpret_cast(&data), sizeof(Fw::ComBuffer)); } void ComQueue::buffQueueIn_handler(const NATIVE_INT_TYPE portNum, Fw::Buffer& fwBuffer) { @@ -139,7 +139,11 @@ void ComQueue::buffQueueIn_handler(const NATIVE_INT_TYPE portNum, Fw::Buffer& fw // Ensure that the port number of buffQueueIn is consistent with the expectation FW_ASSERT(portNum >= 0 && portNum < BUFFER_PORT_COUNT, portNum); FW_ASSERT(queueNum < TOTAL_PORT_COUNT); - this->enqueue(queueNum, QueueType::BUFFER_QUEUE, reinterpret_cast(&fwBuffer), sizeof(Fw::Buffer)); + bool status = + this->enqueue(queueNum, QueueType::BUFFER_QUEUE, reinterpret_cast(&fwBuffer), sizeof(Fw::Buffer)); + if (!status) { + this->deallocate_out(portNum, fwBuffer); + } } void ComQueue::comStatusIn_handler(const NATIVE_INT_TYPE portNum, Fw::Success& condition) { @@ -181,29 +185,45 @@ void ComQueue::run_handler(const NATIVE_INT_TYPE portNum, U32 context) { this->tlmWrite_buffQueueDepth(buffQueueDepth); } +// ---------------------------------------------------------------------- +// Hook implementations for typed async input ports +// ---------------------------------------------------------------------- + +void ComQueue::buffQueueIn_overflowHook(FwIndexType portNum, Fw::Buffer& fwBuffer) { + FW_ASSERT(portNum >= 0 && portNum < BUFFER_PORT_COUNT, portNum); + this->deallocate_out(portNum, fwBuffer); +} + // ---------------------------------------------------------------------- // Private helper methods // ---------------------------------------------------------------------- -void ComQueue::enqueue(const FwIndexType queueNum, QueueType queueType, const U8* data, const FwSizeType size) { +bool ComQueue::enqueue(const FwIndexType queueNum, QueueType queueType, const U8* data, const FwSizeType size) { // Enqueue the given message onto the matching queue. When no space is available then emit the queue overflow event, // set the appropriate throttle, and move on. Will assert if passed a message for a depth 0 queue. const FwSizeType expectedSize = (queueType == QueueType::COM_QUEUE) ? sizeof(Fw::ComBuffer) : sizeof(Fw::Buffer); const FwIndexType portNum = queueNum - ((queueType == QueueType::COM_QUEUE) ? 0 : COM_PORT_COUNT); + bool rvStatus = true; FW_ASSERT( expectedSize == size, static_cast(size), static_cast(expectedSize)); FW_ASSERT(portNum >= 0, portNum); Fw::SerializeStatus status = this->m_queues[queueNum].enqueue(data, size); - if (status == Fw::FW_SERIALIZE_NO_ROOM_LEFT && !this->m_throttle[queueNum]) { - this->log_WARNING_HI_QueueOverflow(queueType, static_cast(portNum)); - this->m_throttle[queueNum] = true; + if (status == Fw::FW_SERIALIZE_NO_ROOM_LEFT) { + if (!this->m_throttle[queueNum]) { + this->log_WARNING_HI_QueueOverflow(queueType, static_cast(portNum)); + this->m_throttle[queueNum] = true; + } + + rvStatus = false; } // When the component is already in READY state process the queue to send out the next available message immediately if (this->m_state == READY) { this->processQueue(); } + + return rvStatus; } void ComQueue::sendComBuffer(Fw::ComBuffer& comBuffer) { diff --git a/Svc/ComQueue/ComQueue.fpp b/Svc/ComQueue/ComQueue.fpp index 43cda64368..a45ec081a5 100644 --- a/Svc/ComQueue/ComQueue.fpp +++ b/Svc/ComQueue/ComQueue.fpp @@ -4,7 +4,7 @@ module Svc { @ Array of queue depths for Fw::Com types array ComQueueDepth = [ComQueueComPorts] U32 - + @ Array of queue depths for Fw::Buffer types array BuffQueueDepth = [ComQueueBufferPorts] U32 @@ -22,6 +22,9 @@ module Svc { @ Fw::Buffer output port output port buffQueueSend: Fw.BufferSend + @ Port for deallocating Fw::Buffer on queue overflow + output port deallocate: Fw.BufferSend + @ Port for receiving the status signal async input port comStatusIn: Fw.SuccessCondition @@ -29,7 +32,7 @@ module Svc { async input port comQueueIn: [ComQueueComPorts] Fw.Com drop @ Port array for receiving Fw::Buffers - async input port buffQueueIn: [ComQueueBufferPorts] Fw.BufferSend drop + async input port buffQueueIn: [ComQueueBufferPorts] Fw.BufferSend hook @ Port for scheduling telemetry output async input port run: Svc.Sched drop diff --git a/Svc/ComQueue/ComQueue.hpp b/Svc/ComQueue/ComQueue.hpp index 550be00c98..40e1e0bd7d 100644 --- a/Svc/ComQueue/ComQueue.hpp +++ b/Svc/ComQueue/ComQueue.hpp @@ -151,13 +151,23 @@ class ComQueue : public ComQueueComponentBase { U32 context /*!component.m_queue.getQueueSize(); + QueueType overflow_type; + NATIVE_INT_TYPE portNum; + + // fill the queue + for (NATIVE_INT_TYPE msgCount = 0; msgCount < msgCountMax; msgCount++) { + sendByQueueNumber(buffer, queueNum, portNum, overflow_type); + ASSERT_EQ(overflow_type, QueueType::BUFFER_QUEUE); + } + + // send one more to overflow the queue + sendByQueueNumber(buffer, queueNum, portNum, overflow_type); + + ASSERT_from_deallocate_SIZE(1); + ASSERT_from_deallocate(0, buffer); + + // send another + sendByQueueNumber(buffer, queueNum, portNum, overflow_type); + + ASSERT_from_deallocate_SIZE(2); + ASSERT_from_deallocate(0, buffer); + ASSERT_from_deallocate(1, buffer); + + component.cleanup(); +} + void ComQueueTester ::testReadyFirst() { U8 data[BUFFER_LENGTH] = {0xde, 0xad, 0xbe}; Fw::ComBuffer comBuffer(&data[0], sizeof(data)); @@ -283,45 +330,4 @@ void ComQueueTester ::from_comQueueSend_handler(const NATIVE_INT_TYPE portNum, F // Helper methods // ---------------------------------------------------------------------- -void ComQueueTester ::connectPorts() { - // buffQueueIn - for (NATIVE_INT_TYPE i = 0; i < ComQueue::BUFFER_PORT_COUNT; ++i) { - this->connect_to_buffQueueIn(i, this->component.get_buffQueueIn_InputPort(i)); - } - - // comQueueIn - for (NATIVE_INT_TYPE i = 0; i < ComQueue::COM_PORT_COUNT; ++i) { - this->connect_to_comQueueIn(i, this->component.get_comQueueIn_InputPort(i)); - } - - // comStatusIn - this->connect_to_comStatusIn(0, this->component.get_comStatusIn_InputPort(0)); - - // run - this->connect_to_run(0, this->component.get_run_InputPort(0)); - - // Log - this->component.set_Log_OutputPort(0, this->get_from_Log(0)); - - // LogText - this->component.set_LogText_OutputPort(0, this->get_from_LogText(0)); - - // Time - this->component.set_Time_OutputPort(0, this->get_from_Time(0)); - - // Tlm - this->component.set_Tlm_OutputPort(0, this->get_from_Tlm(0)); - - // buffQueueSend - this->component.set_buffQueueSend_OutputPort(0, this->get_from_buffQueueSend(0)); - - // comQueueSend - this->component.set_comQueueSend_OutputPort(0, this->get_from_comQueueSend(0)); -} - -void ComQueueTester ::initComponents() { - this->init(); - this->component.init(QUEUE_DEPTH, INSTANCE); -} - } // end namespace Svc diff --git a/Svc/ComQueue/test/ut/ComQueueTester.hpp b/Svc/ComQueue/test/ut/ComQueueTester.hpp index 5149edf8c8..41da0db4fb 100644 --- a/Svc/ComQueue/test/ut/ComQueueTester.hpp +++ b/Svc/ComQueue/test/ut/ComQueueTester.hpp @@ -14,6 +14,19 @@ namespace Svc { class ComQueueTester : public ComQueueGTestBase { + + public: + + // ---------------------------------------------------------------------- + // Constants + // ---------------------------------------------------------------------- + + // Instance ID supplied to the component instance under test + static const NATIVE_INT_TYPE TEST_INSTANCE_ID = 0; + + // Queue depth supplied to the component instance under test + static const NATIVE_INT_TYPE TEST_INSTANCE_QUEUE_DEPTH = 10; + private: // ---------------------------------------------------------------------- // Construction and destruction @@ -38,7 +51,10 @@ class ComQueueTester : public ComQueueGTestBase { // ---------------------------------------------------------------------- void configure(); - void sendByQueueNumber(NATIVE_INT_TYPE queueNumber, NATIVE_INT_TYPE& portNum, QueueType& queueType); + void sendByQueueNumber(Fw::Buffer& buffer, + NATIVE_INT_TYPE queueNumber, + NATIVE_INT_TYPE& portNum, + QueueType& queueType); void emitOne(); @@ -57,7 +73,9 @@ class ComQueueTester : public ComQueueGTestBase { void testPrioritySend(); - void testQueueOverflow(); + void testExternalQueueOverflow(); + + void testInternalQueueOverflow(); void testReadyFirst(); diff --git a/requirements.txt b/requirements.txt index 0d1607c003..e051503b91 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,20 +18,20 @@ fprime-fpl-convert-xml==1.0.3 fprime-fpl-extract-xml==1.0.3 fprime-fpl-layout==1.0.3 fprime-fpl-write-pic==1.0.3 -fprime-fpp-check==2.2.0a1 -fprime-fpp-depend==2.2.0a1 -fprime-fpp-filenames==2.2.0a1 -fprime-fpp-format==2.2.0a1 -fprime-fpp-from-xml==2.2.0a1 -fprime-fpp-locate-defs==2.2.0a1 -fprime-fpp-locate-uses==2.2.0a1 -fprime-fpp-syntax==2.2.0a1 -fprime-fpp-to-cpp==2.2.0a1 -fprime-fpp-to-dict==2.2.0a1 -fprime-fpp-to-json==2.2.0a1 -fprime-fpp-to-xml==2.2.0a1 +fprime-fpp-check==2.2.0a2 +fprime-fpp-depend==2.2.0a2 +fprime-fpp-filenames==2.2.0a2 +fprime-fpp-format==2.2.0a2 +fprime-fpp-from-xml==2.2.0a2 +fprime-fpp-locate-defs==2.2.0a2 +fprime-fpp-locate-uses==2.2.0a2 +fprime-fpp-syntax==2.2.0a2 +fprime-fpp-to-cpp==2.2.0a2 +fprime-fpp-to-dict==2.2.0a2 +fprime-fpp-to-json==2.2.0a2 +fprime-fpp-to-xml==2.2.0a2 fprime-gds==3.4.4a3 -fprime-tools==3.4.4 +fprime-tools==v3.4.5a1 fprime-visual==1.0.2 gcovr==6.0 idna==3.4 From 3f8830b38beb3429b9f4b97c5b3fba929475617f Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 18 Sep 2024 10:47:58 -0700 Subject: [PATCH 5/5] Fix random UT failures on PosixFile (#2862) * Fix Random UT Failure: PosixFile (#2835) * Fix UT failing FinalizeCrc test when crc calculation miss match. Miss match due to file crc and shadow being out of sync when both are not reinialized at the same time. * Update File::open in File.ccp to match shadow operation in FileRules.cpp to only reset crc when status is OP_OK. * Update FileRules.cpp to print out rule name during test. * Remove changing file mode in shadow_open() in FileRules.cpp to OPEN_NO_MODE when status is not OP_OK. * Fix Random UT Failure: PosixFile (#2835) * Fix additional issue where randomly generate file name may be too large for POSIX. * Fix Random UT Failure: PosixFile (#2835) * Fix preallocate test from picking 0 for length, which causes unintentional error in preallocate cmd. * Fix seek() in SyntheticFileSystem and assert_file_seek() to allow for offset being 0. --- Os/Posix/test/ut/PosixFileTests.cpp | 6 +++--- Os/test/ut/file/FileRules.cpp | 4 ++-- Os/test/ut/file/SyntheticFileSystem.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Os/Posix/test/ut/PosixFileTests.cpp b/Os/Posix/test/ut/PosixFileTests.cpp index d75c5bc40f..55cc7dfc11 100644 --- a/Os/Posix/test/ut/PosixFileTests.cpp +++ b/Os/Posix/test/ut/PosixFileTests.cpp @@ -33,8 +33,8 @@ bool check_permissions(const char* path, int permission) { //! std::shared_ptr get_test_filename(bool random) { const char* filename = TEST_FILE; - char full_buffer[PATH_MAX]; - char buffer[PATH_MAX - sizeof(BASE_PATH)]; + char full_buffer[_POSIX_PATH_MAX]; + char buffer[_POSIX_PATH_MAX - sizeof(BASE_PATH)]; // When random, select random characters if (random) { filename = buffer; @@ -47,7 +47,7 @@ std::shared_ptr get_test_filename(bool random) { } buffer[i] = 0; // Terminate random string } - (void) snprintf(full_buffer, PATH_MAX, "%s/%s", BASE_PATH, filename); + (void) snprintf(full_buffer, _POSIX_PATH_MAX, "%s/%s", BASE_PATH, filename); // Create a shared pointer wrapping our filename buffer std::shared_ptr pointer(new std::string(full_buffer), std::default_delete()); return pointer; diff --git a/Os/test/ut/file/FileRules.cpp b/Os/test/ut/file/FileRules.cpp index e708ec9569..39b0399b61 100644 --- a/Os/test/ut/file/FileRules.cpp +++ b/Os/test/ut/file/FileRules.cpp @@ -231,7 +231,7 @@ void Os::Test::File::Tester::assert_file_seek(const FwSignedSizeType original_po ASSERT_EQ(this->m_shadow.position(shadow_position), Os::File::Status::OP_OK); const FwSignedSizeType expected_offset = (absolute) ? seek_desired : (original_position + seek_desired); - if (expected_offset > 0) { + if (expected_offset >= 0) { ASSERT_EQ(new_position, expected_offset); } else { ASSERT_EQ(new_position, original_position); @@ -497,7 +497,7 @@ void Os::Test::File::Tester::Preallocate::action( state.assert_file_consistent(); FileState original_file_state = state.current_file_state(); FwSignedSizeType offset = static_cast(STest::Pick::lowerUpper(0, FILE_DATA_MAXIMUM)); - FwSignedSizeType length = static_cast(STest::Pick::lowerUpper(0, FILE_DATA_MAXIMUM)); + FwSignedSizeType length = static_cast(STest::Pick::lowerUpper(1, FILE_DATA_MAXIMUM)); Os::File::Status status = state.m_file.preallocate(offset, length); ASSERT_EQ(Os::File::Status::OP_OK, status); state.shadow_preallocate(offset, length); diff --git a/Os/test/ut/file/SyntheticFileSystem.cpp b/Os/test/ut/file/SyntheticFileSystem.cpp index b81f7c6eae..5bcb59cbf4 100644 --- a/Os/test/ut/file/SyntheticFileSystem.cpp +++ b/Os/test/ut/file/SyntheticFileSystem.cpp @@ -191,7 +191,7 @@ Os::File::Status SyntheticFile::seek(const FwSignedSizeType offset, const SeekTy status = Os::File::Status::NOT_OPENED; } else { FwSignedSizeType new_offset = (absolute) ? offset : (offset + this->m_data->m_pointer); - if (new_offset > 0) { + if (new_offset >= 0) { this->m_data->m_pointer = new_offset; } else { status = Os::File::Status::INVALID_ARGUMENT;