Merge pull request 'Update Package' (#378) from KSat/fsfw:mueller/update-pack into development

Reviewed-on: fsfw/fsfw#378
This commit is contained in:
Steffen Gaisser 2021-03-09 14:16:13 +01:00
commit dbda6fee82
23 changed files with 1051 additions and 828 deletions

View File

@ -22,7 +22,9 @@ a C file without issues
### Local Pool ### Local Pool
- Interface of LocalPools has changed. LocalPool is not a template anymore. Instead the size and bucket number of the pools per page and the number of pages are passed to the ctor instead of two ctor arguments and a template parameter - Interface of LocalPools has changed. LocalPool is not a template anymore. Instead the size and
bucket number of the pools per page and the number of pages are passed to the ctor instead of
two ctor arguments and a template parameter
### Parameter Service ### Parameter Service
@ -40,7 +42,8 @@ important use-case)
### File System Interface ### File System Interface
- A new interfaces specifies the functions for a software object which exposes the file system of a given hardware to use message based file handling (e.g. PUS commanding) - A new interfaces specifies the functions for a software object which exposes the file system of
a given hardware to use message based file handling (e.g. PUS commanding)
### Internal Error Reporter ### Internal Error Reporter
@ -52,7 +55,8 @@ ID for now.
### Device Handler Base ### Device Handler Base
- There is an additional `PERFORM_OPERATION` step for the device handler base. It is important - There is an additional `PERFORM_OPERATION` step for the device handler base. It is important
that DHB users adapt their polling sequence tables to perform this step. This steps allows for aclear distinction between operation and communication steps that DHB users adapt their polling sequence tables to perform this step. This steps allows for
a clear distinction between operation and communication steps
- setNormalDatapoolEntriesInvalid is not an abstract method and a default implementation was provided - setNormalDatapoolEntriesInvalid is not an abstract method and a default implementation was provided
- getTransitionDelayMs is now an abstract method - getTransitionDelayMs is now an abstract method
@ -69,7 +73,8 @@ now
### Commanding Service Base ### Commanding Service Base
- CSB uses the new fsfwconfig::FSFW_CSB_FIFO_DEPTH variable to determine the FIFO depth for each CSB instance. This variable has to be set in the FSFWConfig.h file - CSB uses the new fsfwconfig::FSFW_CSB_FIFO_DEPTH variable to determine the FIFO depth for each
CSB instance. This variable has to be set in the FSFWConfig.h file
### Service Interface ### Service Interface
@ -82,6 +87,12 @@ now
For mission code, developers need to replace sif:: calls by the printf counterparts, but only if the CPP stream are excluded. For mission code, developers need to replace sif:: calls by the printf counterparts, but only if the CPP stream are excluded.
If this is not the case, everything should work as usual. If this is not the case, everything should work as usual.
### ActionHelper and ActionMessage
- ActionHelper finish function and ActionMessage::setCompletionReply now expects explicit
information whether to report a success or failure message instead of deriving it implicitely
from returnvalue
### PUS Parameter Service 20 ### PUS Parameter Service 20
Added PUS parameter service 20 (only custom subservices available). Added PUS parameter service 20 (only custom subservices available).

View File

@ -43,10 +43,10 @@ void ActionHelper::step(uint8_t step, MessageQueueId_t reportTo,
queueToUse->sendMessage(reportTo, &reply); queueToUse->sendMessage(reportTo, &reply);
} }
void ActionHelper::finish(MessageQueueId_t reportTo, ActionId_t commandId, void ActionHelper::finish(bool success, MessageQueueId_t reportTo, ActionId_t commandId,
ReturnValue_t result) { ReturnValue_t result) {
CommandMessage reply; CommandMessage reply;
ActionMessage::setCompletionReply(&reply, commandId, result); ActionMessage::setCompletionReply(success, &reply, commandId, result);
queueToUse->sendMessage(reportTo, &reply); queueToUse->sendMessage(reportTo, &reply);
} }
@ -69,7 +69,7 @@ void ActionHelper::prepareExecution(MessageQueueId_t commandedBy,
ipcStore->deleteData(dataAddress); ipcStore->deleteData(dataAddress);
if(result == HasActionsIF::EXECUTION_FINISHED) { if(result == HasActionsIF::EXECUTION_FINISHED) {
CommandMessage reply; CommandMessage reply;
ActionMessage::setCompletionReply(&reply, actionId, result); ActionMessage::setCompletionReply(true, &reply, actionId, result);
queueToUse->sendMessage(commandedBy, &reply); queueToUse->sendMessage(commandedBy, &reply);
} }
if (result != HasReturnvaluesIF::RETURN_OK) { if (result != HasReturnvaluesIF::RETURN_OK) {

View File

@ -62,12 +62,12 @@ public:
ReturnValue_t result = HasReturnvaluesIF::RETURN_OK); ReturnValue_t result = HasReturnvaluesIF::RETURN_OK);
/** /**
* Function to be called by the owner to send a action completion message * Function to be called by the owner to send a action completion message
* * @param success Specify whether action was completed successfully or not.
* @param reportTo MessageQueueId_t to report the action completion message to * @param reportTo MessageQueueId_t to report the action completion message to
* @param commandId ID of the executed command * @param commandId ID of the executed command
* @param result Result of the execution * @param result Result of the execution
*/ */
void finish(MessageQueueId_t reportTo, ActionId_t commandId, void finish(bool success, MessageQueueId_t reportTo, ActionId_t commandId,
ReturnValue_t result = HasReturnvaluesIF::RETURN_OK); ReturnValue_t result = HasReturnvaluesIF::RETURN_OK);
/** /**
* Function to be called by the owner if an action does report data. * Function to be called by the owner if an action does report data.

View File

@ -53,11 +53,12 @@ void ActionMessage::setDataReply(CommandMessage* message, ActionId_t actionId,
message->setParameter2(data.raw); message->setParameter2(data.raw);
} }
void ActionMessage::setCompletionReply(CommandMessage* message, void ActionMessage::setCompletionReply(bool success, CommandMessage* message,
ActionId_t fid, ReturnValue_t result) { ActionId_t fid, ReturnValue_t result) {
if (result == HasReturnvaluesIF::RETURN_OK or result == HasActionsIF::EXECUTION_FINISHED) { if (success) {
message->setCommand(COMPLETION_SUCCESS); message->setCommand(COMPLETION_SUCCESS);
} else { }
else {
message->setCommand(COMPLETION_FAILED); message->setCommand(COMPLETION_FAILED);
} }
message->setParameter(fid); message->setParameter(fid);

View File

@ -24,19 +24,23 @@ public:
static const Command_t DATA_REPLY = MAKE_COMMAND_ID(4); static const Command_t DATA_REPLY = MAKE_COMMAND_ID(4);
static const Command_t COMPLETION_SUCCESS = MAKE_COMMAND_ID(5); static const Command_t COMPLETION_SUCCESS = MAKE_COMMAND_ID(5);
static const Command_t COMPLETION_FAILED = MAKE_COMMAND_ID(6); static const Command_t COMPLETION_FAILED = MAKE_COMMAND_ID(6);
virtual ~ActionMessage(); virtual ~ActionMessage();
static void setCommand(CommandMessage* message, ActionId_t fid, static void setCommand(CommandMessage* message, ActionId_t fid,
store_address_t parameters); store_address_t parameters);
static ActionId_t getActionId(const CommandMessage* message ); static ActionId_t getActionId(const CommandMessage* message );
static store_address_t getStoreId(const CommandMessage* message ); static store_address_t getStoreId(const CommandMessage* message);
static void setStepReply(CommandMessage* message, ActionId_t fid, static void setStepReply(CommandMessage* message, ActionId_t fid,
uint8_t step, ReturnValue_t result = HasReturnvaluesIF::RETURN_OK); uint8_t step, ReturnValue_t result = HasReturnvaluesIF::RETURN_OK);
static uint8_t getStep(const CommandMessage* message ); static uint8_t getStep(const CommandMessage* message );
static ReturnValue_t getReturnCode(const CommandMessage* message ); static ReturnValue_t getReturnCode(const CommandMessage* message );
static void setDataReply(CommandMessage* message, ActionId_t actionId, static void setDataReply(CommandMessage* message, ActionId_t actionId,
store_address_t data); store_address_t data);
static void setCompletionReply(CommandMessage* message, ActionId_t fid, static void setCompletionReply(bool success, CommandMessage* message, ActionId_t fid,
ReturnValue_t result = HasReturnvaluesIF::RETURN_OK); ReturnValue_t result = HasReturnvaluesIF::RETURN_OK);
static void clear(CommandMessage* message); static void clear(CommandMessage* message);
}; };

View File

@ -62,7 +62,7 @@ void SimpleActionHelper::prepareExecution(MessageQueueId_t commandedBy,
stepCount++; stepCount++;
break; break;
case HasActionsIF::EXECUTION_FINISHED: case HasActionsIF::EXECUTION_FINISHED:
ActionMessage::setCompletionReply(&reply, actionId, ActionMessage::setCompletionReply(true, &reply, actionId,
HasReturnvaluesIF::RETURN_OK); HasReturnvaluesIF::RETURN_OK);
queueToUse->sendMessage(commandedBy, &reply); queueToUse->sendMessage(commandedBy, &reply);
break; break;

View File

@ -13,7 +13,7 @@ ExtendedControllerBase::~ExtendedControllerBase() {
ReturnValue_t ExtendedControllerBase::executeAction(ActionId_t actionId, ReturnValue_t ExtendedControllerBase::executeAction(ActionId_t actionId,
MessageQueueId_t commandedBy, const uint8_t *data, size_t size) { MessageQueueId_t commandedBy, const uint8_t *data, size_t size) {
// needs to be overriden and implemented by child class. /* Needs to be overriden and implemented by child class. */
return HasReturnvaluesIF::RETURN_OK; return HasReturnvaluesIF::RETURN_OK;
} }
@ -21,7 +21,7 @@ ReturnValue_t ExtendedControllerBase::executeAction(ActionId_t actionId,
ReturnValue_t ExtendedControllerBase::initializeLocalDataPool( ReturnValue_t ExtendedControllerBase::initializeLocalDataPool(
localpool::DataPool &localDataPoolMap, LocalDataPoolManager &poolManager) { localpool::DataPool &localDataPoolMap, LocalDataPoolManager &poolManager) {
// needs to be overriden and implemented by child class. /* Needs to be overriden and implemented by child class. */
return HasReturnvaluesIF::RETURN_OK; return HasReturnvaluesIF::RETURN_OK;
} }
@ -96,8 +96,10 @@ ReturnValue_t ExtendedControllerBase::initializeAfterTaskCreation() {
ReturnValue_t ExtendedControllerBase::performOperation(uint8_t opCode) { ReturnValue_t ExtendedControllerBase::performOperation(uint8_t opCode) {
handleQueue(); handleQueue();
poolManager.performHkOperation();
performControlOperation(); performControlOperation();
/* We do this after performing control operation because variables will be set changed
in this function. */
poolManager.performHkOperation();
return RETURN_OK; return RETURN_OK;
} }

View File

@ -8,9 +8,9 @@
/** /**
* @brief Helper class to read data sets or pool variables * @brief Helper class to read data sets or pool variables
*/ */
class PoolReadHelper { class PoolReadGuard {
public: public:
PoolReadHelper(ReadCommitIF* readObject, PoolReadGuard(ReadCommitIF* readObject,
MutexIF::TimeoutType timeoutType = MutexIF::TimeoutType::WAITING, MutexIF::TimeoutType timeoutType = MutexIF::TimeoutType::WAITING,
uint32_t mutexTimeout = 20): uint32_t mutexTimeout = 20):
readObject(readObject), mutexTimeout(mutexTimeout) { readObject(readObject), mutexTimeout(mutexTimeout) {
@ -42,7 +42,7 @@ public:
/** /**
* @brief Default destructor which will take care of commiting changed values. * @brief Default destructor which will take care of commiting changed values.
*/ */
~PoolReadHelper() { ~PoolReadGuard() {
if(readObject != nullptr and not noCommit) { if(readObject != nullptr and not noCommit) {
readObject->commit(timeoutType, mutexTimeout); readObject->commit(timeoutType, mutexTimeout);
} }

View File

@ -46,6 +46,8 @@ public:
* read-write or read-only. * read-write or read-only.
*/ */
virtual ReadWriteMode_t getReadWriteMode() const = 0; virtual ReadWriteMode_t getReadWriteMode() const = 0;
virtual void setReadWriteMode(ReadWriteMode_t newMode) = 0;
/** /**
* @brief This operation shall return the data pool id of the variable. * @brief This operation shall return the data pool id of the variable.
*/ */
@ -59,7 +61,6 @@ public:
* @brief With this call, the valid information of the variable is set. * @brief With this call, the valid information of the variable is set.
*/ */
virtual void setValid(bool validity) = 0; virtual void setValid(bool validity) = 0;
}; };
using pool_rwm_t = PoolVariableIF::ReadWriteMode_t; using pool_rwm_t = PoolVariableIF::ReadWriteMode_t;

View File

@ -162,8 +162,8 @@ protected:
*/ */
virtual LocalPoolObjectBase* getPoolObjectHandle(lp_id_t localPoolId) { virtual LocalPoolObjectBase* getPoolObjectHandle(lp_id_t localPoolId) {
#if FSFW_CPP_OSTREAM_ENABLED == 1 #if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "HasLocalDataPoolIF::getPoolObjectHandle: Not overriden" sif::warning << "HasLocalDataPoolIF::getPoolObjectHandle: Not overriden. "
<< ". Returning nullptr!" << std::endl; "Returning nullptr!" << std::endl;
#else #else
sif::printWarning("HasLocalDataPoolIF::getPoolObjectHandle: " sif::printWarning("HasLocalDataPoolIF::getPoolObjectHandle: "
"Not overriden. Returning nullptr!\n"); "Not overriden. Returning nullptr!\n");

View File

@ -42,15 +42,15 @@ LocalDataPoolManager::~LocalDataPoolManager() {}
ReturnValue_t LocalDataPoolManager::initialize(MessageQueueIF* queueToUse) { ReturnValue_t LocalDataPoolManager::initialize(MessageQueueIF* queueToUse) {
if(queueToUse == nullptr) { if(queueToUse == nullptr) {
// error, all destinations invalid /* Error, all destinations invalid */
printWarningOrError(sif::OutputTypes::OUT_ERROR, printWarningOrError(sif::OutputTypes::OUT_ERROR, "initialize",
"initialize", QUEUE_OR_DESTINATION_INVALID); QUEUE_OR_DESTINATION_INVALID);
} }
hkQueue = queueToUse; hkQueue = queueToUse;
ipcStore = objectManager->get<StorageManagerIF>(objects::IPC_STORE); ipcStore = objectManager->get<StorageManagerIF>(objects::IPC_STORE);
if(ipcStore == nullptr) { if(ipcStore == nullptr) {
// error, all destinations invalid /* Error, all destinations invalid */
printWarningOrError(sif::OutputTypes::OUT_ERROR, printWarningOrError(sif::OutputTypes::OUT_ERROR,
"initialize", HasReturnvaluesIF::RETURN_FAILED, "initialize", HasReturnvaluesIF::RETURN_FAILED,
"Could not set IPC store."); "Could not set IPC store.");

View File

@ -42,8 +42,7 @@ LocalPoolDataSetBase::LocalPoolDataSetBase(HasLocalDataPoolIF *hkOwner,
} }
} }
LocalPoolDataSetBase::LocalPoolDataSetBase(sid_t sid, LocalPoolDataSetBase::LocalPoolDataSetBase(sid_t sid, PoolVariableIF** registeredVariablesArray,
PoolVariableIF** registeredVariablesArray,
const size_t maxNumberOfVariables): const size_t maxNumberOfVariables):
PoolDataSetBase(registeredVariablesArray, maxNumberOfVariables) { PoolDataSetBase(registeredVariablesArray, maxNumberOfVariables) {
HasLocalDataPoolIF* hkOwner = objectManager->get<HasLocalDataPoolIF>( HasLocalDataPoolIF* hkOwner = objectManager->get<HasLocalDataPoolIF>(
@ -289,7 +288,7 @@ bool LocalPoolDataSetBase::isValid() const {
void LocalPoolDataSetBase::setValidity(bool valid, bool setEntriesRecursively) { void LocalPoolDataSetBase::setValidity(bool valid, bool setEntriesRecursively) {
if(setEntriesRecursively) { if(setEntriesRecursively) {
for(size_t idx = 0; idx < this->getFillCount(); idx++) { for(size_t idx = 0; idx < this->getFillCount(); idx++) {
registeredVariables[idx] -> setValid(valid); registeredVariables[idx]->setValid(valid);
} }
} }
this->valid = valid; this->valid = valid;
@ -302,3 +301,8 @@ object_id_t LocalPoolDataSetBase::getCreatorObjectId() {
return objects::NO_OBJECT; return objects::NO_OBJECT;
} }
void LocalPoolDataSetBase::setAllVariablesReadOnly() {
for(size_t idx = 0; idx < this->getFillCount(); idx++) {
registeredVariables[idx]->setReadWriteMode(pool_rwm_t::VAR_READ);
}
}

View File

@ -59,7 +59,7 @@ public:
/** /**
* @brief Constructor for users of the local pool data, which need * @brief Constructor for users of the local pool data, which need
* to access data created by one (!) HK manager. * to access data created by one HK manager.
* @details * @details
* Unlike the first constructor, no component for periodic handling * Unlike the first constructor, no component for periodic handling
* will be initiated. * will be initiated.
@ -109,6 +109,12 @@ public:
LocalPoolDataSetBase(const LocalPoolDataSetBase& otherSet) = delete; LocalPoolDataSetBase(const LocalPoolDataSetBase& otherSet) = delete;
const LocalPoolDataSetBase& operator=(const LocalPoolDataSetBase& otherSet) = delete; const LocalPoolDataSetBase& operator=(const LocalPoolDataSetBase& otherSet) = delete;
/**
* Helper functions used to set all currently contained variables to read-only.
* It is recommended to call this in set constructors intended to be used
* by data consumers to prevent accidentally changing pool data.
*/
void setAllVariablesReadOnly();
void setValidityBufferGeneration(bool withValidityBuffer); void setValidityBufferGeneration(bool withValidityBuffer);
sid_t getSid() const; sid_t getSid() const;

View File

@ -37,15 +37,20 @@ LocalPoolObjectBase::LocalPoolObjectBase(object_id_t poolOwner, lp_id_t poolId,
if(poolId == PoolVariableIF::NO_PARAMETER) { if(poolId == PoolVariableIF::NO_PARAMETER) {
#if FSFW_CPP_OSTREAM_ENABLED == 1 #if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "LocalPoolVar<T>::LocalPoolVar: 0 passed as pool ID, " sif::warning << "LocalPoolVar<T>::LocalPoolVar: 0 passed as pool ID, "
<< "which is the NO_PARAMETER value!" << std::endl; "which is the NO_PARAMETER value!" << std::endl;
#else
sif::printWarning("LocalPoolVar<T>::LocalPoolVar: 0 passed as pool ID, "
"which is the NO_PARAMETER value!\n");
#endif #endif
} }
HasLocalDataPoolIF* hkOwner = objectManager->get<HasLocalDataPoolIF>(poolOwner); HasLocalDataPoolIF* hkOwner = objectManager->get<HasLocalDataPoolIF>(poolOwner);
if(hkOwner == nullptr) { if(hkOwner == nullptr) {
#if FSFW_CPP_OSTREAM_ENABLED == 1 #if FSFW_CPP_OSTREAM_ENABLED == 1
sif::error << "LocalPoolVariable: The supplied pool owner did not " sif::error << "LocalPoolVariable: The supplied pool owner did not implement the correct "
<< "implement the correct interface" "interface HasLocalDataPoolIF!" << std::endl;
<< " HasLocalDataPoolIF!" << std::endl; #else
sif::printError( "LocalPoolVariable: The supplied pool owner did not implement the correct "
"interface HasLocalDataPoolIF!\n");
#endif #endif
return; return;
} }

View File

@ -26,8 +26,17 @@ inline LocalPoolVariable<T>::LocalPoolVariable(gp_id_t globalPoolId,
template<typename T> template<typename T>
inline ReturnValue_t LocalPoolVariable<T>::read( inline ReturnValue_t LocalPoolVariable<T>::read(
MutexIF::TimeoutType timeoutType, uint32_t timeoutMs) { MutexIF::TimeoutType timeoutType, uint32_t timeoutMs) {
MutexHelper(LocalDpManagerAttorney::getMutexHandle(*hkManager), timeoutType, timeoutMs); if(hkManager == nullptr) {
return readWithoutLock(); return readWithoutLock();
}
MutexIF* mutex = LocalDpManagerAttorney::getMutexHandle(*hkManager);
ReturnValue_t result = mutex->lockMutex(timeoutType, timeoutMs);
if(result != HasReturnvaluesIF::RETURN_OK) {
return result;
}
result = readWithoutLock();
mutex->unlockMutex();
return result;
} }
template<typename T> template<typename T>
@ -43,7 +52,6 @@ inline ReturnValue_t LocalPoolVariable<T>::readWithoutLock() {
PoolEntry<T>* poolEntry = nullptr; PoolEntry<T>* poolEntry = nullptr;
ReturnValue_t result = LocalDpManagerAttorney::fetchPoolEntry(*hkManager, localPoolId, ReturnValue_t result = LocalDpManagerAttorney::fetchPoolEntry(*hkManager, localPoolId,
&poolEntry); &poolEntry);
//ReturnValue_t result = hkManager->fetchPoolEntry(localPoolId, &poolEntry);
if(result != RETURN_OK) { if(result != RETURN_OK) {
object_id_t ownerObjectId = hkManager->getCreatorObjectId(); object_id_t ownerObjectId = hkManager->getCreatorObjectId();
reportReadCommitError("LocalPoolVariable", result, reportReadCommitError("LocalPoolVariable", result,
@ -51,15 +59,6 @@ inline ReturnValue_t LocalPoolVariable<T>::readWithoutLock() {
return result; return result;
} }
// Actually this should never happen..
// if(poolEntry->address == nullptr) {
// result = PoolVariableIF::INVALID_POOL_ENTRY;
// object_id_t ownerObjectId = hkManager->getOwner()->getObjectId();
// reportReadCommitError("LocalPoolVariable", result,
// false, ownerObjectId, localPoolId);
// return result;
// }
this->value = *(poolEntry->getDataPtr()); this->value = *(poolEntry->getDataPtr());
this->valid = poolEntry->getValid(); this->valid = poolEntry->getValid();
return RETURN_OK; return RETURN_OK;
@ -75,8 +74,17 @@ inline ReturnValue_t LocalPoolVariable<T>::commit(bool setValid,
template<typename T> template<typename T>
inline ReturnValue_t LocalPoolVariable<T>::commit( inline ReturnValue_t LocalPoolVariable<T>::commit(
MutexIF::TimeoutType timeoutType, uint32_t timeoutMs) { MutexIF::TimeoutType timeoutType, uint32_t timeoutMs) {
MutexHelper(LocalDpManagerAttorney::getMutexHandle(*hkManager), timeoutType, timeoutMs); if(hkManager == nullptr) {
return commitWithoutLock(); return commitWithoutLock();
}
MutexIF* mutex = LocalDpManagerAttorney::getMutexHandle(*hkManager);
ReturnValue_t result = mutex->lockMutex(timeoutType, timeoutMs);
if(result != HasReturnvaluesIF::RETURN_OK) {
return result;
}
result = commitWithoutLock();
mutex->unlockMutex();
return result;
} }
template<typename T> template<typename T>

View File

@ -558,7 +558,7 @@ void DeviceHandlerBase::replyToCommand(ReturnValue_t status,
if (cookieInfo.pendingCommand->second.sendReplyTo != NO_COMMANDER) { if (cookieInfo.pendingCommand->second.sendReplyTo != NO_COMMANDER) {
MessageQueueId_t queueId = cookieInfo.pendingCommand->second.sendReplyTo; MessageQueueId_t queueId = cookieInfo.pendingCommand->second.sendReplyTo;
if (status == NO_REPLY_EXPECTED) { if (status == NO_REPLY_EXPECTED) {
actionHelper.finish(queueId, cookieInfo.pendingCommand->first, actionHelper.finish(true, queueId, cookieInfo.pendingCommand->first,
RETURN_OK); RETURN_OK);
} else { } else {
actionHelper.step(1, queueId, cookieInfo.pendingCommand->first, actionHelper.step(1, queueId, cookieInfo.pendingCommand->first,
@ -581,7 +581,11 @@ void DeviceHandlerBase::replyToReply(DeviceReplyMap::iterator iter,
// Check if it was transition or internal command. // Check if it was transition or internal command.
// Don't send any replies in that case. // Don't send any replies in that case.
if (info->sendReplyTo != NO_COMMANDER) { if (info->sendReplyTo != NO_COMMANDER) {
actionHelper.finish(info->sendReplyTo, iter->first, status); bool success = false;
if(status == HasReturnvaluesIF::RETURN_OK) {
success = true;
}
actionHelper.finish(success, info->sendReplyTo, iter->first, status);
} }
info->isExecuting = false; info->isExecuting = false;
} }

View File

@ -1,3 +1,172 @@
## Local Data Pools ## Local Data Pools Developer Information
The following text is targeted towards mission software developers which would like
to use the local data pools provided by the FSFW to store data like sensor values so they can be
used by other software objects like controllers as well. If a custom class should have a local
pool which can be used by other software objects as well, following steps have to be performed:
1. Create a `LocalDataPoolManager` member object in the custom class
2. Implement the `HasLocalDataPoolIF` with specifies the interface between the local pool manager
and the class owning the local pool.
The local data pool manager is also able to process housekeeping service requests in form
of messages, generate periodic housekeeping packet, generate notification and snapshots of changed
variables and datasets and process notifications and snapshots coming from other objects.
The two former tasks are related to the external interface using telemetry and telecommands (TMTC)
while the later two are related to data consumers like controllers only acting on data change
detected by the data creator instead of checking the data manually each cycle. Two important
framework classes `DeviceHandlerBase` and `ExtendedControllerBase` already perform the two steps
shown above so the steps required are altered slightly.
### Storing and Accessing pool data
The pool manager is responsible for thread-safe access of the pool data, but the actual
access to the pool data from the point of view of a mission software developer happens via proxy
classes like pool variable classes. These classes store a copy
of the pool variable with the matching datatype and copy the actual data from the local pool
on a `read` call. Changed variables can then be written to the local pool with a `commit` call.
The `read` and `commit` calls are thread-safe and can be called concurrently from data creators
and data consumers. Generally, a user will create a dataset class which in turn groups all
cohesive pool variables. These sets simply iterator over the list of variables and call the
`read` and `commit` functions of each variable. The following diagram shows the
high-level architecture of the local data pools.
<img align="center" src="./images/PoolArchitecture.png" width="50%"> <br>
An example is shown for using the local data pools with a Gyroscope.
For example, the following code shows an implementation to access data from a Gyroscope taken
from the SOURCE CubeSat project:
```cpp
class GyroPrimaryDataset: public StaticLocalDataSet<3 * sizeof(float)> {
public:
/**
* Constructor for data users
* @param gyroId
*/
GyroPrimaryDataset(object_id_t gyroId):
StaticLocalDataSet(sid_t(gyroId, gyrodefs::GYRO_DATA_SET_ID)) {
setAllVariablesReadOnly();
}
lp_var_t<float> angVelocityX = lp_var_t<float>(sid.objectId,
gyrodefs::ANGULAR_VELOCITY_X, this);
lp_var_t<float> angVelocityY = lp_var_t<float>(sid.objectId,
gyrodefs::ANGULAR_VELOCITY_Y, this);
lp_var_t<float> angVelocityZ = lp_var_t<float>(sid.objectId,
gyrodefs::ANGULAR_VELOCITY_Z, this);
private:
friend class GyroHandler;
/**
* Constructor for data creator
* @param hkOwner
*/
GyroPrimaryDataset(HasLocalDataPoolIF* hkOwner):
StaticLocalDataSet(hkOwner, gyrodefs::GYRO_DATA_SET_ID) {}
};
```
There is a public constructor for users which sets all variables to read-only and there is a
constructor for the GyroHandler data creator by marking it private and declaring the `GyroHandler`
as a friend class. Both the atittude controller and the `GyroHandler` can now
use the same class definition to access the pool variables with `read` and `commit` semantics
in a thread-safe way. Generally, each class requiring access will have the set class as a member
class. The data creator will also be generally a `DeviceHandlerBase` subclass and some additional
steps are necessary to expose the set for housekeeping purposes.
### Using the local data pools in a `DeviceHandlerBase` subclass
It is very common to store data generated by devices like a sensor into a pool which can
then be used by other objects. Therefore, the `DeviceHandlerBase` already has a
local pool. Using the aforementioned example, our `GyroHandler` will now have the set class
as a member:
```cpp
class GyroHandler: ... {
public:
...
private:
...
GyroPrimaryDataset gyroData;
...
};
```
The constructor used for the creators expects the owner class as a parameter, so we initialize
the object in the `GyroHandler` constructor like this:
```cpp
GyroHandler::GyroHandler(object_id_t objectId, object_id_t comIF,
CookieIF *comCookie, uint8_t switchId):
DeviceHandlerBase(objectId, comIF, comCookie), switchId(switchId),
gyroData(this) {}
```
We need to assign the set to a reply ID used in the `DeviceHandlerBase`.
The combination of the `GyroHandler` object ID and the reply ID will be the 64-bit structure ID
`sid_t` and is used to globally identify the set, for example when requesting housekeeping data or
generating update messages. We need to assign our custom set class in some way so that the local
pool manager can access the custom data sets as well.
By default, the `getDataSetHandle` will take care of this tasks. The default implementation for a
`DeviceHandlerBase` subclass will use the internal command map to retrieve
a handle to a dataset from a given reply ID. Therefore,
we assign the set in the `fillCommandAndReplyMap` function:
```cpp
void GyroHandler::fillCommandAndReplyMap() {
...
this->insertInCommandAndReplyMap(gyrodefs::GYRO_DATA, 3, &gyroData);
...
}
```
Now, we need to create the actual pool entries as well, using the `initializeLocalDataPool`
function. Here, we also immediately subscribe for periodic housekeeping packets
with an interval of 4 seconds. They are still disabled in this example and can be enabled
with a housekeeping service command.
```cpp
ReturnValue_t GyroHandler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap,
LocalDataPoolManager &poolManager) {
localDataPoolMap.emplace(gyrodefs::ANGULAR_VELOCITY_X,
new PoolEntry<float>({0.0}));
localDataPoolMap.emplace(gyrodefs::ANGULAR_VELOCITY_Y,
new PoolEntry<float>({0.0}));
localDataPoolMap.emplace(gyrodefs::ANGULAR_VELOCITY_Z,
new PoolEntry<float>({0.0}));
localDataPoolMap.emplace(gyrodefs::GENERAL_CONFIG_REG42,
new PoolEntry<uint8_t>({0}));
localDataPoolMap.emplace(gyrodefs::RANGE_CONFIG_REG43,
new PoolEntry<uint8_t>({0}));
poolManager.subscribeForPeriodicPacket(gyroData.getSid(), false, 4.0, false);
return HasReturnvaluesIF::RETURN_OK;
}
```
Now, if we receive some sensor data and converted them into the right format,
we can write it into the pool like this, using a guard class to ensure the set is commited back
in any case:
```cpp
PoolReadGuard readHelper(&gyroData);
if(readHelper.getReadResult() == HasReturnvaluesIF::RETURN_OK) {
if(not gyroData.isValid()) {
gyroData.setValidity(true, true);
}
gyroData.angVelocityX = angularVelocityX;
gyroData.angVelocityY = angularVelocityY;
gyroData.angVelocityZ = angularVelocityZ;
}
```
The guard class will commit the changed data on destruction automatically.
### Using the local data pools in a `ExtendedControllerBase` subclass
Coming soon

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -157,8 +157,8 @@ ReturnValue_t CCSDSTime::convertFromASCII(Clock::TimeOfDay_t* to,
if (length < 19) { if (length < 19) {
return RETURN_FAILED; return RETURN_FAILED;
} }
// Newlib nano can't parse uint8, see SCNu8 documentation and https://sourceware.org/newlib/README // Newlib nano can't parse uint8, see SCNu8 documentation and https://sourceware.org/newlib/README
// Suggestion: use uint16 all the time. This should work on all systems. // Suggestion: use uint16 all the time. This should work on all systems.
#if FSFW_NO_C99_IO == 1 #if FSFW_NO_C99_IO == 1
uint16_t year; uint16_t year;
uint16_t month; uint16_t month;
@ -200,8 +200,8 @@ ReturnValue_t CCSDSTime::convertFromASCII(Clock::TimeOfDay_t* to,
to->usecond = (second - floor(second)) * 1000000; to->usecond = (second - floor(second)) * 1000000;
return RETURN_OK; return RETURN_OK;
} }
// Warning: Compiler/Linker fails ambiguously if library does not implement // Warning: Compiler/Linker fails ambiguously if library does not implement
// C99 I/O // C99 I/O
#else #else
uint16_t year; uint16_t year;
uint8_t month; uint8_t month;
@ -396,12 +396,20 @@ ReturnValue_t CCSDSTime::convertToCcsds(OBT_FLP* to, const timeval* from) {
ReturnValue_t CCSDSTime::convertFromCcsds(timeval* to, const uint8_t* from, ReturnValue_t CCSDSTime::convertFromCcsds(timeval* to, const uint8_t* from,
size_t* foundLength, size_t maxLength) { size_t* foundLength, size_t maxLength) {
//We don't expect ascii here. SHOULDDO if(maxLength >= 19) {
Clock::TimeOfDay_t timeOfDay;
/* Try to parse it as ASCII */
ReturnValue_t result = convertFromASCII(&timeOfDay, from, maxLength);
if (result == RETURN_OK) {
return Clock::convertTimeOfDayToTimeval(&timeOfDay, to);
}
}
uint8_t codeIdentification = (*from >> 4); uint8_t codeIdentification = (*from >> 4);
switch (codeIdentification) { switch (codeIdentification) {
//unsupported, as Leap second correction would have to be applied /* Unsupported, as Leap second correction would have to be applied */
// case CUC_LEVEL1: case CUC_LEVEL1:
// return convertFromCUC(to, from, foundLength, maxLength); return UNSUPPORTED_TIME_FORMAT;
case CDS: case CDS:
return convertFromCDS(to, from, foundLength, maxLength); return convertFromCDS(to, from, foundLength, maxLength);
case CCS: case CCS:
@ -486,7 +494,7 @@ ReturnValue_t CCSDSTime::checkTimeOfDay(const Clock::TimeOfDay_t* time) {
ReturnValue_t CCSDSTime::convertTimevalToTimeOfDay(Clock::TimeOfDay_t* to, ReturnValue_t CCSDSTime::convertTimevalToTimeOfDay(Clock::TimeOfDay_t* to,
timeval* from) { timeval* from) {
//This is rather tricky. Implement only if needed. Also, if so, move to OSAL. //This is rather tricky. Implement only if needed. Also, if so, move to OSAL.
return UNSUPPORTED_TIME_FORMAT; return UNSUPPORTED_TIME_FORMAT;
} }
@ -494,11 +502,11 @@ ReturnValue_t CCSDSTime::convertFromCDS(timeval* to, const uint8_t* from,
size_t* foundLength, size_t maxLength) { size_t* foundLength, size_t maxLength) {
uint8_t pField = *from; uint8_t pField = *from;
from++; from++;
//Check epoch //Check epoch
if (pField & 0b1000) { if (pField & 0b1000) {
return NOT_ENOUGH_INFORMATION_FOR_TARGET_FORMAT; return NOT_ENOUGH_INFORMATION_FOR_TARGET_FORMAT;
} }
//Check length //Check length
uint8_t expectedLength = 7; //Including p-Field. uint8_t expectedLength = 7; //Including p-Field.
bool extendedDays = pField & 0b100; bool extendedDays = pField & 0b100;
if (extendedDays) { if (extendedDays) {
@ -515,7 +523,7 @@ ReturnValue_t CCSDSTime::convertFromCDS(timeval* to, const uint8_t* from,
if (expectedLength > maxLength) { if (expectedLength > maxLength) {
return LENGTH_MISMATCH; return LENGTH_MISMATCH;
} }
//Check and count days //Check and count days
uint32_t days = 0; uint32_t days = 0;
if (extendedDays) { if (extendedDays) {
days = (from[0] << 16) + (from[1] << 8) + from[2]; days = (from[0] << 16) + (from[1] << 8) + from[2];
@ -524,7 +532,7 @@ ReturnValue_t CCSDSTime::convertFromCDS(timeval* to, const uint8_t* from,
days = (from[0] << 8) + from[1]; days = (from[0] << 8) + from[1];
from += 2; from += 2;
} }
//Move to POSIX epoch. //Move to POSIX epoch.
if (days <= DAYS_CCSDS_TO_UNIX_EPOCH) { if (days <= DAYS_CCSDS_TO_UNIX_EPOCH) {
return INVALID_TIME_FORMAT; return INVALID_TIME_FORMAT;
} }

View File

@ -70,7 +70,7 @@ TEST_CASE( "Action Helper" , "[ActionHelper]") {
SECTION("Handle finish"){ SECTION("Handle finish"){
CHECK(not testMqMock.wasMessageSent()); CHECK(not testMqMock.wasMessageSent());
ReturnValue_t status = 0x9876; ReturnValue_t status = 0x9876;
actionHelper.finish(testMqMock.getId(), testActionId, status); actionHelper.finish(true, testMqMock.getId(), testActionId, status);
CHECK(testMqMock.wasMessageSent()); CHECK(testMqMock.wasMessageSent());
CommandMessage testMessage; CommandMessage testMessage;
REQUIRE(testMqMock.receiveMessage(&testMessage) == static_cast<uint32_t>(HasReturnvaluesIF::RETURN_OK)); REQUIRE(testMqMock.receiveMessage(&testMessage) == static_cast<uint32_t>(HasReturnvaluesIF::RETURN_OK));

View File

@ -192,7 +192,7 @@ public:
resetSubscriptionList(); resetSubscriptionList();
ReturnValue_t status = HasReturnvaluesIF::RETURN_OK; ReturnValue_t status = HasReturnvaluesIF::RETURN_OK;
{ {
PoolReadHelper readHelper(&dataset); PoolReadGuard readHelper(&dataset);
if(readHelper.getReadResult() != HasReturnvaluesIF::RETURN_OK) { if(readHelper.getReadResult() != HasReturnvaluesIF::RETURN_OK) {
status = readHelper.getReadResult(); status = readHelper.getReadResult();
} }
@ -205,7 +205,7 @@ public:
} }
{ {
PoolReadHelper readHelper(&testUint32); PoolReadGuard readHelper(&testUint32);
if(readHelper.getReadResult() != HasReturnvaluesIF::RETURN_OK) { if(readHelper.getReadResult() != HasReturnvaluesIF::RETURN_OK) {
status = readHelper.getReadResult(); status = readHelper.getReadResult();
} }
@ -214,7 +214,7 @@ public:
} }
{ {
PoolReadHelper readHelper(&testInt64Vec); PoolReadGuard readHelper(&testInt64Vec);
if(readHelper.getReadResult() != HasReturnvaluesIF::RETURN_OK) { if(readHelper.getReadResult() != HasReturnvaluesIF::RETURN_OK) {
status = readHelper.getReadResult(); status = readHelper.getReadResult();
} }