GCC Code Coverage Report


Directory: /home/runner/work/fprime/fprime/Svc/SeqDispatcher/
File: SeqDispatcher.cpp
Date: 2026-09-03 22:13:53
Exec Total Coverage
Lines: 0 106 0.0%
Functions: 0 12 0.0%
Branches: 0 109 0.0%

Line Branch Exec Source
1 // ======================================================================
2 // \title SeqDispatcher.cpp
3 // \author zimri.leisher
4 // \brief cpp file for SeqDispatcher component implementation class
5 // ======================================================================
6
7 #include <Svc/SeqDispatcher/SeqDispatcher.hpp>
8
9 namespace Svc {
10
11 // ----------------------------------------------------------------------
12 // Construction, initialization, and destruction
13 // ----------------------------------------------------------------------
14
15 SeqDispatcher ::SeqDispatcher(const char* const compName) : SeqDispatcherComponentBase(compName) {}
16
17 SeqDispatcher ::~SeqDispatcher() {}
18
19 FwIndexType SeqDispatcher::getNextAvailableSequencerIdx() {
20 for (FwIndexType i = 0; i < SeqDispatcherSequencerPorts; i++) {
21 if (this->isConnected_seqRunOut_OutputPort(i) &&
22 this->m_entryTable[i].state == SeqDispatcher_CmdSequencerState::AVAILABLE) {
23 return i;
24 }
25 }
26 return -1;
27 }
28
29 void SeqDispatcher::runSequence(FwIndexType sequencerIdx,
30 const Fw::ConstStringBase& fileName,
31 BlockState block,
32 const Svc::SeqArgs& args) {
33 // this function is only designed for internal usage
34 // we can guarantee it cannot be called with input that would fail
35 FW_ASSERT(sequencerIdx >= 0 && sequencerIdx < SeqDispatcherSequencerPorts,
36 static_cast<FwAssertArgType>(sequencerIdx));
37 FW_ASSERT(this->isConnected_seqRunOut_OutputPort(sequencerIdx));
38 FW_ASSERT(this->m_entryTable[sequencerIdx].state == SeqDispatcher_CmdSequencerState::AVAILABLE,
39 static_cast<FwAssertArgType>(this->m_entryTable[sequencerIdx].state));
40
41 if (block == BlockState::NO_BLOCK) {
42 this->m_entryTable[sequencerIdx].state = SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_NO_BLOCK;
43 } else {
44 this->m_entryTable[sequencerIdx].state = SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_BLOCK;
45 }
46
47 this->m_sequencersAvailable--;
48 this->tlmWrite_sequencersAvailable(this->m_sequencersAvailable);
49 this->m_entryTable[sequencerIdx].sequenceRunning = fileName;
50
51 this->m_dispatchedCount++;
52 this->tlmWrite_dispatchedCount(this->m_dispatchedCount);
53 this->seqRunOut_out(sequencerIdx, this->m_entryTable[sequencerIdx].sequenceRunning, args);
54 }
55
56 void SeqDispatcher::seqStartIn_handler(FwIndexType portNum, //!< The port number
57 const Fw::StringBase& fileName, //!< The sequence file name
58 const Svc::SeqArgs& args //!< Sequence arguments (not currently used)
59 ) {
60 (void)args; // Suppress unused parameter warning
61 FW_ASSERT(portNum >= 0 && portNum < SeqDispatcherSequencerPorts, static_cast<FwAssertArgType>(portNum));
62 if (this->m_entryTable[portNum].state == SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_BLOCK ||
63 this->m_entryTable[portNum].state == SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_NO_BLOCK) {
64 // we were aware of this sequencer running a sequence
65 if (this->m_entryTable[portNum].sequenceRunning != fileName) {
66 // uh oh. entry table is wrong
67 // let's just update it to be correct. nothing we can do about
68 // it except raise a warning and update our state
69 this->log_WARNING_HI_ConflictingSequenceStarted(static_cast<U16>(portNum), fileName,
70 this->m_entryTable[portNum].sequenceRunning);
71 this->m_entryTable[portNum].sequenceRunning = fileName;
72 }
73 } else {
74 // we were not aware that this sequencer was running. ground must have
75 // directly commanded that specific sequencer
76
77 // warn because this may be unintentional
78 this->log_WARNING_LO_UnexpectedSequenceStarted(static_cast<U16>(portNum), fileName);
79
80 // update the state
81 this->m_entryTable[portNum].state = SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_NO_BLOCK;
82 this->m_entryTable[portNum].sequenceRunning = fileName;
83 this->m_sequencersAvailable--;
84 this->tlmWrite_sequencersAvailable(this->m_sequencersAvailable);
85 }
86 }
87
88 void SeqDispatcher::seqDoneIn_handler(FwIndexType portNum, //!< The port number
89 FwOpcodeType opCode, //!< Command Op Code
90 U32 cmdSeq, //!< Command Sequence
91 const Fw::CmdResponse& response //!< The command response argument
92 ) {
93 FW_ASSERT(portNum >= 0 && portNum < SeqDispatcherSequencerPorts, static_cast<FwAssertArgType>(portNum));
94 if (this->m_entryTable[portNum].state != SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_BLOCK &&
95 this->m_entryTable[portNum].state != SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_NO_BLOCK) {
96 // this sequencer was not running a sequence that we were aware of.
97
98 // we should have caught this in seqStartIn and updated the state
99 // accordingly, but somehow we didn't? very sad and shouldn't happen
100
101 // anyways, don't have to do anything cuz now that this seq we didn't know
102 // about is done, the sequencer is available again (which is its current
103 // state in our internal entry table already)
104 this->log_WARNING_LO_UnknownSequenceFinished(static_cast<U16>(portNum));
105 // sequencer was already counted available; don't increment again
106 this->m_entryTable[portNum].state = SeqDispatcher_CmdSequencerState::AVAILABLE;
107 this->m_entryTable[portNum].sequenceRunning = "<no seq>";
108 return;
109 } else {
110 // ok, a sequence has finished that we knew about
111 if (this->m_entryTable[portNum].state == SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_BLOCK) {
112 // we need to give a cmd response cuz some other sequence is being blocked
113 // by this
114 this->cmdResponse_out(this->m_entryTable[portNum].opCode, this->m_entryTable[portNum].cmdSeq, response);
115
116 if (response == Fw::CmdResponse::EXECUTION_ERROR) {
117 // dispatched sequence errored
118 this->m_errorCount++;
119 this->tlmWrite_errorCount(this->m_errorCount);
120 }
121 }
122 }
123
124 // all command responses mean the sequence is no longer running
125 // so component should be available
126 this->m_entryTable[portNum].state = SeqDispatcher_CmdSequencerState::AVAILABLE;
127 this->m_entryTable[portNum].sequenceRunning = "<no seq>";
128 this->m_sequencersAvailable++;
129 this->tlmWrite_sequencersAvailable(this->m_sequencersAvailable);
130 }
131
132 //! Handler for input port seqRunIn
133 void SeqDispatcher::seqRunIn_handler(FwIndexType portNum, const Fw::StringBase& fileName, const Svc::SeqArgs& args) {
134 FwIndexType idx = this->getNextAvailableSequencerIdx();
135 // no available sequencers
136 if (idx == -1) {
137 this->log_WARNING_HI_NoAvailableSequencers();
138 return;
139 }
140
141 this->runSequence(idx, fileName, BlockState::NO_BLOCK, args);
142 }
143 // ----------------------------------------------------------------------
144 // Command handler implementations
145 // ----------------------------------------------------------------------
146
147 // RUN command delegates to RUN_ARGS with empty arguments for backward compatibility
148 void SeqDispatcher ::RUN_cmdHandler(const FwOpcodeType opCode,
149 const U32 cmdSeq,
150 const Fw::CmdStringArg& fileName,
151 const BlockState& block) {
152 // Create empty args and delegate to RUN_ARGS handler
153 Svc::SeqArgs emptyArgs{0, 0};
154 this->RUN_ARGS_cmdHandler(opCode, cmdSeq, fileName, block, emptyArgs);
155 }
156
157 // RUN_ARGS command dispatches a sequence with optional arguments to the first available sequencer
158 void SeqDispatcher ::RUN_ARGS_cmdHandler(const FwOpcodeType opCode,
159 const U32 cmdSeq,
160 const Fw::CmdStringArg& fileName,
161 const BlockState& block,
162 const Svc::SeqArgs& buffer) {
163 FwIndexType idx = this->getNextAvailableSequencerIdx();
164 // no available sequencers
165 if (idx == -1) {
166 this->log_WARNING_HI_NoAvailableSequencers();
167 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
168 return;
169 }
170
171 this->runSequence(idx, fileName, block, buffer);
172
173 if (block == BlockState::NO_BLOCK) {
174 // return instantly
175 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
176 } else {
177 // otherwise don't return a response yet. just save the opCode and cmdSeq
178 // so we can return a response later
179 this->m_entryTable[idx].opCode = opCode;
180 this->m_entryTable[idx].cmdSeq = cmdSeq;
181 }
182 }
183
184 void SeqDispatcher::LOG_STATUS_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/
185 const U32 cmdSeq) { /*!< The command sequence number*/
186 for (FwIndexType idx = 0; idx < SeqDispatcherSequencerPorts; idx++) {
187 this->log_ACTIVITY_LO_LogSequencerStatus(static_cast<U16>(idx), this->m_entryTable[idx].state,
188 Fw::LogStringArg(this->m_entryTable[idx].sequenceRunning));
189 }
190 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
191 }
192
193 void SeqDispatcher::CANCEL_NAME_cmdHandler(
194 const FwOpcodeType opCode, /*!< The opcode*/
195 const U32 cmdSeq, /*!< The command sequence number*/
196 const Fw::CmdStringArg& fileName) /*!< The name of the sequence file to cancel*/ {
197 bool canceled = false;
198 for (FwIndexType idx = 0; idx < SeqDispatcherSequencerPorts; idx++) {
199 // only slots actively running the named sequence are candidates
200 const bool running = this->m_entryTable[idx].state != SeqDispatcher_CmdSequencerState::AVAILABLE;
201 if (running && this->m_entryTable[idx].sequenceRunning == fileName) {
202 if (this->isConnected_seqCancelOut_OutputPort(idx)) {
203 this->seqCancelOut_out(idx);
204 // Entry table is cleared via seqDoneIn_handler
205 this->log_ACTIVITY_HI_SequenceCanceled(static_cast<U16>(idx),
206 Fw::LogStringArg(this->m_entryTable[idx].sequenceRunning));
207 this->tlmWrite_canceledCount(++this->m_canceledCount);
208 canceled = true;
209 }
210 }
211 }
212
213 if (canceled) {
214 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
215 } else {
216 this->log_WARNING_LO_CancelSequenceNotFound(Fw::LogStringArg(fileName));
217 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
218 }
219 }
220
221 //! Broadcast a cancel to every running sequencer.
222 //! This does not exclude the caller!
223 //! A sequence issuing CANCEL_ALL will cancel itself is connected to this seqDispatcher.
224 void SeqDispatcher::CANCEL_ALL_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/
225 const U32 cmdSeq) { /*!< The command sequence number*/
226 for (FwIndexType idx = 0; idx < SeqDispatcherSequencerPorts; idx++) {
227 const bool running = this->m_entryTable[idx].state != SeqDispatcher_CmdSequencerState::AVAILABLE;
228 if (running && this->isConnected_seqCancelOut_OutputPort(idx)) {
229 this->seqCancelOut_out(idx);
230 // Entry table is cleared via seqDoneIn_handler
231 this->log_ACTIVITY_HI_SequenceCanceled(static_cast<U16>(idx),
232 Fw::LogStringArg(this->m_entryTable[idx].sequenceRunning));
233 this->tlmWrite_canceledCount(++this->m_canceledCount);
234 }
235 }
236 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
237 }
238 } // namespace Svc
239