fprime/Svc/CmdSequencer/CmdSequencerImpl.cpp
Ian Brault 2b65cc83cf
Add new Fw::ConstStringBase type for strings backed by immutable string literals (#4269)
* Add new Fw::StringBase type StaticString for strings backed my immutable literals

* Spellcheck fix

* Add disclaimer comment about use of StaticString

* Refactor the StringBase interface into an immutable ConstStringBase abstract base class and the now mutable StringBase class

* Rename StaticString to ConstExternalString and inherit from ConstStringBase

* Fix typo

* Change references from StringBase to ConstStringBase where applicable

* Updates following review meeting: add missing deserialize function and add new error status, move length function implementation into ConstStringBase so it is not pure virtual

* Clang format fix

* Additional clang-format fixes

* Fix the copy-assignment operator for StringBase not being correctly evaluated

* Clang format fix

* Explicitly delete the Serializable assignment operator and provide a skeleton implementation for RawTimeInterface to appease the compiler

* Revert "Explicitly delete the Serializable assignment operator and provide a skeleton implementation for RawTimeInterface to appease the compiler"

This reverts commit 086d7bcd3ca9c4f6e553d7fc34d0d126a69a165b.

* Move ConstStringBase to separate hpp/cpp files, plus other pull request feedback

* Clang format fix

* Update length implementation for ConstStringBase and ConstExternalString

* Improved asserts in ConstExternalString constructor

Co-authored-by: Rob Bocchino <bocchino@icloud.com>

* Fixed ConstStringBase length implementation

Co-authored-by: Rob Bocchino <bocchino@icloud.com>

* Clang format fix

* Add some UTs for ConstExternalString, fix non-overridden interfaces, and fix ConstStringBase::maxLength asserting for zero capacity strings

* Spell-check fix for ConstExternalString UTs

* Revise length implementation in ConstStringBase

If the capacity is zero, return zero

* Format

---------

Co-authored-by: Ian Brault <ian.r.brault@jpl.nasa.gov>
Co-authored-by: Rob Bocchino <bocchino@icloud.com>
Co-authored-by: Rob Bocchino <bocchino@jpl.nasa.gov>
Co-authored-by: M Starch <LeStarch@googlemail.com>
2025-11-07 09:50:05 -08:00

455 lines
16 KiB
C++

// ======================================================================
// \title CmdSequencerImpl.cpp
// \author Bocchino/Canham
// \brief cpp file for CmdDispatcherComponentBase component implementation class
//
// Copyright (C) 2009-2018 California Institute of Technology.
// ALL RIGHTS RESERVED. United States Government Sponsorship
// acknowledged.
#include <Fw/Com/ComPacket.hpp>
#include <Fw/Types/Assert.hpp>
#include <Fw/Types/SerialBuffer.hpp>
#include <Fw/Types/Serializable.hpp>
#include <Svc/CmdSequencer/CmdSequencerImpl.hpp>
extern "C" {
#include <Utils/Hash/libcrc/lib_crc.h>
}
namespace Svc {
// ----------------------------------------------------------------------
// Construction, initialization, and destruction
// ----------------------------------------------------------------------
CmdSequencerComponentImpl::CmdSequencerComponentImpl(const char* name)
: CmdSequencerComponentBase(name),
m_FPrimeSequence(*this),
m_sequence(&this->m_FPrimeSequence),
m_loadCmdCount(0),
m_cancelCmdCount(0),
m_errorCount(0),
m_runMode(STOPPED),
m_stepMode(AUTO),
m_executedCount(0),
m_totalExecutedCount(0),
m_sequencesCompletedCount(0),
m_timeout(0),
m_blockState(Svc::CmdSequencer_BlockState::NO_BLOCK),
m_opCode(0),
m_cmdSeq(0),
m_join_waiting(false) {}
void CmdSequencerComponentImpl::setTimeout(const U32 timeout) {
this->m_timeout = timeout;
}
void CmdSequencerComponentImpl ::setSequenceFormat(Sequence& sequence) {
this->m_sequence = &sequence;
}
void CmdSequencerComponentImpl ::allocateBuffer(const FwEnumStoreType identifier,
Fw::MemAllocator& allocator,
const FwSizeType bytes) {
this->m_sequence->allocateBuffer(identifier, allocator, bytes);
}
void CmdSequencerComponentImpl ::loadSequence(const Fw::ConstStringBase& fileName) {
FW_ASSERT(this->m_runMode == STOPPED, this->m_runMode);
if (not this->loadFile(fileName)) {
this->m_sequence->clear();
}
}
void CmdSequencerComponentImpl ::deallocateBuffer(Fw::MemAllocator& allocator) {
this->m_sequence->deallocateBuffer(allocator);
}
CmdSequencerComponentImpl::~CmdSequencerComponentImpl() {}
// ----------------------------------------------------------------------
// Handler implementations
// ----------------------------------------------------------------------
void CmdSequencerComponentImpl::CS_RUN_cmdHandler(FwOpcodeType opCode,
U32 cmdSeq,
const Fw::CmdStringArg& fileName,
Svc::CmdSequencer_BlockState block) {
if (not this->requireRunMode(STOPPED)) {
if (m_join_waiting) {
// Inform user previous seq file is not complete
this->log_WARNING_HI_CS_JoinWaitingNotComplete();
}
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
this->m_blockState = block.e;
this->m_cmdSeq = cmdSeq;
this->m_opCode = opCode;
// load commands
if (not this->loadFile(fileName)) {
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
this->m_executedCount = 0;
// 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();
}
if (Svc::CmdSequencer_BlockState::NO_BLOCK == this->m_blockState) {
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}
}
void CmdSequencerComponentImpl::CS_VALIDATE_cmdHandler(FwOpcodeType opCode,
U32 cmdSeq,
const Fw::CmdStringArg& fileName) {
if (!this->requireRunMode(STOPPED)) {
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
// load commands
if (not this->loadFile(fileName)) {
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
// clear the buffer
this->m_sequence->clear();
this->log_ACTIVITY_HI_CS_SequenceValid(this->m_sequence->getLogFileName());
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}
//! Handler for input port seqRunIn
void CmdSequencerComponentImpl::seqRunIn_handler(FwIndexType portNum, const Fw::StringBase& filename) {
if (!this->requireRunMode(STOPPED)) {
this->seqDone_out(0, 0, 0, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
// If file name is non-empty, load a file.
// Empty file name means don't load.
if (filename != "") {
Fw::CmdStringArg cmdStr(filename);
const bool status = this->loadFile(cmdStr);
if (!status) {
this->seqDone_out(0, 0, 0, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
} else if (not this->m_sequence->hasMoreRecords()) {
// No sequence loaded
this->log_WARNING_LO_CS_NoSequenceActive();
this->error();
this->seqDone_out(0, 0, 0, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
this->m_executedCount = 0;
// 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();
}
this->log_ACTIVITY_HI_CS_PortSequenceStarted(this->m_sequence->getLogFileName());
}
void CmdSequencerComponentImpl ::seqCancelIn_handler(const FwIndexType portNum) {
if (RUNNING == this->m_runMode) {
this->performCmd_Cancel();
this->log_ACTIVITY_HI_CS_SequenceCanceled(this->m_sequence->getLogFileName());
++this->m_cancelCmdCount;
this->tlmWrite_CS_CancelCommands(this->m_cancelCmdCount);
} else {
this->log_WARNING_LO_CS_NoSequenceActive();
}
}
void CmdSequencerComponentImpl::CS_CANCEL_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) {
if (RUNNING == this->m_runMode) {
this->performCmd_Cancel();
this->log_ACTIVITY_HI_CS_SequenceCanceled(this->m_sequence->getLogFileName());
++this->m_cancelCmdCount;
this->tlmWrite_CS_CancelCommands(this->m_cancelCmdCount);
} else {
this->log_WARNING_LO_CS_NoSequenceActive();
}
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
}
void CmdSequencerComponentImpl::CS_JOIN_WAIT_cmdHandler(const FwOpcodeType opCode, const U32 cmdSeq) {
// If there is no running sequence do not wait
if (m_runMode != RUNNING) {
this->log_WARNING_LO_CS_NoSequenceActive();
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
return;
} else {
m_join_waiting = true;
Fw::LogStringArg& logFileName = this->m_sequence->getLogFileName();
this->log_ACTIVITY_HI_CS_JoinWaiting(logFileName, m_cmdSeq, m_opCode);
m_cmdSeq = cmdSeq;
m_opCode = opCode;
}
}
// ----------------------------------------------------------------------
// Private helper methods
// ----------------------------------------------------------------------
bool CmdSequencerComponentImpl ::loadFile(const Fw::ConstStringBase& fileName) {
const bool status = this->m_sequence->loadFile(fileName);
if (status) {
Fw::LogStringArg& logFileName = this->m_sequence->getLogFileName();
this->log_ACTIVITY_LO_CS_SequenceLoaded(logFileName);
++this->m_loadCmdCount;
this->tlmWrite_CS_LoadCommands(this->m_loadCmdCount);
}
return status;
}
void CmdSequencerComponentImpl::error() {
++this->m_errorCount;
this->tlmWrite_CS_Errors(m_errorCount);
}
void CmdSequencerComponentImpl::performCmd_Cancel() {
this->m_sequence->reset();
this->m_runMode = STOPPED;
this->m_cmdTimer.clear();
this->m_cmdTimeoutTimer.clear();
this->m_executedCount = 0;
// write sequence done port with error, if connected
if (this->isConnected_seqDone_OutputPort(0)) {
this->seqDone_out(0, 0, 0, Fw::CmdResponse::EXECUTION_ERROR);
}
if (Svc::CmdSequencer_BlockState::BLOCK == this->m_blockState || m_join_waiting) {
// Do not wait if sequence was canceled or a cmd failed
this->m_join_waiting = false;
this->cmdResponse_out(this->m_opCode, this->m_cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
}
this->m_blockState = Svc::CmdSequencer_BlockState::NO_BLOCK;
}
void CmdSequencerComponentImpl ::cmdResponseIn_handler(FwIndexType portNum,
FwOpcodeType opcode,
U32 cmdSeq,
const Fw::CmdResponse& response) {
if (this->m_runMode == STOPPED) {
// Sequencer is not running
this->log_WARNING_HI_CS_UnexpectedCompletion(opcode);
} else {
// clear command timeout
this->m_cmdTimeoutTimer.clear();
if (response != Fw::CmdResponse::OK) {
this->commandError(this->m_executedCount, opcode, response.e);
this->performCmd_Cancel();
} else if (this->m_runMode == RUNNING && this->m_stepMode == AUTO) {
// Auto mode
this->commandComplete(opcode);
if (not this->m_sequence->hasMoreRecords()) {
// No data left
this->m_runMode = STOPPED;
this->sequenceComplete();
} else {
this->performCmd_Step();
}
} else {
// Manual step mode
this->commandComplete(opcode);
if (not this->m_sequence->hasMoreRecords()) {
this->m_runMode = STOPPED;
this->sequenceComplete();
}
}
}
}
void CmdSequencerComponentImpl ::schedIn_handler(FwIndexType portNum, U32 order) {
Fw::Time currTime = this->getTime();
// check to see if a command time is pending
if (this->m_cmdTimer.isExpiredAt(currTime)) {
this->comCmdOut_out(0, m_record.m_command, 0);
this->m_cmdTimer.clear();
// start command timeout timer
this->setCmdTimeout(currTime);
} else if (this->m_cmdTimeoutTimer.isExpiredAt(this->getTime())) { // check for command timeout
this->log_WARNING_HI_CS_SequenceTimeout(m_sequence->getLogFileName(), this->m_executedCount);
// If there is a command timeout, cancel the sequence
this->performCmd_Cancel();
}
}
void CmdSequencerComponentImpl ::CS_START_cmdHandler(FwOpcodeType opcode, U32 cmdSeq) {
if (not this->m_sequence->hasMoreRecords()) {
// No sequence loaded
this->log_WARNING_LO_CS_NoSequenceActive();
this->cmdResponse_out(opcode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
if (!this->requireRunMode(STOPPED)) {
this->cmdResponse_out(opcode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
return;
}
this->m_blockState = Svc::CmdSequencer_BlockState::NO_BLOCK;
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);
}
void CmdSequencerComponentImpl ::CS_STEP_cmdHandler(FwOpcodeType opcode, U32 cmdSeq) {
if (this->requireRunMode(RUNNING)) {
this->performCmd_Step();
// check for special case where end of sequence entry was encountered
if (this->m_runMode != STOPPED) {
this->log_ACTIVITY_HI_CS_CmdStepped(this->m_sequence->getLogFileName(), this->m_executedCount);
}
this->cmdResponse_out(opcode, cmdSeq, Fw::CmdResponse::OK);
} else {
this->cmdResponse_out(opcode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
}
}
void CmdSequencerComponentImpl ::CS_AUTO_cmdHandler(FwOpcodeType opcode, U32 cmdSeq) {
if (this->requireRunMode(STOPPED)) {
this->m_stepMode = AUTO;
this->log_ACTIVITY_HI_CS_ModeSwitched(CmdSequencer_SeqMode::AUTO);
this->cmdResponse_out(opcode, cmdSeq, Fw::CmdResponse::OK);
} else {
this->cmdResponse_out(opcode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
}
}
void CmdSequencerComponentImpl ::CS_MANUAL_cmdHandler(FwOpcodeType opcode, U32 cmdSeq) {
if (this->requireRunMode(STOPPED)) {
this->m_stepMode = MANUAL;
this->log_ACTIVITY_HI_CS_ModeSwitched(CmdSequencer_SeqMode::STEP);
this->cmdResponse_out(opcode, cmdSeq, Fw::CmdResponse::OK);
} else {
this->cmdResponse_out(opcode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
}
}
// ----------------------------------------------------------------------
// Helper methods
// ----------------------------------------------------------------------
bool CmdSequencerComponentImpl::requireRunMode(RunMode mode) {
if (this->m_runMode == mode) {
return true;
} else {
this->log_WARNING_HI_CS_InvalidMode();
return false;
}
}
void CmdSequencerComponentImpl ::commandError(const U32 number, const FwOpcodeType opCode, const U32 error) {
this->log_WARNING_HI_CS_CommandError(this->m_sequence->getLogFileName(), number, opCode, error);
this->error();
}
void CmdSequencerComponentImpl::performCmd_Step() {
this->m_sequence->nextRecord(m_record);
// set clock time base and context from value set when sequence was loaded
const Sequence::Header& header = this->m_sequence->getHeader();
this->m_record.m_timeTag.setTimeBase(header.m_timeBase);
this->m_record.m_timeTag.setTimeContext(header.m_timeContext);
Fw::Time currentTime = this->getTime();
switch (this->m_record.m_descriptor) {
case Sequence::Record::END_OF_SEQUENCE:
this->m_runMode = STOPPED;
this->sequenceComplete();
break;
case Sequence::Record::RELATIVE:
this->performCmd_Step_RELATIVE(currentTime);
break;
case Sequence::Record::ABSOLUTE:
this->performCmd_Step_ABSOLUTE(currentTime);
break;
default:
FW_ASSERT(0, m_record.m_descriptor);
}
}
void CmdSequencerComponentImpl::sequenceComplete() {
++this->m_sequencesCompletedCount;
// reset buffer
this->m_sequence->clear();
this->log_ACTIVITY_HI_CS_SequenceComplete(this->m_sequence->getLogFileName());
this->tlmWrite_CS_SequencesCompleted(this->m_sequencesCompletedCount);
this->m_executedCount = 0;
// write sequence done port, if connected
if (this->isConnected_seqDone_OutputPort(0)) {
this->seqDone_out(0, 0, 0, Fw::CmdResponse::OK);
}
if (Svc::CmdSequencer_BlockState::BLOCK == this->m_blockState || m_join_waiting) {
this->cmdResponse_out(this->m_opCode, this->m_cmdSeq, Fw::CmdResponse::OK);
}
m_join_waiting = false;
this->m_blockState = Svc::CmdSequencer_BlockState::NO_BLOCK;
}
void CmdSequencerComponentImpl::commandComplete(const FwOpcodeType opcode) {
this->log_ACTIVITY_LO_CS_CommandComplete(this->m_sequence->getLogFileName(), this->m_executedCount, opcode);
++this->m_executedCount;
++this->m_totalExecutedCount;
this->tlmWrite_CS_CommandsExecuted(this->m_totalExecutedCount);
}
void CmdSequencerComponentImpl ::performCmd_Step_RELATIVE(Fw::Time& currentTime) {
this->m_record.m_timeTag.add(currentTime.getSeconds(), currentTime.getUSeconds());
this->performCmd_Step_ABSOLUTE(currentTime);
}
void CmdSequencerComponentImpl ::performCmd_Step_ABSOLUTE(Fw::Time& currentTime) {
if (currentTime >= this->m_record.m_timeTag) {
this->comCmdOut_out(0, m_record.m_command, 0);
this->setCmdTimeout(currentTime);
} else {
this->m_cmdTimer.set(this->m_record.m_timeTag);
}
}
void CmdSequencerComponentImpl ::pingIn_handler(FwIndexType portNum, /*!< The port number*/
U32 key /*!< Value to return to pinger*/
) {
// send ping response
this->pingOut_out(0, key);
}
void CmdSequencerComponentImpl ::setCmdTimeout(const Fw::Time& currentTime) {
// start timeout timer if enabled and not in step mode
if ((this->m_timeout > 0) and (AUTO == this->m_stepMode)) {
Fw::Time expTime = currentTime;
expTime.add(this->m_timeout, 0);
this->m_cmdTimeoutTimer.set(expTime);
}
}
} // namespace Svc