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 0000000000..a85d7c7f45 Binary files /dev/null and b/Svc/SeqDispatcher/docs/seq_dispatcher_model.png differ diff --git a/Svc/SeqDispatcher/test/ut/SeqDispatcherTestMain.cpp b/Svc/SeqDispatcher/test/ut/SeqDispatcherTestMain.cpp new file mode 100644 index 0000000000..b4362ef055 --- /dev/null +++ b/Svc/SeqDispatcher/test/ut/SeqDispatcherTestMain.cpp @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------------- +// TestMain.cpp +// ---------------------------------------------------------------------- + +#include "SeqDispatcherTester.hpp" + +TEST(Nominal, testDispatch) { + Svc::SeqDispatcherTester tester; + tester.testDispatch(); +} + +TEST(Nominal, testLogStatus) { + Svc::SeqDispatcherTester tester; + tester.testLogStatus(); +} + + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Svc/SeqDispatcher/test/ut/SeqDispatcherTester.cpp b/Svc/SeqDispatcher/test/ut/SeqDispatcherTester.cpp new file mode 100644 index 0000000000..261f5a275c --- /dev/null +++ b/Svc/SeqDispatcher/test/ut/SeqDispatcherTester.cpp @@ -0,0 +1,92 @@ +// ====================================================================== +// \title SeqDispatcher.hpp +// \author zimri.leisher +// \brief cpp file for SeqDispatcher test harness implementation class +// ====================================================================== + +#include "SeqDispatcherTester.hpp" + +namespace Svc{ + +// ---------------------------------------------------------------------- +// Construction and destruction +// ---------------------------------------------------------------------- + +SeqDispatcherTester ::SeqDispatcherTester() + : SeqDispatcherGTestBase("SeqDispatcherTester", SeqDispatcherTester::MAX_HISTORY_SIZE), + component("SeqDispatcher") { + this->connectPorts(); + 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