diff --git a/Svc/CmdSequencer/CmdSequencerImpl.cpp b/Svc/CmdSequencer/CmdSequencerImpl.cpp index f105e65b984..6442ed4abe5 100644 --- a/Svc/CmdSequencer/CmdSequencerImpl.cpp +++ b/Svc/CmdSequencer/CmdSequencerImpl.cpp @@ -100,7 +100,10 @@ void CmdSequencerComponentImpl::CS_RUN_cmdHandler(FwOpcodeType opCode, if (AUTO == this->m_stepMode) { this->m_runMode = RUNNING; if (this->isConnected_seqStartOut_OutputPort(0)) { - this->seqStartOut_out(0, this->m_sequence->getStringFileName()); + // Create empty SeqArgs as placeholder + // Use parameterized constructor to ensure m_size is initialized to 0 + Svc::SeqArgs emptyArgs{0, 0}; + this->seqStartOut_out(0, this->m_sequence->getStringFileName(), emptyArgs); } this->performCmd_Step(); } @@ -162,7 +165,10 @@ void CmdSequencerComponentImpl::doSequenceRun(const Fw::StringBase& filename) { if (AUTO == this->m_stepMode) { this->m_runMode = RUNNING; if (this->isConnected_seqStartOut_OutputPort(0)) { - this->seqStartOut_out(0, this->m_sequence->getStringFileName()); + // Create empty SeqArgs as placeholder + // Use parameterized constructor to ensure m_size is initialized to 0 + Svc::SeqArgs emptyArgs{0, 0}; + this->seqStartOut_out(0, this->m_sequence->getStringFileName(), emptyArgs); } this->performCmd_Step(); } @@ -170,7 +176,8 @@ void CmdSequencerComponentImpl::doSequenceRun(const Fw::StringBase& filename) { this->log_ACTIVITY_HI_CS_PortSequenceStarted(this->m_sequence->getLogFileName()); } -void CmdSequencerComponentImpl::seqRunIn_handler(FwIndexType portNum, const Fw::StringBase& filename) { +void CmdSequencerComponentImpl::seqRunIn_handler(FwIndexType portNum, const Fw::StringBase& filename, const Svc::SeqArgs& args) { + (void)args; // Suppress unused parameter warning this->doSequenceRun(filename); } @@ -322,7 +329,9 @@ void CmdSequencerComponentImpl ::CS_START_cmdHandler(FwOpcodeType opcode, U32 cm 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()); + // Create empty SeqArgs as placeholder + Svc::SeqArgs emptyArgs{0, 0}; + this->seqStartOut_out(0, this->m_sequence->getStringFileName(), emptyArgs); } this->cmdResponse_out(opcode, cmdSeq, Fw::CmdResponse::OK); } diff --git a/Svc/CmdSequencer/CmdSequencerImpl.hpp b/Svc/CmdSequencer/CmdSequencerImpl.hpp index c10c5d01fe7..035a250f4f1 100644 --- a/Svc/CmdSequencer/CmdSequencerImpl.hpp +++ b/Svc/CmdSequencer/CmdSequencerImpl.hpp @@ -520,7 +520,8 @@ class CmdSequencerComponentImpl final : public CmdSequencerComponentBase { //! Handler for input port seqRunIn void seqRunIn_handler(FwIndexType portNum, //!< The port number - const Fw::StringBase& filename //!< The sequence file + const Fw::StringBase& filename, //!< The sequence file + const Svc::SeqArgs& args //!< Sequence arguments (not currently used) ) override; //! Handler implementation for seqDispatchIn diff --git a/Svc/CmdSequencer/test/ut/CmdSequencerTester.cpp b/Svc/CmdSequencer/test/ut/CmdSequencerTester.cpp index 59990fed6b9..c43b9d1f04d 100644 --- a/Svc/CmdSequencer/test/ut/CmdSequencerTester.cpp +++ b/Svc/CmdSequencer/test/ut/CmdSequencerTester.cpp @@ -271,7 +271,8 @@ void CmdSequencerTester ::parameterizedDataReadErrors(SequenceFiles::File& file) void CmdSequencerTester ::parameterizedNeverLoaded() { // Try to run a sequence Fw::String fArg(""); - this->invoke_to_seqRunIn(0, fArg); + Svc::SeqArgs emptyArgs{0, 0}; + this->invoke_to_seqRunIn(0, fArg, emptyArgs); this->clearAndDispatch(); // Assert seqDone response ASSERT_from_seqDone_SIZE(1); @@ -474,7 +475,8 @@ void CmdSequencerTester ::runSequence(const U32 cmdSeq, const char* const fileNa void CmdSequencerTester ::runSequenceByPortCall(const char* const fileName) { // Invoke the seqRun port Fw::String fArg(fileName); - this->invoke_to_seqRunIn(0, fArg); + Svc::SeqArgs emptyArgs{0, 0}; + this->invoke_to_seqRunIn(0, fArg, emptyArgs); this->clearAndDispatch(); // Assert no command response ASSERT_CMD_RESPONSE_SIZE(0); @@ -500,7 +502,8 @@ void CmdSequencerTester ::runSequenceByFileDispatcherPortCall(const char* const void CmdSequencerTester ::runLoadedSequence() { // Invoke the port Fw::String fArg(""); - this->invoke_to_seqRunIn(0, fArg); + Svc::SeqArgs emptyArgs{0, 0}; + this->invoke_to_seqRunIn(0, fArg, emptyArgs); this->clearAndDispatch(); // Assert no command response ASSERT_CMD_RESPONSE_SIZE(0); @@ -530,7 +533,8 @@ void CmdSequencerTester ::startNewSequence(const char* const fileName) { ASSERT_EVENTS_CS_InvalidMode_SIZE(1); // Invoke sequence port Fw::String fArg(fileName); - this->invoke_to_seqRunIn(0, fArg); + Svc::SeqArgs emptyArgs{0, 0}; + this->invoke_to_seqRunIn(0, fArg, emptyArgs); this->clearAndDispatch(); // Assert response on seqDone ASSERT_from_seqDone_SIZE(1); diff --git a/Svc/CmdSequencer/test/ut/ImmediateBase.cpp b/Svc/CmdSequencer/test/ut/ImmediateBase.cpp index 7a483082971..20a1d0cfefc 100644 --- a/Svc/CmdSequencer/test/ut/ImmediateBase.cpp +++ b/Svc/CmdSequencer/test/ut/ImmediateBase.cpp @@ -166,7 +166,8 @@ void CmdSequencerTester ::parameterizedLoadRunRun(SequenceFiles::File& file, con this->parameterizedAutoByPort(file, numCommands, bound); // Try to run a loaded sequence Fw::String fArg(""); - this->invoke_to_seqRunIn(0, fArg); + Svc::SeqArgs emptyArgs{0, 0}; + this->invoke_to_seqRunIn(0, fArg, emptyArgs); this->clearAndDispatch(); // Assert seqDone response ASSERT_from_seqDone_SIZE(1); diff --git a/Svc/CmdSequencer/test/ut/InvalidFiles.cpp b/Svc/CmdSequencer/test/ut/InvalidFiles.cpp index e90041dabad..71e480e8571 100644 --- a/Svc/CmdSequencer/test/ut/InvalidFiles.cpp +++ b/Svc/CmdSequencer/test/ut/InvalidFiles.cpp @@ -218,7 +218,8 @@ void CmdSequencerTester ::MissingCRC() { ASSERT_TLM_CS_Errors(0, 2); // Run the sequence by port call Fw::String fArg(file.getName()); - this->invoke_to_seqRunIn(0, fArg); + Svc::SeqArgs emptyArgs{0, 0}; + this->invoke_to_seqRunIn(0, fArg, emptyArgs); this->clearAndDispatch(); // Assert seqDone response ASSERT_from_seqDone_SIZE(1); diff --git a/Svc/FpySequencer/FpySequencer.cpp b/Svc/FpySequencer/FpySequencer.cpp index e5a09c00a87..3e0ab837977 100644 --- a/Svc/FpySequencer/FpySequencer.cpp +++ b/Svc/FpySequencer/FpySequencer.cpp @@ -23,6 +23,7 @@ FpySequencer ::FpySequencer(const char* const compName) m_sequenceBlockState(), m_savedOpCode(0), m_savedCmdSeq(0), + m_pendingSeqArgs(0, 0), m_goalState(), m_sequencesStarted(0), m_statementsDispatched(0), @@ -40,6 +41,16 @@ void FpySequencer::RUN_cmdHandler(FwOpcodeType opCode, //!< The op const Fw::CmdStringArg& fileName, //!< The name of the sequence file FpySequencer_BlockState block //!< Return command status when complete or not ) { + // Empty args and delegate to RUN_ARGS handler + Svc::SeqArgs emptyArgs{0, 0}; + this->RUN_ARGS_cmdHandler(opCode, cmdSeq, fileName, block, emptyArgs); +} + +void FpySequencer ::RUN_ARGS_cmdHandler(FwOpcodeType opCode, + U32 cmdSeq, + const Fw::CmdStringArg& fileName, + Svc::FpySequencer_BlockState block, + Svc::SeqArgs buffer) { // can only run a seq while in idle if (sequencer_getState() != State::IDLE) { this->log_WARNING_HI_InvalidCommand(static_cast(sequencer_getState())); @@ -53,6 +64,8 @@ void FpySequencer::RUN_cmdHandler(FwOpcodeType opCode, //!< The op this->m_savedCmdSeq = cmdSeq; } + // Store args for pushArgsToStack action + this->m_pendingSeqArgs = buffer; this->sequencer_sendSignal_cmd_RUN(FpySequencer_SequenceExecutionArgs(fileName, block)); // only respond if the user doesn't want us to block further execution @@ -80,6 +93,9 @@ void FpySequencer::VALIDATE_cmdHandler(FwOpcodeType opCode, //!< Th this->m_savedOpCode = opCode; this->m_savedCmdSeq = cmdSeq; + // VALIDATE command doesn't receive args via command interface, use empty SeqArgs + // Store empty args for pushArgsToStack action + this->m_pendingSeqArgs = Svc::SeqArgs{0, 0}; this->sequencer_sendSignal_cmd_VALIDATE( FpySequencer_SequenceExecutionArgs(fileName, FpySequencer_BlockState::BLOCK)); } @@ -358,14 +374,16 @@ void FpySequencer::cmdResponseIn_handler(FwIndexType portNum, //!< T } //! Handler for input port seqRunIn -void FpySequencer::seqRunIn_handler(FwIndexType portNum, const Fw::StringBase& filename) { +void FpySequencer::seqRunIn_handler(FwIndexType portNum, const Fw::StringBase& filename, const Svc::SeqArgs& args) { // can only run a seq while in idle if (sequencer_getState() != State::IDLE) { this->log_WARNING_HI_InvalidSeqRunCall(static_cast(sequencer_getState())); return; } - // seqRunIn is never blocking + // seqRunIn is never blocking - store args for pushArgsToStack action + // Args must be serialized in F' big-endian format by the caller before being sent + this->m_pendingSeqArgs = args; this->sequencer_sendSignal_cmd_RUN(FpySequencer_SequenceExecutionArgs(filename, FpySequencer_BlockState::NO_BLOCK)); } diff --git a/Svc/FpySequencer/FpySequencer.hpp b/Svc/FpySequencer/FpySequencer.hpp index 53dad8cf911..c7bffd24a0f 100644 --- a/Svc/FpySequencer/FpySequencer.hpp +++ b/Svc/FpySequencer/FpySequencer.hpp @@ -104,7 +104,7 @@ class FpySequencer : public FpySequencerComponentBase { // pushes a byte array to the top of the stack from the source array // leaves the source array unmodified // does not convert endianness - void push(U8* src, Fpy::StackSizeType size); + void push(const U8* src, Fpy::StackSizeType size); // pushes zero bytes to the stack void pushZeroes(Fpy::StackSizeType byteCount); @@ -145,6 +145,14 @@ class FpySequencer : public FpySequencerComponentBase { const Fw::CmdStringArg& fileName, //!< The name of the sequence file FpySequencer_BlockState block //!< Return command status when complete or not ) override; + + //! Handler implementation for command RUN_ARGS + void RUN_ARGS_cmdHandler(FwOpcodeType opCode, //!< The opcode + U32 cmdSeq, //!< The command sequence number + const Fw::CmdStringArg& fileName, //!< The name of the sequence file + Svc::FpySequencer_BlockState block, //!< Return command status when complete or not + Svc::SeqArgs buffer //!< Arguments to pass to the sequencer + ) override; //! Handler for command VALIDATE //! @@ -366,6 +374,14 @@ class FpySequencer : public FpySequencerComponentBase { Svc_FpySequencer_SequencerStateMachine::Signal signal //!< The signal ) override; + //! Implementation for action pushArgsToStack of state machine Svc_FpySequencer_SequencerStateMachine + //! + //! pushes sequence arguments to the stack + void Svc_FpySequencer_SequencerStateMachine_action_pushArgsToStack( + SmId smId, //!< The state machine id + Svc_FpySequencer_SequencerStateMachine::Signal signal //!< The signal + ) override; + //! Implementation for action clearBreakpoint of state machine Svc_FpySequencer_SequencerStateMachine //! //! clears the breakpoint, allowing execution of the sequence to continue @@ -470,7 +486,7 @@ class FpySequencer : public FpySequencerComponentBase { ) override; //! Handler for input port seqRunIn - void seqRunIn_handler(FwIndexType portNum, const Fw::StringBase& filename) override; + void seqRunIn_handler(FwIndexType portNum, const Fw::StringBase& filename, const Svc::SeqArgs& args) override; //! Handler for input port pingIn void pingIn_handler(FwIndexType portNum, //!< The port number @@ -600,6 +616,9 @@ class FpySequencer : public FpySequencerComponentBase { FwOpcodeType m_savedOpCode; U32 m_savedCmdSeq; + // sequence arguments to push to stack when entering RUNNING state + Svc::SeqArgs m_pendingSeqArgs; + // the goal state is the state that we're trying to reach in the sequencer // if it's RUNNING, then we should promptly go to RUNNING once we validate the // sequence. if it's VALID, we should wait after VALIDATING diff --git a/Svc/FpySequencer/FpySequencerCommands.fppi b/Svc/FpySequencer/FpySequencerCommands.fppi index b1f45bb5b07..8f047d5063e 100644 --- a/Svc/FpySequencer/FpySequencerCommands.fppi +++ b/Svc/FpySequencer/FpySequencerCommands.fppi @@ -6,25 +6,32 @@ async command RUN( ) \ opcode 0 priority 7 assert +async command RUN_ARGS( + fileName: string size FileNameStringSize @< The name of the sequence file + $block: BlockState @< Return command status when complete or not + buffer: Svc.SeqArgs @< Arguments to pass to the sequencer + ) \ + opcode 1 priority 7 assert + @ Loads and validates a sequence # prio: lower than sm sig and CANCEL async command VALIDATE( fileName: string size FileNameStringSize @< The name of the sequence file ) \ - opcode 1 priority 7 assert + opcode 2 priority 7 assert @ Must be called after VALIDATE. Runs the sequence that was validated. # prio: lower than sm sig and CANCEL async command RUN_VALIDATED( $block: BlockState @< Return command status when complete or not ) \ - opcode 2 priority 7 assert + opcode 3 priority 7 assert @ Cancels a running or validated sequence. After running CANCEL, the sequencer @ should return to IDLE # less prio than sm sig, but higher than everything else async command CANCEL() \ - opcode 3 priority 8 assert + opcode 4 priority 8 assert @ Sets the breakpoint which will pause the execution of the sequencer when @ reached, until unpaused by the CONTINUE command. Will pause just before @@ -34,31 +41,31 @@ async command SET_BREAKPOINT( stmtIdx: U32 @< The directive index to pause execution before. breakOnce: bool @< Whether or not to break only once at this breakpoint ) \ - opcode 4 priority 7 assert + opcode 5 priority 7 assert @ Pauses the execution of the sequencer, just before it is about to dispatch the next directive, @ until unpaused by the CONTINUE command, or stepped by the STEP command. This command is only valid @ substates of the RUNNING state that are not RUNNING.PAUSED. async command BREAK() \ - opcode 5 priority 7 assert + opcode 6 priority 7 assert @ Continues the automatic execution of the sequence after it has been paused. If a breakpoint is still @ set, it may pause again on that breakpoint. This command is only valid in the RUNNING.PAUSED state. async command CONTINUE() \ - opcode 6 priority 7 assert + opcode 7 priority 7 assert @ Clears the breakpoint, but does not continue executing the sequence. This command @ is valid in all states. This happens automatically when a sequence ends execution. async command CLEAR_BREAKPOINT() \ - opcode 7 priority 7 assert + opcode 8 priority 7 assert @ Dispatches and awaits the result of the next directive, or ends the sequence if no more directives remain. Returns @ to the RUNNING.PAUSED state if the directive executes successfully. This command is only valid in the RUNNING.PAUSED state. async command STEP() \ - opcode 8 priority 7 assert + opcode 9 priority 7 assert @ Writes the contents of the stack to a file. This command is only valid in the RUNNING.PAUSED state. async command DUMP_STACK_TO_FILE( fileName: string size FileNameStringSize @< The name of the output file ) \ - opcode 9 priority 7 assert \ No newline at end of file + opcode 10 priority 7 assert \ No newline at end of file diff --git a/Svc/FpySequencer/FpySequencerStack.cpp b/Svc/FpySequencer/FpySequencerStack.cpp index cde70382c13..0ddf3e707a3 100644 --- a/Svc/FpySequencer/FpySequencerStack.cpp +++ b/Svc/FpySequencer/FpySequencerStack.cpp @@ -122,7 +122,7 @@ void FpySequencer::Stack::pop(U8* dest, Fpy::StackSizeType destSize) { // pushes a byte array to the top of the stack from the source array // leaves the source array unmodified // does not convert endianness -void FpySequencer::Stack::push(U8* src, Fpy::StackSizeType srcSize) { +void FpySequencer::Stack::push(const U8* src, Fpy::StackSizeType srcSize) { FW_ASSERT(this->size + srcSize <= Fpy::MAX_STACK_SIZE, static_cast(this->size), static_cast(srcSize)); memcpy(this->top(), src, srcSize); diff --git a/Svc/FpySequencer/FpySequencerStateMachine.cpp b/Svc/FpySequencer/FpySequencerStateMachine.cpp index f8c046d0395..8e23dba6e52 100644 --- a/Svc/FpySequencer/FpySequencerStateMachine.cpp +++ b/Svc/FpySequencer/FpySequencerStateMachine.cpp @@ -255,6 +255,34 @@ void FpySequencer::Svc_FpySequencer_SequencerStateMachine_action_incrementSequen this->m_sequencesStarted++; } +//! Implementation for action pushArgsToStack of state machine Svc_FpySequencer_SequencerStateMachine +//! +//! pushes sequence arguments to the stack +void FpySequencer::Svc_FpySequencer_SequencerStateMachine_action_pushArgsToStack( + SmId smId, //!< The state machine id + Svc_FpySequencer_SequencerStateMachine::Signal signal //!< The signal +) { + const Svc::SeqArgs& args = this->m_pendingSeqArgs; + + // Early return if no arguments provided + if (args.get_size() == 0) { + return; + } + + Fpy::StackSizeType availableSpace = Fpy::MAX_STACK_SIZE - this->m_runtime.stack.size; + + if (args.get_size() > availableSpace) { + // Args too large - fail the sequence gracefully. + this->sequencer_sendSignal_result_failure(); + return; + } + + // Push args buffer to stack. Args are already serialized in big-endian format + // by F' serialization system, so no endianness conversion is needed. + this->m_runtime.stack.push(args.get_buffer(), + static_cast(args.get_size())); +} + //! Implementation for action clearSequenceFile of state machine Svc_FpySequencer_SequencerStateMachine //! //! clears all variables related to the loading/validating of the sequence file @@ -343,8 +371,8 @@ void FpySequencer::Svc_FpySequencer_SequencerStateMachine_action_report_seqStart Svc_FpySequencer_SequencerStateMachine::Signal signal //!< The signal ) { if (this->isConnected_seqStartOut_OutputPort(0)) { - // report that the sequence started to internal callers - this->seqStartOut_out(0, this->m_sequenceFilePath); + // report that the sequence started to internal callers with the actual args + this->seqStartOut_out(0, this->m_sequenceFilePath, this->m_pendingSeqArgs); } } // ---------------------------------------------------------------------- diff --git a/Svc/FpySequencer/FpySequencerStateMachine.fppi b/Svc/FpySequencer/FpySequencerStateMachine.fppi index 317723e91cf..876056456ec 100644 --- a/Svc/FpySequencer/FpySequencerStateMachine.fppi +++ b/Svc/FpySequencer/FpySequencerStateMachine.fppi @@ -169,6 +169,8 @@ state machine SequencerStateMachine { action checkStatementTimeout @ increments the m_sequencesStarted counter action incrementSequenceCounter + @ pushes sequence arguments to the stack + action pushArgsToStack @ reports that a breakpoint was hit action report_seqBroken @ sets the breakpoint to the provided args @@ -226,7 +228,7 @@ state machine SequencerStateMachine { state RUNNING { @ start with a fresh baked runtime, and tick up the @ sequence counter - entry do { resetRuntime, incrementSequenceCounter } + entry do { resetRuntime, incrementSequenceCounter, pushArgsToStack } initial enter BREAK_CHECK diff --git a/Svc/FpySequencer/test/ut/FpySequencerTestMain.cpp b/Svc/FpySequencer/test/ut/FpySequencerTestMain.cpp index 11e0cc79bdf..ac9af063716 100644 --- a/Svc/FpySequencer/test/ut/FpySequencerTestMain.cpp +++ b/Svc/FpySequencer/test/ut/FpySequencerTestMain.cpp @@ -2069,7 +2069,7 @@ TEST_F(FpySequencerTester, cmd_RUN) { ASSERT_EQ(tester_get_m_statementsDispatched(), 0); dispatchUntilState(State::RUNNING_AWAITING_STATEMENT_RESPONSE); ASSERT_from_seqStartOut_SIZE(1); - ASSERT_from_seqStartOut(0, Fw::String("test.bin")); + ASSERT_from_seqStartOut(0, Fw::String("test.bin"), Svc::SeqArgs(0, 0)); ASSERT_EQ(tester_get_m_sequencesStarted(), 1); dispatchUntilState(State::IDLE); ASSERT_EQ(tester_get_m_statementsDispatched(), 1); @@ -2086,7 +2086,7 @@ TEST_F(FpySequencerTester, cmd_RUN) { ASSERT_from_seqDoneOut_SIZE(0); dispatchUntilState(State::VALIDATING); ASSERT_from_seqStartOut_SIZE(1); - ASSERT_from_seqStartOut(0, Fw::String("test.bin")); + ASSERT_from_seqStartOut(0, Fw::String("test.bin"), Svc::SeqArgs(0, 0)); dispatchUntilState(State::RUNNING_AWAITING_STATEMENT_RESPONSE); dispatchUntilState(State::IDLE); ASSERT_CMD_RESPONSE_SIZE(1); @@ -2117,6 +2117,46 @@ TEST_F(FpySequencerTester, cmd_RUN) { removeFile("test.bin"); } +TEST_F(FpySequencerTester, cmd_RUN_ARGS) { + allocMem(); + add_LOAD_REL(0, 4); // Load first arg (U32 at offset 0) - duplicates it on stack + add_LOAD_REL(4, 4); // Load second arg (U32 at offset 4) - duplicates it on stack + add_DISCARD(16); // Discard all: 2 loaded copies + 2 original args + writeToFile("test.bin"); + + // Pass two U32 args: 10 and 20 + Svc::SeqArgs args{0, 0}; + Fw::ExternalSerializeBuffer argBuf(args.get_buffer(), SequenceArgumentsMaxSize); + U32 arg1 = 10, arg2 = 20; + ASSERT_EQ(argBuf.serializeFrom(arg1), Fw::FW_SERIALIZE_OK); + ASSERT_EQ(argBuf.serializeFrom(arg2), Fw::FW_SERIALIZE_OK); + args.set_size(argBuf.getSize()); + + sendCmd_RUN_ARGS(0, 0, Fw::String("test.bin"), FpySequencer_BlockState::BLOCK, args); + dispatchUntilState(State::VALIDATING); + ASSERT_EQ(tester_get_m_sequencesStarted(), 0); + ASSERT_EQ(tester_get_m_statementsDispatched(), 0); + dispatchUntilState(State::RUNNING_AWAITING_STATEMENT_RESPONSE); + ASSERT_from_seqStartOut_SIZE(1); + ASSERT_EQ(tester_get_m_sequencesStarted(), 1); + + // Verify both args are on stack (8 bytes) + auto* runtime = tester_get_m_runtime_ptr(); + ASSERT_EQ(runtime->stack.size, static_cast(8)); + + dispatchUntilState(State::IDLE); + ASSERT_EQ(tester_get_m_statementsDispatched(), 3); // LOAD_REL, LOAD_REL, DISCARD + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, Svc::FpySequencerTester::get_OPCODE_RUN_ARGS(), 0, Fw::CmdResponse::OK); + ASSERT_from_seqDoneOut_SIZE(1); + ASSERT_from_seqDoneOut(0, 0, 0, Fw::CmdResponse::OK); + + // Stack should be empty after discards + ASSERT_EQ(runtime->stack.size, static_cast(0)); + + removeFile("test.bin"); +} + TEST_F(FpySequencerTester, cmd_VALIDATE) { sendCmd_VALIDATE(0, 0, Fw::String("invalid seq")); // should try validating, then go to idle cuz it failed @@ -3169,14 +3209,15 @@ TEST_F(FpySequencerTester, seqRunIn) { add_NO_OP(); writeToFile("test.bin"); - invoke_to_seqRunIn(0, Fw::String("test.bin")); + Svc::SeqArgs emptyArgs; + invoke_to_seqRunIn(0, Fw::String("test.bin"), emptyArgs); this->tester_doDispatch(); dispatchUntilState(State::VALIDATING); dispatchUntilState(State::RUNNING_AWAITING_STATEMENT_RESPONSE); dispatchUntilState(State::IDLE); ASSERT_from_seqStartOut_SIZE(1); - ASSERT_from_seqStartOut(0, Fw::String("test.bin")); + ASSERT_from_seqStartOut(0, Fw::String("test.bin"), Svc::SeqArgs(0, 0)); ASSERT_from_seqDoneOut_SIZE(1); ASSERT_from_seqDoneOut(0, 0, 0, Fw::CmdResponse::OK); @@ -3184,13 +3225,53 @@ TEST_F(FpySequencerTester, seqRunIn) { // try running while already running this->tester_setState(State::RUNNING_DISPATCH_STATEMENT); - invoke_to_seqRunIn(0, Fw::String("test.bin")); + invoke_to_seqRunIn(0, Fw::String("test.bin"), emptyArgs); // dispatch cmd this->tester_doDispatch(); ASSERT_EVENTS_InvalidSeqRunCall_SIZE(1); removeFile("test.bin"); } +TEST_F(FpySequencerTester, seqRunInArgs) { + allocMem(); + add_LOAD_REL(0, 4); // Load first arg (U32 at offset 0) - duplicates it on stack + add_LOAD_REL(4, 4); // Load second arg (U32 at offset 4) - duplicates it on stack + add_DISCARD(16); // Discard all: 2 loaded copies + 2 original args + writeToFile("test.bin"); + + // Pass two U32 args: 10 and 20 + Svc::SeqArgs args{0, 0}; + Fw::ExternalSerializeBuffer argBuf(args.get_buffer(), SequenceArgumentsMaxSize); + U32 arg1 = 10, arg2 = 20; + ASSERT_EQ(argBuf.serializeFrom(arg1), Fw::FW_SERIALIZE_OK); + ASSERT_EQ(argBuf.serializeFrom(arg2), Fw::FW_SERIALIZE_OK); + args.set_size(argBuf.getSize()); + + invoke_to_seqRunIn(0, Fw::String("test.bin"), args); + dispatchUntilState(State::VALIDATING); + ASSERT_EQ(tester_get_m_sequencesStarted(), 0); + ASSERT_EQ(tester_get_m_statementsDispatched(), 0); + dispatchUntilState(State::RUNNING_AWAITING_STATEMENT_RESPONSE); + ASSERT_from_seqStartOut_SIZE(1); + ASSERT_EQ(tester_get_m_sequencesStarted(), 1); + + // Verify both args are on stack (8 bytes) + auto* runtime = tester_get_m_runtime_ptr(); + ASSERT_EQ(runtime->stack.size, static_cast(8)); + + dispatchUntilState(State::IDLE); + ASSERT_EQ(tester_get_m_statementsDispatched(), 3); // LOAD_REL, LOAD_REL, DISCARD + ASSERT_from_seqStartOut_SIZE(1); + ASSERT_from_seqStartOut(0, Fw::String("test.bin"), args); + ASSERT_from_seqDoneOut_SIZE(1); + ASSERT_from_seqDoneOut(0, 0, 0, Fw::CmdResponse::OK); + + // Stack should be empty after discards + ASSERT_EQ(runtime->stack.size, static_cast(0)); + + removeFile("test.bin"); +} + // ---------------------------------------------------------------------- // Stack Unit Tests // ---------------------------------------------------------------------- diff --git a/Svc/FpySequencer/test/ut/FpySequencerTester.hpp b/Svc/FpySequencer/test/ut/FpySequencerTester.hpp index 58ef6294703..be59d972cbb 100644 --- a/Svc/FpySequencer/test/ut/FpySequencerTester.hpp +++ b/Svc/FpySequencer/test/ut/FpySequencerTester.hpp @@ -277,6 +277,9 @@ class FpySequencerTester : public FpySequencerGTestBase, public ::testing::Test //! Get the OPCODE_RUN value static FwOpcodeType get_OPCODE_RUN() { return FpySequencerComponentBase::OPCODE_RUN; } + //! Get the OPCODE_RUN_ARGS value + static FwOpcodeType get_OPCODE_RUN_ARGS() { return FpySequencerComponentBase::OPCODE_RUN_ARGS; } + //! Get the OPCODE_VALIDATE value static FwOpcodeType get_OPCODE_VALIDATE() { return FpySequencerComponentBase::OPCODE_VALIDATE; } diff --git a/Svc/Seq/Seq.fpp b/Svc/Seq/Seq.fpp index 1caf1ce037a..94f52ee8bae 100644 --- a/Svc/Seq/Seq.fpp +++ b/Svc/Seq/Seq.fpp @@ -1,8 +1,13 @@ module Svc { + struct SeqArgs { + $size: FwSizeType + buffer: [SequenceArgumentsMaxSize] U8 + } default { $size = 0, buffer = 0 } @ Port to request a sequence be run port CmdSeqIn( filename: string size 240 @< The sequence file + args: SeqArgs @< Sequence arguments (placeholder - not currently processed) ) @ Port to cancel a sequence diff --git a/Svc/SeqDispatcher/SeqDispatcher.cpp b/Svc/SeqDispatcher/SeqDispatcher.cpp index 0275e1800b9..e5d60091057 100644 --- a/Svc/SeqDispatcher/SeqDispatcher.cpp +++ b/Svc/SeqDispatcher/SeqDispatcher.cpp @@ -26,7 +26,7 @@ FwIndexType SeqDispatcher::getNextAvailableSequencerIdx() { return -1; } -void SeqDispatcher::runSequence(FwIndexType sequencerIdx, const Fw::ConstStringBase& fileName, Fw::Wait block) { +void SeqDispatcher::runSequence(FwIndexType sequencerIdx, const Fw::ConstStringBase& fileName, Fw::Wait block, const Svc::SeqArgs& args) { // 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, @@ -47,12 +47,14 @@ void SeqDispatcher::runSequence(FwIndexType sequencerIdx, const Fw::ConstStringB this->m_dispatchedCount++; this->tlmWrite_dispatchedCount(this->m_dispatchedCount); - this->seqRunOut_out(sequencerIdx, this->m_entryTable[sequencerIdx].sequenceRunning); + this->seqRunOut_out(sequencerIdx, this->m_entryTable[sequencerIdx].sequenceRunning, args); } void SeqDispatcher::seqStartIn_handler(FwIndexType portNum, //!< The port number - const Fw::StringBase& fileName //!< The sequence file name + const Fw::StringBase& fileName, //!< The sequence file name + const Svc::SeqArgs& args //!< Sequence arguments (not currently used) ) { + (void)args; // Suppress unused parameter warning FW_ASSERT(portNum >= 0 && portNum < SeqDispatcherSequencerPorts, static_cast(portNum)); if (this->m_entryTable[portNum].state == SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_BLOCK || this->m_entryTable[portNum].state == SeqDispatcher_CmdSequencerState::RUNNING_SEQUENCE_NO_BLOCK) { @@ -121,7 +123,7 @@ void SeqDispatcher::seqDoneIn_handler(FwIndexType portNum, //!< The } //! Handler for input port seqRunIn -void SeqDispatcher::seqRunIn_handler(FwIndexType portNum, const Fw::StringBase& fileName) { +void SeqDispatcher::seqRunIn_handler(FwIndexType portNum, const Fw::StringBase& fileName, const Svc::SeqArgs& args) { FwIndexType idx = this->getNextAvailableSequencerIdx(); // no available sequencers if (idx == -1) { @@ -129,16 +131,28 @@ void SeqDispatcher::seqRunIn_handler(FwIndexType portNum, const Fw::StringBase& return; } - this->runSequence(idx, fileName, Fw::Wait::NO_WAIT); + this->runSequence(idx, fileName, Fw::Wait::NO_WAIT, args); } // ---------------------------------------------------------------------- // Command handler implementations // ---------------------------------------------------------------------- +// RUN command delegates to RUN_ARGS with empty arguments for backward compatibility void SeqDispatcher ::RUN_cmdHandler(const FwOpcodeType opCode, const U32 cmdSeq, const Fw::CmdStringArg& fileName, Fw::Wait block) { + // Create empty args and delegate to RUN_ARGS handler + Svc::SeqArgs emptyArgs{0, 0}; + this->RUN_ARGS_cmdHandler(opCode, cmdSeq, fileName, block, emptyArgs); +} + +// RUN_ARGS command dispatches a sequence with optional arguments to the first available sequencer +void SeqDispatcher ::RUN_ARGS_cmdHandler(const FwOpcodeType opCode, + const U32 cmdSeq, + const Fw::CmdStringArg& fileName, + Fw::Wait block, + Svc::SeqArgs buffer) { FwIndexType idx = this->getNextAvailableSequencerIdx(); // no available sequencers if (idx == -1) { @@ -147,7 +161,7 @@ void SeqDispatcher ::RUN_cmdHandler(const FwOpcodeType opCode, return; } - this->runSequence(idx, fileName, block); + this->runSequence(idx, fileName, block, buffer); if (block == Fw::Wait::NO_WAIT) { // return instantly diff --git a/Svc/SeqDispatcher/SeqDispatcher.hpp b/Svc/SeqDispatcher/SeqDispatcher.hpp index 6ae6b670308..bbf4bdb3ed1 100644 --- a/Svc/SeqDispatcher/SeqDispatcher.hpp +++ b/Svc/SeqDispatcher/SeqDispatcher.hpp @@ -40,12 +40,14 @@ class SeqDispatcher final : public SeqDispatcherComponentBase { //! Handler for input port seqStartIn void seqStartIn_handler(FwIndexType portNum, //!< The port number - const Fw::StringBase& fileName //!< The sequence file + const Fw::StringBase& fileName, //!< The sequence file + const Svc::SeqArgs& args //!< Optional sequence arguments ); //! Handler for input port seqRunIn void seqRunIn_handler(FwIndexType portNum, //!< The port number - const Fw::StringBase& fileName //!< The sequence file + const Fw::StringBase& fileName, //!< The sequence file + const Svc::SeqArgs& args //!< Optional sequence arguments ); private: @@ -68,7 +70,7 @@ class SeqDispatcher final : public SeqDispatcherComponentBase { FwIndexType getNextAvailableSequencerIdx(); - void runSequence(FwIndexType sequencerIdx, const Fw::ConstStringBase& fileName, Fw::Wait block); + void runSequence(FwIndexType sequencerIdx, const Fw::ConstStringBase& fileName, Fw::Wait block, const Svc::SeqArgs& args); // ---------------------------------------------------------------------- // Command handler implementations @@ -81,6 +83,14 @@ class SeqDispatcher final : public SeqDispatcherComponentBase { const Fw::CmdStringArg& fileName, /*!< The name of the sequence file*/ Fw::Wait block); + //! Implementation for RUN_ARGS command handler + //! + void RUN_ARGS_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, /*!< Return command status when complete or not*/ + Svc::SeqArgs buffer); /*!< Arguments to pass to a sequencer*/ + void LOG_STATUS_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/ const U32 cmdSeq); /*!< The command sequence number*/ }; diff --git a/Svc/SeqDispatcher/SeqDispatcherCommands.fppi b/Svc/SeqDispatcher/SeqDispatcherCommands.fppi index 378c2f6ec7a..5da052bb6d6 100644 --- a/Svc/SeqDispatcher/SeqDispatcherCommands.fppi +++ b/Svc/SeqDispatcher/SeqDispatcherCommands.fppi @@ -5,5 +5,13 @@ async command RUN( ) \ opcode 0 +@ Dispatches a sequence with arguments to the first available sequencer +async command RUN_ARGS( + fileName: string size 240 @< The name of the sequence file + $block: Fw.Wait @< Return command status when complete or not + buffer: Svc.SeqArgs @< Arguments to pass to a sequencer + ) \ + opcode 1 + @ Logs via Events the state of each connected command sequencer -async command LOG_STATUS() opcode 1 \ No newline at end of file +async command LOG_STATUS() opcode 2 \ No newline at end of file diff --git a/Svc/SeqDispatcher/test/ut/SeqDispatcherTestMain.cpp b/Svc/SeqDispatcher/test/ut/SeqDispatcherTestMain.cpp index ee84f3b4203..66a96299f1f 100644 --- a/Svc/SeqDispatcher/test/ut/SeqDispatcherTestMain.cpp +++ b/Svc/SeqDispatcher/test/ut/SeqDispatcherTestMain.cpp @@ -14,6 +14,26 @@ TEST(Nominal, testLogStatus) { tester.testLogStatus(); } +TEST(RunArgs, testRunArgsWithValidArguments) { + Svc::SeqDispatcherTester tester; + tester.testRunArgsWithValidArguments(); +} + +TEST(RunArgs, testRunArgsWithMaxSizedArguments) { + Svc::SeqDispatcherTester tester; + tester.testRunArgsWithMaxSizedArguments(); +} + +TEST(RunArgs, testRunArgsNoSequencersAvailable) { + Svc::SeqDispatcherTester tester; + tester.testRunArgsNoSequencersAvailable(); +} + +TEST(RunArgs, testRunArgsBlockingVsNonBlocking) { + Svc::SeqDispatcherTester tester; + tester.testRunArgsBlockingVsNonBlocking(); +} + 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 index 5b3d88c7a52..7cab086ffab 100644 --- a/Svc/SeqDispatcher/test/ut/SeqDispatcherTester.cpp +++ b/Svc/SeqDispatcher/test/ut/SeqDispatcherTester.cpp @@ -85,9 +85,147 @@ void SeqDispatcherTester::testLogStatus() { } void SeqDispatcherTester::seqRunOut_handler(FwIndexType portNum, //!< The port number - const Fw::StringBase& filename //!< The sequence file + const Fw::StringBase& filename, //!< The sequence file + const Svc::SeqArgs& args //!< Sequence arguments ) { - this->pushFromPortEntry_seqRunOut(filename); + this->pushFromPortEntry_seqRunOut(filename, args); +} + +// Test RUN_ARGS with valid arguments - verify arguments are propagated correctly +void SeqDispatcherTester::testRunArgsWithValidArguments() { + // Create test arguments with some data + // Note: Keep size small to fit within FW_CMD_ARG_BUFFER_MAX_SIZE constraints + // Total command payload must fit: filename (~44 bytes) + Fw::Wait (4 bytes) + SeqArgs + // With FW_CMD_ARG_BUFFER_MAX_SIZE ~= 500 bytes, SeqArgs should be < 400 bytes total + Svc::SeqArgs testArgs{0, 0}; + + // Send RUN_ARGS command with non-blocking mode + sendCmd_RUN_ARGS(0, 0, Fw::String("test"), Fw::Wait::NO_WAIT, testArgs); + this->component.doDispatch(); + + // Should get immediate response for non-blocking + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, SeqDispatcher::OPCODE_RUN_ARGS, 0, Fw::CmdResponse::OK); + + // Verify that seqRunOut was called with correct arguments + ASSERT_from_seqRunOut_SIZE(1); + ASSERT_from_seqRunOut(0, Fw::String("test"), testArgs); + + // Verify telemetry + ASSERT_TLM_dispatchedCount(0, 1); + ASSERT_TLM_sequencersAvailable(0, SeqDispatcherSequencerPorts - 1); +} + +// Test RUN_ARGS with maximum-sized arguments - test boundary conditions +void SeqDispatcherTester::testRunArgsWithMaxSizedArguments() { + // Create test arguments with a payload to test buffer handling + // Note: The actual max size is limited by FW_CMD_ARG_BUFFER_MAX_SIZE (~500 bytes) + // After accounting for filename (~44 bytes) and Fw::Wait (4 bytes), we have ~450 bytes + // With SeqArgs overhead (FwSizeType = 8 bytes), buffer can be ~440 bytes + // We use 400 bytes to stay safely within limits + Svc::SeqArgs largeArgs(400, 0); + U8* buffer = largeArgs.get_buffer(); + for (FwSizeType i = 0; i < 400; i++) { + buffer[i] = static_cast(i % 256); + } + + // Send RUN_ARGS command with large arguments (use short filename to save space) + sendCmd_RUN_ARGS(0, 0, Fw::String("test"), Fw::Wait::NO_WAIT, largeArgs); + this->component.doDispatch(); + + // Should get immediate response for non-blocking + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, SeqDispatcher::OPCODE_RUN_ARGS, 0, Fw::CmdResponse::OK); + + // Verify that seqRunOut was called with correct large arguments + ASSERT_from_seqRunOut_SIZE(1); + ASSERT_from_seqRunOut(0, Fw::String("test"), largeArgs); + + // Verify telemetry + ASSERT_TLM_dispatchedCount(0, 1); +} + +// Test RUN_ARGS when no sequencers available - verify error handling +void SeqDispatcherTester::testRunArgsNoSequencersAvailable() { + // Fill all sequencers + Svc::SeqArgs emptyArgs{0, 0}; + for (int i = 0; i < SeqDispatcherSequencerPorts; i++) { + sendCmd_RUN_ARGS(0, 0, Fw::String("test"), Fw::Wait::WAIT, emptyArgs); + this->component.doDispatch(); + // no response because blocking + ASSERT_CMD_RESPONSE_SIZE(0); + } + ASSERT_TLM_sequencersAvailable(SeqDispatcherSequencerPorts - 1, 0); + this->clearHistory(); + + // Now try to send another sequence when all are busy + Svc::SeqArgs testArgs; + testArgs.set_size(0); + + sendCmd_RUN_ARGS(0, 0, Fw::String("test"), Fw::Wait::WAIT, testArgs); + this->component.doDispatch(); + + // Should get error response + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, SeqDispatcher::OPCODE_RUN_ARGS, 0, Fw::CmdResponse::EXECUTION_ERROR); + + // Should get warning event + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_NoAvailableSequencers_SIZE(1); + + // Verify no seqRunOut was called + ASSERT_from_seqRunOut_SIZE(0); +} + +// Test RUN_ARGS with blocking vs non-blocking behavior +void SeqDispatcherTester::testRunArgsBlockingVsNonBlocking() { + Svc::SeqArgs testArgs{0, 0}; + + // Test non-blocking mode - should get immediate response + sendCmd_RUN_ARGS(0, 0, Fw::String("nonblocking"), Fw::Wait::NO_WAIT, testArgs); + this->component.doDispatch(); + + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, SeqDispatcher::OPCODE_RUN_ARGS, 0, Fw::CmdResponse::OK); + ASSERT_from_seqRunOut_SIZE(1); + this->clearHistory(); + + // Free up the sequencer + this->invoke_to_seqDoneIn(0, 0, 0, Fw::CmdResponse::OK); + this->component.doDispatch(); + this->clearHistory(); + + // Test blocking mode - should NOT get immediate response + sendCmd_RUN_ARGS(0, 0, Fw::String("blocking"), Fw::Wait::WAIT, testArgs); + this->component.doDispatch(); + + ASSERT_CMD_RESPONSE_SIZE(0); // No response yet + ASSERT_from_seqRunOut_SIZE(1); + ASSERT_from_seqRunOut(0, Fw::String("blocking"), testArgs); + + // Now complete the sequence + this->invoke_to_seqDoneIn(0, 0, 0, Fw::CmdResponse::OK); + this->component.doDispatch(); + + // Should now get response + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, SeqDispatcher::OPCODE_RUN_ARGS, 0, Fw::CmdResponse::OK); + + // Test blocking mode with error response + this->clearHistory(); + sendCmd_RUN_ARGS(0, 0, Fw::String("blocking_error"), Fw::Wait::WAIT, testArgs); + this->component.doDispatch(); + + ASSERT_CMD_RESPONSE_SIZE(0); // No response yet + + // Complete with error + this->invoke_to_seqDoneIn(0, 0, 0, Fw::CmdResponse::EXECUTION_ERROR); + this->component.doDispatch(); + + // Should get error response + ASSERT_CMD_RESPONSE_SIZE(1); + ASSERT_CMD_RESPONSE(0, SeqDispatcher::OPCODE_RUN_ARGS, 0, Fw::CmdResponse::EXECUTION_ERROR); + ASSERT_TLM_errorCount(0, 1); } } // namespace Svc diff --git a/Svc/SeqDispatcher/test/ut/SeqDispatcherTester.hpp b/Svc/SeqDispatcher/test/ut/SeqDispatcherTester.hpp index f3ed71369f3..7787180db52 100644 --- a/Svc/SeqDispatcher/test/ut/SeqDispatcherTester.hpp +++ b/Svc/SeqDispatcher/test/ut/SeqDispatcherTester.hpp @@ -40,6 +40,10 @@ class SeqDispatcherTester : public SeqDispatcherGTestBase { void testDispatch(); void testLogStatus(); + void testRunArgsWithValidArguments(); + void testRunArgsWithMaxSizedArguments(); + void testRunArgsNoSequencersAvailable(); + void testRunArgsBlockingVsNonBlocking(); private: // ---------------------------------------------------------------------- @@ -47,7 +51,8 @@ class SeqDispatcherTester : public SeqDispatcherGTestBase { // ---------------------------------------------------------------------- void seqRunOut_handler(FwIndexType portNum, //!< The port number - const Fw::StringBase& filename //!< The sequence file + const Fw::StringBase& filename, //!< The sequence file + const Svc::SeqArgs& args //!< Sequence arguments ); private: diff --git a/default/config/AcConstants.fpp b/default/config/AcConstants.fpp index 09e911ea83b..8a375bf6e19 100644 --- a/default/config/AcConstants.fpp +++ b/default/config/AcConstants.fpp @@ -59,3 +59,9 @@ constant FwAssertTextSize = 256 @ the constants FW_ASSERT_TEXT_SIZE and FW_LOG_STRING_MAX_SIZE, set @ in FpConfig.h. constant AssertFatalAdapterEventFileSize = FileNameStringSize + +@ The maximum size in bytes for passing sequence arguments through CmdSeqIn ports +@ Note: This must fit within FW_CMD_ARG_BUFFER_MAX_SIZE along with other command arguments +@ Total serialized size: fileName (~44 bytes) + Fw::Wait (4 bytes) + SeqArgs (8 + buffer_size) +@ With FW_CMD_ARG_BUFFER_MAX_SIZE ~= 500, we limit this to 400 bytes +constant SequenceArgumentsMaxSize = 400 \ No newline at end of file