From 9a3cd1d7fc5b069c655ba1bf53a8273df34b9bc8 Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Wed, 3 Mar 2021 15:13:03 +0100 Subject: [PATCH 01/21] success flag not explicitely expected for actions --- action/ActionHelper.cpp | 6 +++--- action/ActionHelper.h | 4 ++-- action/ActionMessage.cpp | 7 ++++--- action/ActionMessage.h | 8 ++++++-- action/SimpleActionHelper.cpp | 2 +- devicehandlers/DeviceHandlerBase.cpp | 8 ++++++-- unittest/tests/action/TestActionHelper.cpp | 2 +- 7 files changed, 23 insertions(+), 14 deletions(-) diff --git a/action/ActionHelper.cpp b/action/ActionHelper.cpp index e32be68b2..42ee4022f 100644 --- a/action/ActionHelper.cpp +++ b/action/ActionHelper.cpp @@ -43,10 +43,10 @@ void ActionHelper::step(uint8_t step, MessageQueueId_t reportTo, 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) { CommandMessage reply; - ActionMessage::setCompletionReply(&reply, commandId, result); + ActionMessage::setCompletionReply(success, &reply, commandId, result); queueToUse->sendMessage(reportTo, &reply); } @@ -69,7 +69,7 @@ void ActionHelper::prepareExecution(MessageQueueId_t commandedBy, ipcStore->deleteData(dataAddress); if(result == HasActionsIF::EXECUTION_FINISHED) { CommandMessage reply; - ActionMessage::setCompletionReply(&reply, actionId, result); + ActionMessage::setCompletionReply(true, &reply, actionId, result); queueToUse->sendMessage(commandedBy, &reply); } if (result != HasReturnvaluesIF::RETURN_OK) { diff --git a/action/ActionHelper.h b/action/ActionHelper.h index 35ac41d1c..c90247478 100644 --- a/action/ActionHelper.h +++ b/action/ActionHelper.h @@ -62,12 +62,12 @@ public: ReturnValue_t result = HasReturnvaluesIF::RETURN_OK); /** * 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 commandId ID of the executed command * @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); /** * Function to be called by the owner if an action does report data. diff --git a/action/ActionMessage.cpp b/action/ActionMessage.cpp index c3eb47109..15b59b9dc 100644 --- a/action/ActionMessage.cpp +++ b/action/ActionMessage.cpp @@ -53,11 +53,12 @@ void ActionMessage::setDataReply(CommandMessage* message, ActionId_t actionId, message->setParameter2(data.raw); } -void ActionMessage::setCompletionReply(CommandMessage* message, +void ActionMessage::setCompletionReply(bool success, CommandMessage* message, ActionId_t fid, ReturnValue_t result) { - if (result == HasReturnvaluesIF::RETURN_OK or result == HasActionsIF::EXECUTION_FINISHED) { + if (success) { message->setCommand(COMPLETION_SUCCESS); - } else { + } + else { message->setCommand(COMPLETION_FAILED); } message->setParameter(fid); diff --git a/action/ActionMessage.h b/action/ActionMessage.h index 246ac601d..df97485b9 100644 --- a/action/ActionMessage.h +++ b/action/ActionMessage.h @@ -24,19 +24,23 @@ public: 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_FAILED = MAKE_COMMAND_ID(6); + virtual ~ActionMessage(); static void setCommand(CommandMessage* message, ActionId_t fid, store_address_t parameters); + 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, uint8_t step, ReturnValue_t result = HasReturnvaluesIF::RETURN_OK); static uint8_t getStep(const CommandMessage* message ); static ReturnValue_t getReturnCode(const CommandMessage* message ); static void setDataReply(CommandMessage* message, ActionId_t actionId, 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); + static void clear(CommandMessage* message); }; diff --git a/action/SimpleActionHelper.cpp b/action/SimpleActionHelper.cpp index f77d01473..979ee3ad4 100644 --- a/action/SimpleActionHelper.cpp +++ b/action/SimpleActionHelper.cpp @@ -62,7 +62,7 @@ void SimpleActionHelper::prepareExecution(MessageQueueId_t commandedBy, stepCount++; break; case HasActionsIF::EXECUTION_FINISHED: - ActionMessage::setCompletionReply(&reply, actionId, + ActionMessage::setCompletionReply(true, &reply, actionId, HasReturnvaluesIF::RETURN_OK); queueToUse->sendMessage(commandedBy, &reply); break; diff --git a/devicehandlers/DeviceHandlerBase.cpp b/devicehandlers/DeviceHandlerBase.cpp index 35d34bf9b..15eac11f4 100644 --- a/devicehandlers/DeviceHandlerBase.cpp +++ b/devicehandlers/DeviceHandlerBase.cpp @@ -558,7 +558,7 @@ void DeviceHandlerBase::replyToCommand(ReturnValue_t status, if (cookieInfo.pendingCommand->second.sendReplyTo != NO_COMMANDER) { MessageQueueId_t queueId = cookieInfo.pendingCommand->second.sendReplyTo; if (status == NO_REPLY_EXPECTED) { - actionHelper.finish(queueId, cookieInfo.pendingCommand->first, + actionHelper.finish(true, queueId, cookieInfo.pendingCommand->first, RETURN_OK); } else { 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. // Don't send any replies in that case. 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; } diff --git a/unittest/tests/action/TestActionHelper.cpp b/unittest/tests/action/TestActionHelper.cpp index 2493a6b8e..a7adfc82d 100644 --- a/unittest/tests/action/TestActionHelper.cpp +++ b/unittest/tests/action/TestActionHelper.cpp @@ -70,7 +70,7 @@ TEST_CASE( "Action Helper" , "[ActionHelper]") { SECTION("Handle finish"){ CHECK(not testMqMock.wasMessageSent()); ReturnValue_t status = 0x9876; - actionHelper.finish(testMqMock.getId(), testActionId, status); + actionHelper.finish(true, testMqMock.getId(), testActionId, status); CHECK(testMqMock.wasMessageSent()); CommandMessage testMessage; REQUIRE(testMqMock.receiveMessage(&testMessage) == static_cast(HasReturnvaluesIF::RETURN_OK)); From 6580aa73bf587afddcdfd908844dabe4add36489 Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Wed, 3 Mar 2021 17:13:37 +0100 Subject: [PATCH 02/21] updated clock module --- osal/FreeRTOS/Clock.cpp | 214 +++++++++++++++++++------------------- timemanager/CCSDSTime.cpp | 16 ++- 2 files changed, 119 insertions(+), 111 deletions(-) diff --git a/osal/FreeRTOS/Clock.cpp b/osal/FreeRTOS/Clock.cpp index 9c0a02679..806edcc7c 100644 --- a/osal/FreeRTOS/Clock.cpp +++ b/osal/FreeRTOS/Clock.cpp @@ -16,56 +16,56 @@ uint16_t Clock::leapSeconds = 0; MutexIF* Clock::timeMutex = nullptr; uint32_t Clock::getTicksPerSecond(void) { - return 1000; + return 1000; } ReturnValue_t Clock::setClock(const TimeOfDay_t* time) { - timeval time_timeval; + timeval time_timeval; - ReturnValue_t result = convertTimeOfDayToTimeval(time, &time_timeval); - if (result != HasReturnvaluesIF::RETURN_OK){ - return result; - } + ReturnValue_t result = convertTimeOfDayToTimeval(time, &time_timeval); + if (result != HasReturnvaluesIF::RETURN_OK){ + return result; + } - return setClock(&time_timeval); + return setClock(&time_timeval); } ReturnValue_t Clock::setClock(const timeval* time) { - timeval uptime = getUptime(); + timeval uptime = getUptime(); - timeval offset = *time - uptime; + timeval offset = *time - uptime; - Timekeeper::instance()->setOffset(offset); + Timekeeper::instance()->setOffset(offset); - return HasReturnvaluesIF::RETURN_OK; + return HasReturnvaluesIF::RETURN_OK; } ReturnValue_t Clock::getClock_timeval(timeval* time) { - timeval uptime = getUptime(); + timeval uptime = getUptime(); - timeval offset = Timekeeper::instance()->getOffset(); + timeval offset = Timekeeper::instance()->getOffset(); - *time = offset + uptime; + *time = offset + uptime; - return HasReturnvaluesIF::RETURN_OK; + return HasReturnvaluesIF::RETURN_OK; } ReturnValue_t Clock::getUptime(timeval* uptime) { - *uptime = getUptime(); + *uptime = getUptime(); - return HasReturnvaluesIF::RETURN_OK; + return HasReturnvaluesIF::RETURN_OK; } timeval Clock::getUptime() { - TickType_t ticksSinceStart = xTaskGetTickCount(); - return Timekeeper::ticksToTimeval(ticksSinceStart); + TickType_t ticksSinceStart = xTaskGetTickCount(); + return Timekeeper::ticksToTimeval(ticksSinceStart); } ReturnValue_t Clock::getUptime(uint32_t* uptimeMs) { - timeval uptime = getUptime(); - *uptimeMs = uptime.tv_sec * 1000 + uptime.tv_usec / 1000; - return HasReturnvaluesIF::RETURN_OK; + timeval uptime = getUptime(); + *uptimeMs = uptime.tv_sec * 1000 + uptime.tv_usec / 1000; + return HasReturnvaluesIF::RETURN_OK; } @@ -76,129 +76,129 @@ ReturnValue_t Clock::getUptime(uint32_t* uptimeMs) { ReturnValue_t Clock::getClock_usecs(uint64_t* time) { - timeval time_timeval; - ReturnValue_t result = getClock_timeval(&time_timeval); - if (result != HasReturnvaluesIF::RETURN_OK) { - return result; - } - *time = time_timeval.tv_sec * 1000000 + time_timeval.tv_usec; - return HasReturnvaluesIF::RETURN_OK; + timeval time_timeval; + ReturnValue_t result = getClock_timeval(&time_timeval); + if (result != HasReturnvaluesIF::RETURN_OK) { + return result; + } + *time = time_timeval.tv_sec * 1000000 + time_timeval.tv_usec; + return HasReturnvaluesIF::RETURN_OK; } ReturnValue_t Clock::getDateAndTime(TimeOfDay_t* time) { - timeval time_timeval; - ReturnValue_t result = getClock_timeval(&time_timeval); - if (result != HasReturnvaluesIF::RETURN_OK) { - return result; - } - struct tm time_tm; + timeval time_timeval; + ReturnValue_t result = getClock_timeval(&time_timeval); + if (result != HasReturnvaluesIF::RETURN_OK) { + return result; + } + struct tm time_tm; - gmtime_r(&time_timeval.tv_sec,&time_tm); + gmtime_r(&time_timeval.tv_sec,&time_tm); - time->year = time_tm.tm_year + 1900; - time->month = time_tm.tm_mon + 1; - time->day = time_tm.tm_mday; + time->year = time_tm.tm_year + 1900; + time->month = time_tm.tm_mon + 1; + time->day = time_tm.tm_mday; - time->hour = time_tm.tm_hour; - time->minute = time_tm.tm_min; - time->second = time_tm.tm_sec; + time->hour = time_tm.tm_hour; + time->minute = time_tm.tm_min; + time->second = time_tm.tm_sec; - time->usecond = time_timeval.tv_usec; + time->usecond = time_timeval.tv_usec; - return HasReturnvaluesIF::RETURN_OK; + return HasReturnvaluesIF::RETURN_OK; } ReturnValue_t Clock::convertTimeOfDayToTimeval(const TimeOfDay_t* from, - timeval* to) { - struct tm time_tm; + timeval* to) { + struct tm time_tm; - time_tm.tm_year = from->year - 1900; - time_tm.tm_mon = from->month - 1; - time_tm.tm_mday = from->day; + time_tm.tm_year = from->year - 1900; + time_tm.tm_mon = from->month - 1; + time_tm.tm_mday = from->day; - time_tm.tm_hour = from->hour; - time_tm.tm_min = from->minute; - time_tm.tm_sec = from->second; + time_tm.tm_hour = from->hour; + time_tm.tm_min = from->minute; + time_tm.tm_sec = from->second; - time_t seconds = mktime(&time_tm); + time_t seconds = mktime(&time_tm); - to->tv_sec = seconds; - to->tv_usec = from->usecond; - //Fails in 2038.. - return HasReturnvaluesIF::RETURN_OK; + to->tv_sec = seconds; + to->tv_usec = from->usecond; + //Fails in 2038.. + return HasReturnvaluesIF::RETURN_OK; } ReturnValue_t Clock::convertTimevalToJD2000(timeval time, double* JD2000) { - *JD2000 = (time.tv_sec - 946728000. + time.tv_usec / 1000000.) / 24. - / 3600.; - return HasReturnvaluesIF::RETURN_OK; + *JD2000 = (time.tv_sec - 946728000. + time.tv_usec / 1000000.) / 24. + / 3600.; + return HasReturnvaluesIF::RETURN_OK; } ReturnValue_t Clock::convertUTCToTT(timeval utc, timeval* tt) { - //SHOULDDO: works not for dates in the past (might have less leap seconds) - if (timeMutex == nullptr) { - return HasReturnvaluesIF::RETURN_FAILED; - } + //SHOULDDO: works not for dates in the past (might have less leap seconds) + if (timeMutex == nullptr) { + return HasReturnvaluesIF::RETURN_FAILED; + } - uint16_t leapSeconds; - ReturnValue_t result = getLeapSeconds(&leapSeconds); - if (result != HasReturnvaluesIF::RETURN_OK) { - return result; - } - timeval leapSeconds_timeval = { 0, 0 }; - leapSeconds_timeval.tv_sec = leapSeconds; + uint16_t leapSeconds; + ReturnValue_t result = getLeapSeconds(&leapSeconds); + if (result != HasReturnvaluesIF::RETURN_OK) { + return result; + } + timeval leapSeconds_timeval = { 0, 0 }; + leapSeconds_timeval.tv_sec = leapSeconds; - //initial offset between UTC and TAI - timeval UTCtoTAI1972 = { 10, 0 }; + //initial offset between UTC and TAI + timeval UTCtoTAI1972 = { 10, 0 }; - timeval TAItoTT = { 32, 184000 }; + timeval TAItoTT = { 32, 184000 }; - *tt = utc + leapSeconds_timeval + UTCtoTAI1972 + TAItoTT; + *tt = utc + leapSeconds_timeval + UTCtoTAI1972 + TAItoTT; - return HasReturnvaluesIF::RETURN_OK; + return HasReturnvaluesIF::RETURN_OK; } ReturnValue_t Clock::setLeapSeconds(const uint16_t leapSeconds_) { - if (checkOrCreateClockMutex() != HasReturnvaluesIF::RETURN_OK) { - return HasReturnvaluesIF::RETURN_FAILED; - } - ReturnValue_t result = timeMutex->lockMutex(MutexIF::TimeoutType::BLOCKING); - if (result != HasReturnvaluesIF::RETURN_OK) { - return result; - } + if (checkOrCreateClockMutex() != HasReturnvaluesIF::RETURN_OK) { + return HasReturnvaluesIF::RETURN_FAILED; + } + ReturnValue_t result = timeMutex->lockMutex(MutexIF::TimeoutType::BLOCKING); + if (result != HasReturnvaluesIF::RETURN_OK) { + return result; + } - leapSeconds = leapSeconds_; + leapSeconds = leapSeconds_; - result = timeMutex->unlockMutex(); - return result; + result = timeMutex->unlockMutex(); + return result; } ReturnValue_t Clock::getLeapSeconds(uint16_t* leapSeconds_) { - if (timeMutex == NULL) { - return HasReturnvaluesIF::RETURN_FAILED; - } - ReturnValue_t result = timeMutex->lockMutex(MutexIF::TimeoutType::BLOCKING); - if (result != HasReturnvaluesIF::RETURN_OK) { - return result; - } + if (timeMutex == NULL) { + return HasReturnvaluesIF::RETURN_FAILED; + } + ReturnValue_t result = timeMutex->lockMutex(MutexIF::TimeoutType::BLOCKING); + if (result != HasReturnvaluesIF::RETURN_OK) { + return result; + } - *leapSeconds_ = leapSeconds; + *leapSeconds_ = leapSeconds; - result = timeMutex->unlockMutex(); - return result; + result = timeMutex->unlockMutex(); + return result; } ReturnValue_t Clock::checkOrCreateClockMutex() { - if (timeMutex == NULL) { - MutexFactory* mutexFactory = MutexFactory::instance(); - if (mutexFactory == NULL) { - return HasReturnvaluesIF::RETURN_FAILED; - } - timeMutex = mutexFactory->createMutex(); - if (timeMutex == NULL) { - return HasReturnvaluesIF::RETURN_FAILED; - } - } - return HasReturnvaluesIF::RETURN_OK; + if (timeMutex == NULL) { + MutexFactory* mutexFactory = MutexFactory::instance(); + if (mutexFactory == NULL) { + return HasReturnvaluesIF::RETURN_FAILED; + } + timeMutex = mutexFactory->createMutex(); + if (timeMutex == NULL) { + return HasReturnvaluesIF::RETURN_FAILED; + } + } + return HasReturnvaluesIF::RETURN_OK; } diff --git a/timemanager/CCSDSTime.cpp b/timemanager/CCSDSTime.cpp index aefcac2eb..8f0d1c314 100644 --- a/timemanager/CCSDSTime.cpp +++ b/timemanager/CCSDSTime.cpp @@ -396,12 +396,20 @@ ReturnValue_t CCSDSTime::convertToCcsds(OBT_FLP* to, const timeval* from) { ReturnValue_t CCSDSTime::convertFromCcsds(timeval* to, const uint8_t* from, 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); switch (codeIdentification) { - //unsupported, as Leap second correction would have to be applied -// case CUC_LEVEL1: -// return convertFromCUC(to, from, foundLength, maxLength); + /* Unsupported, as Leap second correction would have to be applied */ + case CUC_LEVEL1: + return UNSUPPORTED_TIME_FORMAT; case CDS: return convertFromCDS(to, from, foundLength, maxLength); case CCS: From bff8d103ba77837b63b91577ff30c52926d625ef Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 3 Mar 2021 22:45:44 +0100 Subject: [PATCH 03/21] form stuff and cleaning up --- datapoollocal/LocalDataPoolManager.cpp | 8 ++++---- datapoollocal/LocalPoolVariable.tpp | 10 ---------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/datapoollocal/LocalDataPoolManager.cpp b/datapoollocal/LocalDataPoolManager.cpp index c15182d26..72873d993 100644 --- a/datapoollocal/LocalDataPoolManager.cpp +++ b/datapoollocal/LocalDataPoolManager.cpp @@ -42,15 +42,15 @@ LocalDataPoolManager::~LocalDataPoolManager() {} ReturnValue_t LocalDataPoolManager::initialize(MessageQueueIF* queueToUse) { if(queueToUse == nullptr) { - // error, all destinations invalid - printWarningOrError(sif::OutputTypes::OUT_ERROR, - "initialize", QUEUE_OR_DESTINATION_INVALID); + /* Error, all destinations invalid */ + printWarningOrError(sif::OutputTypes::OUT_ERROR, "initialize", + QUEUE_OR_DESTINATION_INVALID); } hkQueue = queueToUse; ipcStore = objectManager->get(objects::IPC_STORE); if(ipcStore == nullptr) { - // error, all destinations invalid + /* Error, all destinations invalid */ printWarningOrError(sif::OutputTypes::OUT_ERROR, "initialize", HasReturnvaluesIF::RETURN_FAILED, "Could not set IPC store."); diff --git a/datapoollocal/LocalPoolVariable.tpp b/datapoollocal/LocalPoolVariable.tpp index 35de4ebdf..aa789a6dd 100644 --- a/datapoollocal/LocalPoolVariable.tpp +++ b/datapoollocal/LocalPoolVariable.tpp @@ -43,7 +43,6 @@ inline ReturnValue_t LocalPoolVariable::readWithoutLock() { PoolEntry* poolEntry = nullptr; ReturnValue_t result = LocalDpManagerAttorney::fetchPoolEntry(*hkManager, localPoolId, &poolEntry); - //ReturnValue_t result = hkManager->fetchPoolEntry(localPoolId, &poolEntry); if(result != RETURN_OK) { object_id_t ownerObjectId = hkManager->getCreatorObjectId(); reportReadCommitError("LocalPoolVariable", result, @@ -51,15 +50,6 @@ inline ReturnValue_t LocalPoolVariable::readWithoutLock() { 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->valid = poolEntry->getValid(); return RETURN_OK; From d8d18c93335f3e850deda56aa10ace757d8e6c2a Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 3 Mar 2021 23:49:27 +0100 Subject: [PATCH 04/21] fixed order --- controller/ExtendedControllerBase.cpp | 8 +++++--- datapoollocal/LocalPoolDataSetBase.cpp | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/controller/ExtendedControllerBase.cpp b/controller/ExtendedControllerBase.cpp index cc93efa86..95ba012eb 100644 --- a/controller/ExtendedControllerBase.cpp +++ b/controller/ExtendedControllerBase.cpp @@ -13,7 +13,7 @@ ExtendedControllerBase::~ExtendedControllerBase() { ReturnValue_t ExtendedControllerBase::executeAction(ActionId_t actionId, 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; } @@ -21,7 +21,7 @@ ReturnValue_t ExtendedControllerBase::executeAction(ActionId_t actionId, ReturnValue_t ExtendedControllerBase::initializeLocalDataPool( 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; } @@ -96,8 +96,10 @@ ReturnValue_t ExtendedControllerBase::initializeAfterTaskCreation() { ReturnValue_t ExtendedControllerBase::performOperation(uint8_t opCode) { handleQueue(); - poolManager.performHkOperation(); performControlOperation(); + /* We do this after performing control operation because variables will be set changed + in this function. */ + poolManager.performHkOperation(); return RETURN_OK; } diff --git a/datapoollocal/LocalPoolDataSetBase.cpp b/datapoollocal/LocalPoolDataSetBase.cpp index 75a3ab7c2..d043d18f5 100644 --- a/datapoollocal/LocalPoolDataSetBase.cpp +++ b/datapoollocal/LocalPoolDataSetBase.cpp @@ -42,8 +42,7 @@ LocalPoolDataSetBase::LocalPoolDataSetBase(HasLocalDataPoolIF *hkOwner, } } -LocalPoolDataSetBase::LocalPoolDataSetBase(sid_t sid, - PoolVariableIF** registeredVariablesArray, +LocalPoolDataSetBase::LocalPoolDataSetBase(sid_t sid, PoolVariableIF** registeredVariablesArray, const size_t maxNumberOfVariables): PoolDataSetBase(registeredVariablesArray, maxNumberOfVariables) { HasLocalDataPoolIF* hkOwner = objectManager->get( From dae4a5fa7447dbd9bf49242facbd4c20509e8dc4 Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 16:36:34 +0100 Subject: [PATCH 05/21] small form stuff --- datapoollocal/HasLocalDataPoolIF.h | 4 ++-- datapoollocal/LocalPoolDataSetBase.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/datapoollocal/HasLocalDataPoolIF.h b/datapoollocal/HasLocalDataPoolIF.h index 49f2db7d3..d52c72b6c 100644 --- a/datapoollocal/HasLocalDataPoolIF.h +++ b/datapoollocal/HasLocalDataPoolIF.h @@ -162,8 +162,8 @@ protected: */ virtual LocalPoolObjectBase* getPoolObjectHandle(lp_id_t localPoolId) { #if FSFW_CPP_OSTREAM_ENABLED == 1 - sif::warning << "HasLocalDataPoolIF::getPoolObjectHandle: Not overriden" - << ". Returning nullptr!" << std::endl; + sif::warning << "HasLocalDataPoolIF::getPoolObjectHandle: Not overriden. " + "Returning nullptr!" << std::endl; #else sif::printWarning("HasLocalDataPoolIF::getPoolObjectHandle: " "Not overriden. Returning nullptr!\n"); diff --git a/datapoollocal/LocalPoolDataSetBase.h b/datapoollocal/LocalPoolDataSetBase.h index 8a3a63394..9501f7821 100644 --- a/datapoollocal/LocalPoolDataSetBase.h +++ b/datapoollocal/LocalPoolDataSetBase.h @@ -59,7 +59,7 @@ public: /** * @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 * Unlike the first constructor, no component for periodic handling * will be initiated. From 21a7fd621d69d1058a2a55ddc6a4867021b0fb85 Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 16:38:35 +0100 Subject: [PATCH 06/21] renamed guard class --- datapool/{PoolReadHelper.h => PoolReadGuard.h} | 6 +++--- unittest/tests/datapoollocal/LocalPoolOwnerBase.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) rename datapool/{PoolReadHelper.h => PoolReadGuard.h} (94%) diff --git a/datapool/PoolReadHelper.h b/datapool/PoolReadGuard.h similarity index 94% rename from datapool/PoolReadHelper.h rename to datapool/PoolReadGuard.h index 9825e83c1..eb014f1e7 100644 --- a/datapool/PoolReadHelper.h +++ b/datapool/PoolReadGuard.h @@ -8,9 +8,9 @@ /** * @brief Helper class to read data sets or pool variables */ -class PoolReadHelper { +class PoolReadGuard { public: - PoolReadHelper(ReadCommitIF* readObject, + PoolReadGuard(ReadCommitIF* readObject, MutexIF::TimeoutType timeoutType = MutexIF::TimeoutType::WAITING, uint32_t mutexTimeout = 20): readObject(readObject), mutexTimeout(mutexTimeout) { @@ -42,7 +42,7 @@ public: /** * @brief Default destructor which will take care of commiting changed values. */ - ~PoolReadHelper() { + ~PoolReadGuard() { if(readObject != nullptr and not noCommit) { readObject->commit(timeoutType, mutexTimeout); } diff --git a/unittest/tests/datapoollocal/LocalPoolOwnerBase.h b/unittest/tests/datapoollocal/LocalPoolOwnerBase.h index bb090e3b3..5c277850f 100644 --- a/unittest/tests/datapoollocal/LocalPoolOwnerBase.h +++ b/unittest/tests/datapoollocal/LocalPoolOwnerBase.h @@ -192,7 +192,7 @@ public: resetSubscriptionList(); ReturnValue_t status = HasReturnvaluesIF::RETURN_OK; { - PoolReadHelper readHelper(&dataset); + PoolReadGuard readHelper(&dataset); if(readHelper.getReadResult() != HasReturnvaluesIF::RETURN_OK) { status = readHelper.getReadResult(); } @@ -205,7 +205,7 @@ public: } { - PoolReadHelper readHelper(&testUint32); + PoolReadGuard readHelper(&testUint32); if(readHelper.getReadResult() != HasReturnvaluesIF::RETURN_OK) { status = readHelper.getReadResult(); } @@ -214,7 +214,7 @@ public: } { - PoolReadHelper readHelper(&testInt64Vec); + PoolReadGuard readHelper(&testInt64Vec); if(readHelper.getReadResult() != HasReturnvaluesIF::RETURN_OK) { status = readHelper.getReadResult(); } From d66e486f166f3a9d926be4f270a22caf37f6243e Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 16:44:17 +0100 Subject: [PATCH 07/21] whitespaces instead of tabs --- timemanager/CCSDSTime.cpp | 954 +++++++++++++++++++------------------- timemanager/CCSDSTime.h | 370 +++++++-------- 2 files changed, 662 insertions(+), 662 deletions(-) diff --git a/timemanager/CCSDSTime.cpp b/timemanager/CCSDSTime.cpp index 8f0d1c314..192c1f57a 100644 --- a/timemanager/CCSDSTime.cpp +++ b/timemanager/CCSDSTime.cpp @@ -12,390 +12,390 @@ CCSDSTime::~CCSDSTime() { } ReturnValue_t CCSDSTime::convertToCcsds(Ccs_seconds* to, - const Clock::TimeOfDay_t* from) { - ReturnValue_t result = checkTimeOfDay(from); - if (result != RETURN_OK) { - return result; - } + const Clock::TimeOfDay_t* from) { + ReturnValue_t result = checkTimeOfDay(from); + if (result != RETURN_OK) { + return result; + } - to->pField = (CCS << 4); + to->pField = (CCS << 4); - to->yearMSB = (from->year >> 8); - to->yearLSB = from->year & 0xff; - to->month = from->month; - to->day = from->day; - to->hour = from->hour; - to->minute = from->minute; - to->second = from->second; + to->yearMSB = (from->year >> 8); + to->yearLSB = from->year & 0xff; + to->month = from->month; + to->day = from->day; + to->hour = from->hour; + to->minute = from->minute; + to->second = from->second; - return RETURN_OK; + return RETURN_OK; } ReturnValue_t CCSDSTime::convertToCcsds(Ccs_mseconds* to, - const Clock::TimeOfDay_t* from) { - ReturnValue_t result = checkTimeOfDay(from); - if (result != RETURN_OK) { - return result; - } + const Clock::TimeOfDay_t* from) { + ReturnValue_t result = checkTimeOfDay(from); + if (result != RETURN_OK) { + return result; + } - to->pField = (CCS << 4) + 2; + to->pField = (CCS << 4) + 2; - to->yearMSB = (from->year >> 8); - to->yearLSB = from->year & 0xff; - to->month = from->month; - to->day = from->day; - to->hour = from->hour; - to->minute = from->minute; - to->second = from->second; - to->secondEminus2 = from->usecond / 10000; - to->secondEminus4 = (from->usecond % 10000) / 100; + to->yearMSB = (from->year >> 8); + to->yearLSB = from->year & 0xff; + to->month = from->month; + to->day = from->day; + to->hour = from->hour; + to->minute = from->minute; + to->second = from->second; + to->secondEminus2 = from->usecond / 10000; + to->secondEminus4 = (from->usecond % 10000) / 100; - return RETURN_OK; + return RETURN_OK; } ReturnValue_t CCSDSTime::convertFromCcsds(Clock::TimeOfDay_t* to, const uint8_t* from, size_t length) { - ReturnValue_t result; - if (length > 0xFF) { - return LENGTH_MISMATCH; - } - result = convertFromASCII(to, from, length); //Try to parse it as ASCII - if (result == RETURN_OK) { - return RETURN_OK; - } + ReturnValue_t result; + if (length > 0xFF) { + return LENGTH_MISMATCH; + } + result = convertFromASCII(to, from, length); //Try to parse it as ASCII + if (result == RETURN_OK) { + return RETURN_OK; + } - //Seems to be no ascii, try the other formats - uint8_t codeIdentification = (*from >> 4); - switch (codeIdentification) { - case CUC_LEVEL1: //CUC_LEVEL2 can not be converted to TimeOfDay (ToD is Level 1) <- Well, if we know the epoch, we can... <- see bug 1133 - return convertFromCUC(to, from, length); - case CDS: - return convertFromCDS(to, from, length); - case CCS: { - size_t temp = 0; - return convertFromCCS(to, from, &temp, length); - } + //Seems to be no ascii, try the other formats + uint8_t codeIdentification = (*from >> 4); + switch (codeIdentification) { + case CUC_LEVEL1: //CUC_LEVEL2 can not be converted to TimeOfDay (ToD is Level 1) <- Well, if we know the epoch, we can... <- see bug 1133 + return convertFromCUC(to, from, length); + case CDS: + return convertFromCDS(to, from, length); + case CCS: { + size_t temp = 0; + return convertFromCCS(to, from, &temp, length); + } - default: - return UNSUPPORTED_TIME_FORMAT; - } + default: + return UNSUPPORTED_TIME_FORMAT; + } } ReturnValue_t CCSDSTime::convertFromCUC(Clock::TimeOfDay_t* to, const uint8_t* from, uint8_t length) { - return UNSUPPORTED_TIME_FORMAT; + return UNSUPPORTED_TIME_FORMAT; } ReturnValue_t CCSDSTime::convertFromCDS(Clock::TimeOfDay_t* to, const uint8_t* from, uint8_t length) { - timeval time; - ReturnValue_t result = convertFromCDS(&time, from, NULL, length); - if (result != HasReturnvaluesIF::RETURN_OK) { - return result; - } - return convertTimevalToTimeOfDay(to, &time); + timeval time; + ReturnValue_t result = convertFromCDS(&time, from, NULL, length); + if (result != HasReturnvaluesIF::RETURN_OK) { + return result; + } + return convertTimevalToTimeOfDay(to, &time); } ReturnValue_t CCSDSTime::convertFromCCS(Clock::TimeOfDay_t* to, const uint8_t* from, size_t* foundLength, size_t maxLength) { - uint8_t subsecondsLength = *from & 0b111; - uint32_t totalLength = subsecondsLength + 8; - if (maxLength < totalLength) { - return LENGTH_MISMATCH; - } + uint8_t subsecondsLength = *from & 0b111; + uint32_t totalLength = subsecondsLength + 8; + if (maxLength < totalLength) { + return LENGTH_MISMATCH; + } - *foundLength = totalLength; + *foundLength = totalLength; - ReturnValue_t result = checkCcs(from, maxLength); + ReturnValue_t result = checkCcs(from, maxLength); - if (result != RETURN_OK) { - return result; - } + if (result != RETURN_OK) { + return result; + } - Ccs_mseconds *temp = (Ccs_mseconds *) from; + Ccs_mseconds *temp = (Ccs_mseconds *) from; - to->year = (temp->yearMSB << 8) + temp->yearLSB; - to->hour = temp->hour; - to->minute = temp->minute; - to->second = temp->second; + to->year = (temp->yearMSB << 8) + temp->yearLSB; + to->hour = temp->hour; + to->minute = temp->minute; + to->second = temp->second; - if (temp->pField & (1 << 3)) { //day of year variation - uint16_t tempDay = (temp->month << 8) + temp->day; - result = convertDaysOfYear(tempDay, to->year, - &(temp->month), &(temp->day)); - if (result != RETURN_OK) { - return result; - } - } + if (temp->pField & (1 << 3)) { //day of year variation + uint16_t tempDay = (temp->month << 8) + temp->day; + result = convertDaysOfYear(tempDay, to->year, + &(temp->month), &(temp->day)); + if (result != RETURN_OK) { + return result; + } + } - to->month = temp->month; - to->day = temp->day; + to->month = temp->month; + to->day = temp->day; - to->usecond = 0; - if (subsecondsLength > 0) { - *foundLength += 1; - if (temp->secondEminus2 >= 100) { - return INVALID_TIME_FORMAT; - } - to->usecond = temp->secondEminus2 * 10000; - } + to->usecond = 0; + if (subsecondsLength > 0) { + *foundLength += 1; + if (temp->secondEminus2 >= 100) { + return INVALID_TIME_FORMAT; + } + to->usecond = temp->secondEminus2 * 10000; + } - if (subsecondsLength > 1) { - *foundLength += 1; - if (temp->secondEminus4 >= 100) { - return INVALID_TIME_FORMAT; - } - to->usecond += temp->secondEminus4 * 100; - } + if (subsecondsLength > 1) { + *foundLength += 1; + if (temp->secondEminus4 >= 100) { + return INVALID_TIME_FORMAT; + } + to->usecond += temp->secondEminus4 * 100; + } - return RETURN_OK; + return RETURN_OK; } ReturnValue_t CCSDSTime::convertFromASCII(Clock::TimeOfDay_t* to, const uint8_t* from, uint8_t length) { - if (length < 19) { - return RETURN_FAILED; - } -// 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. + if (length < 19) { + return RETURN_FAILED; + } + // 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. #if FSFW_NO_C99_IO == 1 - uint16_t year; - uint16_t month; - uint16_t day; - uint16_t hour; - uint16_t minute; - float second; - int count = sscanf((char *) from, "%4" SCNu16 "-%2" SCNu16 "-%2" SCNu16 "T%" - "2" SCNu16 ":%2" SCNu16 ":%fZ", &year, &month, &day, &hour, - &minute, &second); - if (count == 6) { - to->year = year; - to->month = month; - to->day = day; - to->hour = hour; - to->minute = minute; - to->second = second; - to->usecond = (second - floor(second)) * 1000000; - return RETURN_OK; - } + uint16_t year; + uint16_t month; + uint16_t day; + uint16_t hour; + uint16_t minute; + float second; + int count = sscanf((char *) from, "%4" SCNu16 "-%2" SCNu16 "-%2" SCNu16 "T%" + "2" SCNu16 ":%2" SCNu16 ":%fZ", &year, &month, &day, &hour, + &minute, &second); + if (count == 6) { + to->year = year; + to->month = month; + to->day = day; + to->hour = hour; + to->minute = minute; + to->second = second; + to->usecond = (second - floor(second)) * 1000000; + return RETURN_OK; + } - // try Code B (yyyy-ddd) - count = sscanf((char *) from, "%4" SCNu16 "-%3" SCNu16 "T%2" SCNu16 ":%" - "2" SCNu16 ":%fZ", &year, &day, &hour, &minute, &second); - if (count == 5) { - uint8_t tempDay; - ReturnValue_t result = CCSDSTime::convertDaysOfYear(day, year, - reinterpret_cast(&month), - reinterpret_cast(&tempDay)); - if (result != RETURN_OK) { - return RETURN_FAILED; - } - to->year = year; - to->month = month; - to->day = tempDay; - to->hour = hour; - to->minute = minute; - to->second = second; - to->usecond = (second - floor(second)) * 1000000; - return RETURN_OK; - } -// Warning: Compiler/Linker fails ambiguously if library does not implement -// C99 I/O + // try Code B (yyyy-ddd) + count = sscanf((char *) from, "%4" SCNu16 "-%3" SCNu16 "T%2" SCNu16 ":%" + "2" SCNu16 ":%fZ", &year, &day, &hour, &minute, &second); + if (count == 5) { + uint8_t tempDay; + ReturnValue_t result = CCSDSTime::convertDaysOfYear(day, year, + reinterpret_cast(&month), + reinterpret_cast(&tempDay)); + if (result != RETURN_OK) { + return RETURN_FAILED; + } + to->year = year; + to->month = month; + to->day = tempDay; + to->hour = hour; + to->minute = minute; + to->second = second; + to->usecond = (second - floor(second)) * 1000000; + return RETURN_OK; + } + // Warning: Compiler/Linker fails ambiguously if library does not implement + // C99 I/O #else - uint16_t year; - uint8_t month; - uint16_t day; - uint8_t hour; - uint8_t minute; - float second; + uint16_t year; + uint8_t month; + uint16_t day; + uint8_t hour; + uint8_t minute; + float second; //try Code A (yyyy-mm-dd) int count = sscanf((char *) from, "%4" SCNu16 "-%2" SCNu8 "-%2" SCNu16 "T%2" SCNu8 ":%2" SCNu8 ":%fZ", &year, &month, &day, &hour, &minute, &second); - if (count == 6) { - to->year = year; - to->month = month; - to->day = day; - to->hour = hour; - to->minute = minute; - to->second = second; - to->usecond = (second - floor(second)) * 1000000; - return RETURN_OK; - } + if (count == 6) { + to->year = year; + to->month = month; + to->day = day; + to->hour = hour; + to->minute = minute; + to->second = second; + to->usecond = (second - floor(second)) * 1000000; + return RETURN_OK; + } //try Code B (yyyy-ddd) count = sscanf((char *) from, "%4" SCNu16 "-%3" SCNu16 "T%2" SCNu8 ":%2" SCNu8 ":%fZ", &year, &day, &hour, &minute, &second); - if (count == 5) { - uint8_t tempDay; - ReturnValue_t result = CCSDSTime::convertDaysOfYear(day, year, &month, - &tempDay); - if (result != RETURN_OK) { - return RETURN_FAILED; - } - to->year = year; - to->month = month; - to->day = tempDay; - to->hour = hour; - to->minute = minute; - to->second = second; - to->usecond = (second - floor(second)) * 1000000; - return RETURN_OK; - } + if (count == 5) { + uint8_t tempDay; + ReturnValue_t result = CCSDSTime::convertDaysOfYear(day, year, &month, + &tempDay); + if (result != RETURN_OK) { + return RETURN_FAILED; + } + to->year = year; + to->month = month; + to->day = tempDay; + to->hour = hour; + to->minute = minute; + to->second = second; + to->usecond = (second - floor(second)) * 1000000; + return RETURN_OK; + } #endif - return UNSUPPORTED_TIME_FORMAT; + return UNSUPPORTED_TIME_FORMAT; } ReturnValue_t CCSDSTime::checkCcs(const uint8_t* time, uint8_t length) { - Ccs_mseconds *time_struct = (Ccs_mseconds *) time; + Ccs_mseconds *time_struct = (Ccs_mseconds *) time; - uint8_t additionalBytes = time_struct->pField & 0b111; - if ((additionalBytes == 0b111) || (length < (additionalBytes + 8))) { - return INVALID_TIME_FORMAT; - } + uint8_t additionalBytes = time_struct->pField & 0b111; + if ((additionalBytes == 0b111) || (length < (additionalBytes + 8))) { + return INVALID_TIME_FORMAT; + } - if (time_struct->pField & (1 << 3)) { //day of year variation - uint16_t day = (time_struct->month << 8) + time_struct->day; - if (day > 366) { - return INVALID_TIME_FORMAT; - } - } else { - if (time_struct->month > 12) { - return INVALID_TIME_FORMAT; - } - if (time_struct->day > 31) { - return INVALID_TIME_FORMAT; - } - } - if (time_struct->hour > 23) { - return INVALID_TIME_FORMAT; - } - if (time_struct->minute > 59) { - return INVALID_TIME_FORMAT; - } - if (time_struct->second > 59) { - return INVALID_TIME_FORMAT; - } + if (time_struct->pField & (1 << 3)) { //day of year variation + uint16_t day = (time_struct->month << 8) + time_struct->day; + if (day > 366) { + return INVALID_TIME_FORMAT; + } + } else { + if (time_struct->month > 12) { + return INVALID_TIME_FORMAT; + } + if (time_struct->day > 31) { + return INVALID_TIME_FORMAT; + } + } + if (time_struct->hour > 23) { + return INVALID_TIME_FORMAT; + } + if (time_struct->minute > 59) { + return INVALID_TIME_FORMAT; + } + if (time_struct->second > 59) { + return INVALID_TIME_FORMAT; + } - uint8_t *additionalByte = &time_struct->secondEminus2; + uint8_t *additionalByte = &time_struct->secondEminus2; - for (; additionalBytes != 0; additionalBytes--) { - if (*additionalByte++ > 99) { - return INVALID_TIME_FORMAT; - } - } - return RETURN_OK; + for (; additionalBytes != 0; additionalBytes--) { + if (*additionalByte++ > 99) { + return INVALID_TIME_FORMAT; + } + } + return RETURN_OK; } ReturnValue_t CCSDSTime::convertDaysOfYear(uint16_t dayofYear, uint16_t year, - uint8_t* month, uint8_t* day) { - if (isLeapYear(year)) { - if (dayofYear > 366) { - return INVALID_DAY_OF_YEAR; - } - } else { - if (dayofYear > 365) { - return INVALID_DAY_OF_YEAR; - } - } - *month = 1; - if (dayofYear <= 31) { - *day = dayofYear; - return RETURN_OK; - } - *month += 1; - dayofYear -= 31; - if (isLeapYear(year)) { - if (dayofYear <= 29) { - *day = dayofYear; - return RETURN_OK; - } - *month += 1; - dayofYear -= 29; - } else { - if (dayofYear <= 28) { - *day = dayofYear; - return RETURN_OK; - } - *month += 1; - dayofYear -= 28; - } - while (*month <= 12) { - if (dayofYear <= 31) { - *day = dayofYear; - return RETURN_OK; - } - *month += 1; - dayofYear -= 31; + uint8_t* month, uint8_t* day) { + if (isLeapYear(year)) { + if (dayofYear > 366) { + return INVALID_DAY_OF_YEAR; + } + } else { + if (dayofYear > 365) { + return INVALID_DAY_OF_YEAR; + } + } + *month = 1; + if (dayofYear <= 31) { + *day = dayofYear; + return RETURN_OK; + } + *month += 1; + dayofYear -= 31; + if (isLeapYear(year)) { + if (dayofYear <= 29) { + *day = dayofYear; + return RETURN_OK; + } + *month += 1; + dayofYear -= 29; + } else { + if (dayofYear <= 28) { + *day = dayofYear; + return RETURN_OK; + } + *month += 1; + dayofYear -= 28; + } + while (*month <= 12) { + if (dayofYear <= 31) { + *day = dayofYear; + return RETURN_OK; + } + *month += 1; + dayofYear -= 31; - if (*month == 8) { - continue; - } + if (*month == 8) { + continue; + } - if (dayofYear <= 30) { - *day = dayofYear; - return RETURN_OK; - } - *month += 1; - dayofYear -= 30; - } - return INVALID_DAY_OF_YEAR; + if (dayofYear <= 30) { + *day = dayofYear; + return RETURN_OK; + } + *month += 1; + dayofYear -= 30; + } + return INVALID_DAY_OF_YEAR; } bool CCSDSTime::isLeapYear(uint32_t year) { - if ((year % 400) == 0) { - return true; - } - if ((year % 100) == 0) { - return false; - } - if ((year % 4) == 0) { - return true; - } - return false; + if ((year % 400) == 0) { + return true; + } + if ((year % 100) == 0) { + return false; + } + if ((year % 4) == 0) { + return true; + } + return false; } ReturnValue_t CCSDSTime::convertToCcsds(CDS_short* to, const timeval* from) { - to->pField = (CDS << 4) + 0; - uint32_t days = ((from->tv_sec) / SECONDS_PER_DAY) - + DAYS_CCSDS_TO_UNIX_EPOCH; - if (days > (1 << 16)) { - //Date is beyond year 2137 - return TIME_DOES_NOT_FIT_FORMAT; - } - to->dayMSB = (days & 0xFF00) >> 8; - to->dayLSB = (days & 0xFF); - uint32_t msDay = ((from->tv_sec % SECONDS_PER_DAY) * 1000) - + (from->tv_usec / 1000); - to->msDay_hh = (msDay & 0xFF000000) >> 24; - to->msDay_h = (msDay & 0xFF0000) >> 16; - to->msDay_l = (msDay & 0xFF00) >> 8; - to->msDay_ll = (msDay & 0xFF); - return RETURN_OK; + to->pField = (CDS << 4) + 0; + uint32_t days = ((from->tv_sec) / SECONDS_PER_DAY) + + DAYS_CCSDS_TO_UNIX_EPOCH; + if (days > (1 << 16)) { + //Date is beyond year 2137 + return TIME_DOES_NOT_FIT_FORMAT; + } + to->dayMSB = (days & 0xFF00) >> 8; + to->dayLSB = (days & 0xFF); + uint32_t msDay = ((from->tv_sec % SECONDS_PER_DAY) * 1000) + + (from->tv_usec / 1000); + to->msDay_hh = (msDay & 0xFF000000) >> 24; + to->msDay_h = (msDay & 0xFF0000) >> 16; + to->msDay_l = (msDay & 0xFF00) >> 8; + to->msDay_ll = (msDay & 0xFF); + return RETURN_OK; } ReturnValue_t CCSDSTime::convertToCcsds(OBT_FLP* to, const timeval* from) { - to->pFiled = (AGENCY_DEFINED << 4) + 5; - to->seconds_hh = (from->tv_sec >> 24) & 0xff; - to->seconds_h = (from->tv_sec >> 16) & 0xff; - to->seconds_l = (from->tv_sec >> 8) & 0xff; - to->seconds_ll = (from->tv_sec >> 0) & 0xff; + to->pFiled = (AGENCY_DEFINED << 4) + 5; + to->seconds_hh = (from->tv_sec >> 24) & 0xff; + to->seconds_h = (from->tv_sec >> 16) & 0xff; + to->seconds_l = (from->tv_sec >> 8) & 0xff; + to->seconds_ll = (from->tv_sec >> 0) & 0xff; - //convert the µs to 2E-16 seconds - uint64_t temp = from->tv_usec; - temp = temp << 16; - temp = temp / 1E6; + //convert the µs to 2E-16 seconds + uint64_t temp = from->tv_usec; + temp = temp << 16; + temp = temp / 1E6; - to->subsecondsMSB = (temp >> 8) & 0xff; - to->subsecondsLSB = temp & 0xff; + to->subsecondsMSB = (temp >> 8) & 0xff; + to->subsecondsLSB = temp & 0xff; - return RETURN_OK; + return RETURN_OK; } ReturnValue_t CCSDSTime::convertFromCcsds(timeval* to, const uint8_t* from, - size_t* foundLength, size_t maxLength) { + size_t* foundLength, size_t maxLength) { if(maxLength >= 19) { Clock::TimeOfDay_t timeOfDay; /* Try to parse it as ASCII */ @@ -405,210 +405,210 @@ ReturnValue_t CCSDSTime::convertFromCcsds(timeval* to, const uint8_t* from, } } - uint8_t codeIdentification = (*from >> 4); - switch (codeIdentification) { - /* Unsupported, as Leap second correction would have to be applied */ - case CUC_LEVEL1: - return UNSUPPORTED_TIME_FORMAT; - case CDS: - return convertFromCDS(to, from, foundLength, maxLength); - case CCS: - return convertFromCCS(to, from, foundLength, maxLength); - default: - return UNSUPPORTED_TIME_FORMAT; - } + uint8_t codeIdentification = (*from >> 4); + switch (codeIdentification) { + /* Unsupported, as Leap second correction would have to be applied */ + case CUC_LEVEL1: + return UNSUPPORTED_TIME_FORMAT; + case CDS: + return convertFromCDS(to, from, foundLength, maxLength); + case CCS: + return convertFromCCS(to, from, foundLength, maxLength); + default: + return UNSUPPORTED_TIME_FORMAT; + } } ReturnValue_t CCSDSTime::convertFromCUC(timeval* to, const uint8_t* from, - size_t* foundLength, size_t maxLength) { - if (maxLength < 1) { - return INVALID_TIME_FORMAT; - } - uint8_t pField = *from; - from++; - ReturnValue_t result = convertFromCUC(to, pField, from, foundLength, - maxLength - 1); - if (result == HasReturnvaluesIF::RETURN_OK) { - if (foundLength != NULL) { - *foundLength += 1; - } - } - return result; + size_t* foundLength, size_t maxLength) { + if (maxLength < 1) { + return INVALID_TIME_FORMAT; + } + uint8_t pField = *from; + from++; + ReturnValue_t result = convertFromCUC(to, pField, from, foundLength, + maxLength - 1); + if (result == HasReturnvaluesIF::RETURN_OK) { + if (foundLength != NULL) { + *foundLength += 1; + } + } + return result; } ReturnValue_t CCSDSTime::checkTimeOfDay(const Clock::TimeOfDay_t* time) { - if ((time->month > 12) || (time->month == 0)) { - return INVALID_TIME_FORMAT; - } + if ((time->month > 12) || (time->month == 0)) { + return INVALID_TIME_FORMAT; + } - if (time->day == 0) { - return INVALID_TIME_FORMAT; - } - switch (time->month) { - case 2: - if (isLeapYear(time->year)) { - if (time->day > 29) { - return INVALID_TIME_FORMAT; - } - } else { - if (time->day > 28) { - return INVALID_TIME_FORMAT; - } - } - break; - case 4: - case 6: - case 9: - case 11: - if (time->day > 30) { - return INVALID_TIME_FORMAT; - } - break; - default: - if (time->day > 31) { - return INVALID_TIME_FORMAT; - } - break; - } + if (time->day == 0) { + return INVALID_TIME_FORMAT; + } + switch (time->month) { + case 2: + if (isLeapYear(time->year)) { + if (time->day > 29) { + return INVALID_TIME_FORMAT; + } + } else { + if (time->day > 28) { + return INVALID_TIME_FORMAT; + } + } + break; + case 4: + case 6: + case 9: + case 11: + if (time->day > 30) { + return INVALID_TIME_FORMAT; + } + break; + default: + if (time->day > 31) { + return INVALID_TIME_FORMAT; + } + break; + } - if (time->hour > 23) { - return INVALID_TIME_FORMAT; - } + if (time->hour > 23) { + return INVALID_TIME_FORMAT; + } - if (time->minute > 59) { - return INVALID_TIME_FORMAT; - } + if (time->minute > 59) { + return INVALID_TIME_FORMAT; + } - if (time->second > 59) { - return INVALID_TIME_FORMAT; - } + if (time->second > 59) { + return INVALID_TIME_FORMAT; + } - if (time->usecond > 999999) { - return INVALID_TIME_FORMAT; - } + if (time->usecond > 999999) { + return INVALID_TIME_FORMAT; + } - return RETURN_OK; + return RETURN_OK; } ReturnValue_t CCSDSTime::convertTimevalToTimeOfDay(Clock::TimeOfDay_t* to, - timeval* from) { -//This is rather tricky. Implement only if needed. Also, if so, move to OSAL. - return UNSUPPORTED_TIME_FORMAT; + timeval* from) { + //This is rather tricky. Implement only if needed. Also, if so, move to OSAL. + return UNSUPPORTED_TIME_FORMAT; } ReturnValue_t CCSDSTime::convertFromCDS(timeval* to, const uint8_t* from, - size_t* foundLength, size_t maxLength) { - uint8_t pField = *from; - from++; -//Check epoch - if (pField & 0b1000) { - return NOT_ENOUGH_INFORMATION_FOR_TARGET_FORMAT; - } -//Check length - uint8_t expectedLength = 7; //Including p-Field. - bool extendedDays = pField & 0b100; - if (extendedDays) { - expectedLength += 1; - } - if ((pField & 0b11) == 0b01) { - expectedLength += 2; - } else if ((pField & 0b11) == 0b10) { - expectedLength += 4; - } - if (foundLength != NULL) { - *foundLength = expectedLength; - } - if (expectedLength > maxLength) { - return LENGTH_MISMATCH; - } -//Check and count days - uint32_t days = 0; - if (extendedDays) { - days = (from[0] << 16) + (from[1] << 8) + from[2]; - from += 3; - } else { - days = (from[0] << 8) + from[1]; - from += 2; - } -//Move to POSIX epoch. - if (days <= DAYS_CCSDS_TO_UNIX_EPOCH) { - return INVALID_TIME_FORMAT; - } - days -= DAYS_CCSDS_TO_UNIX_EPOCH; - to->tv_sec = days * SECONDS_PER_DAY; - uint32_t msDay = (from[0] << 24) + (from[1] << 16) + (from[2] << 8) - + from[3]; - from += 4; - to->tv_sec += (msDay / 1000); - to->tv_usec = (msDay % 1000) * 1000; - if ((pField & 0b11) == 0b01) { - uint16_t usecs = (from[0] << 16) + from[1]; - from += 2; - if (usecs > 999) { - return INVALID_TIME_FORMAT; - } - to->tv_usec += usecs; - } else if ((pField & 0b11) == 0b10) { - uint32_t picosecs = (from[0] << 24) + (from[1] << 16) + (from[2] << 8) - + from[3]; - from += 4; - if (picosecs > 999999) { - return INVALID_TIME_FORMAT; - } - //Not very useful. - to->tv_usec += (picosecs / 1000); - } - return RETURN_OK; + size_t* foundLength, size_t maxLength) { + uint8_t pField = *from; + from++; + //Check epoch + if (pField & 0b1000) { + return NOT_ENOUGH_INFORMATION_FOR_TARGET_FORMAT; + } + //Check length + uint8_t expectedLength = 7; //Including p-Field. + bool extendedDays = pField & 0b100; + if (extendedDays) { + expectedLength += 1; + } + if ((pField & 0b11) == 0b01) { + expectedLength += 2; + } else if ((pField & 0b11) == 0b10) { + expectedLength += 4; + } + if (foundLength != NULL) { + *foundLength = expectedLength; + } + if (expectedLength > maxLength) { + return LENGTH_MISMATCH; + } + //Check and count days + uint32_t days = 0; + if (extendedDays) { + days = (from[0] << 16) + (from[1] << 8) + from[2]; + from += 3; + } else { + days = (from[0] << 8) + from[1]; + from += 2; + } + //Move to POSIX epoch. + if (days <= DAYS_CCSDS_TO_UNIX_EPOCH) { + return INVALID_TIME_FORMAT; + } + days -= DAYS_CCSDS_TO_UNIX_EPOCH; + to->tv_sec = days * SECONDS_PER_DAY; + uint32_t msDay = (from[0] << 24) + (from[1] << 16) + (from[2] << 8) + + from[3]; + from += 4; + to->tv_sec += (msDay / 1000); + to->tv_usec = (msDay % 1000) * 1000; + if ((pField & 0b11) == 0b01) { + uint16_t usecs = (from[0] << 16) + from[1]; + from += 2; + if (usecs > 999) { + return INVALID_TIME_FORMAT; + } + to->tv_usec += usecs; + } else if ((pField & 0b11) == 0b10) { + uint32_t picosecs = (from[0] << 24) + (from[1] << 16) + (from[2] << 8) + + from[3]; + from += 4; + if (picosecs > 999999) { + return INVALID_TIME_FORMAT; + } + //Not very useful. + to->tv_usec += (picosecs / 1000); + } + return RETURN_OK; } ReturnValue_t CCSDSTime::convertFromCUC(timeval* to, uint8_t pField, - const uint8_t* from, size_t* foundLength, size_t maxLength) { - uint32_t secs = 0; - uint32_t subSeconds = 0; - uint8_t nCoarse = ((pField & 0b1100) >> 2) + 1; - uint8_t nFine = (pField & 0b11); - size_t totalLength = nCoarse + nFine; - if (foundLength != NULL) { - *foundLength = totalLength; - } - if (totalLength > maxLength) { - return LENGTH_MISMATCH; - } - for (int count = 0; count < nCoarse; count++) { - secs += *from << ((nCoarse * 8 - 8) * (1 + count)); - from++; - } - for (int count = 0; count < nFine; count++) { - subSeconds += *from << ((nFine * 8 - 8) * (1 + count)); - from++; - } - //Move to POSIX epoch. - to->tv_sec = secs; - if (pField & 0b10000) { - //CCSDS-Epoch - to->tv_sec -= (DAYS_CCSDS_TO_UNIX_EPOCH * SECONDS_PER_DAY); - } - to->tv_usec = subsecondsToMicroseconds(subSeconds); - return RETURN_OK; + const uint8_t* from, size_t* foundLength, size_t maxLength) { + uint32_t secs = 0; + uint32_t subSeconds = 0; + uint8_t nCoarse = ((pField & 0b1100) >> 2) + 1; + uint8_t nFine = (pField & 0b11); + size_t totalLength = nCoarse + nFine; + if (foundLength != NULL) { + *foundLength = totalLength; + } + if (totalLength > maxLength) { + return LENGTH_MISMATCH; + } + for (int count = 0; count < nCoarse; count++) { + secs += *from << ((nCoarse * 8 - 8) * (1 + count)); + from++; + } + for (int count = 0; count < nFine; count++) { + subSeconds += *from << ((nFine * 8 - 8) * (1 + count)); + from++; + } + //Move to POSIX epoch. + to->tv_sec = secs; + if (pField & 0b10000) { + //CCSDS-Epoch + to->tv_sec -= (DAYS_CCSDS_TO_UNIX_EPOCH * SECONDS_PER_DAY); + } + to->tv_usec = subsecondsToMicroseconds(subSeconds); + return RETURN_OK; } uint32_t CCSDSTime::subsecondsToMicroseconds(uint16_t subseconds) { - uint64_t temp = (uint64_t) subseconds * 1000000 - / (1 << (sizeof(subseconds) * 8)); - return temp; + uint64_t temp = (uint64_t) subseconds * 1000000 + / (1 << (sizeof(subseconds) * 8)); + return temp; } ReturnValue_t CCSDSTime::convertFromCCS(timeval* to, const uint8_t* from, - size_t* foundLength, size_t maxLength) { - Clock::TimeOfDay_t tempTime; - ReturnValue_t result = convertFromCCS(&tempTime, from, foundLength, - maxLength); - if (result != RETURN_OK) { - return result; - } + size_t* foundLength, size_t maxLength) { + Clock::TimeOfDay_t tempTime; + ReturnValue_t result = convertFromCCS(&tempTime, from, foundLength, + maxLength); + if (result != RETURN_OK) { + return result; + } - return Clock::convertTimeOfDayToTimeval(&tempTime, to); + return Clock::convertTimeOfDayToTimeval(&tempTime, to); } diff --git a/timemanager/CCSDSTime.h b/timemanager/CCSDSTime.h index d74adc61d..de151852a 100644 --- a/timemanager/CCSDSTime.h +++ b/timemanager/CCSDSTime.h @@ -21,215 +21,215 @@ bool operator==(const timeval& lhs, const timeval& rhs); */ class CCSDSTime: public HasReturnvaluesIF { public: - /** - * The Time code identifications, bits 4-6 in the P-Field - */ - enum TimeCodeIdentification { - CCS = 0b101, - CUC_LEVEL1 = 0b001, - CUC_LEVEL2 = 0b010, - CDS = 0b100, - AGENCY_DEFINED = 0b110 - }; - static const uint8_t P_FIELD_CUC_6B_CCSDS = (CUC_LEVEL1 << 4) + (3 << 2) - + 2; - static const uint8_t P_FIELD_CUC_6B_AGENCY = (CUC_LEVEL2 << 4) + (3 << 2) - + 2; - static const uint8_t P_FIELD_CDS_SHORT = (CDS << 4); - /** - * Struct for CDS day-segmented format. - */ - struct CDS_short { - uint8_t pField; - uint8_t dayMSB; - uint8_t dayLSB; - uint8_t msDay_hh; - uint8_t msDay_h; - uint8_t msDay_l; - uint8_t msDay_ll; - }; - /** - * Struct for the CCS fromat in day of month variation with max resolution - */ - struct Ccs_seconds { - uint8_t pField; - uint8_t yearMSB; - uint8_t yearLSB; - uint8_t month; - uint8_t day; - uint8_t hour; - uint8_t minute; - uint8_t second; - }; + /** + * The Time code identifications, bits 4-6 in the P-Field + */ + enum TimeCodeIdentification { + CCS = 0b101, + CUC_LEVEL1 = 0b001, + CUC_LEVEL2 = 0b010, + CDS = 0b100, + AGENCY_DEFINED = 0b110 + }; + static const uint8_t P_FIELD_CUC_6B_CCSDS = (CUC_LEVEL1 << 4) + (3 << 2) + + 2; + static const uint8_t P_FIELD_CUC_6B_AGENCY = (CUC_LEVEL2 << 4) + (3 << 2) + + 2; + static const uint8_t P_FIELD_CDS_SHORT = (CDS << 4); + /** + * Struct for CDS day-segmented format. + */ + struct CDS_short { + uint8_t pField; + uint8_t dayMSB; + uint8_t dayLSB; + uint8_t msDay_hh; + uint8_t msDay_h; + uint8_t msDay_l; + uint8_t msDay_ll; + }; + /** + * Struct for the CCS fromat in day of month variation with max resolution + */ + struct Ccs_seconds { + uint8_t pField; + uint8_t yearMSB; + uint8_t yearLSB; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + }; - /** - * Struct for the CCS fromat in day of month variation with 10E-4 seconds resolution - */ - struct Ccs_mseconds { - uint8_t pField; - uint8_t yearMSB; - uint8_t yearLSB; - uint8_t month; - uint8_t day; - uint8_t hour; - uint8_t minute; - uint8_t second; - uint8_t secondEminus2; - uint8_t secondEminus4; - }; + /** + * Struct for the CCS fromat in day of month variation with 10E-4 seconds resolution + */ + struct Ccs_mseconds { + uint8_t pField; + uint8_t yearMSB; + uint8_t yearLSB; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t secondEminus2; + uint8_t secondEminus4; + }; - struct OBT_FLP { - uint8_t pFiled; - uint8_t seconds_hh; - uint8_t seconds_h; - uint8_t seconds_l; - uint8_t seconds_ll; - uint8_t subsecondsMSB; - uint8_t subsecondsLSB; - }; + struct OBT_FLP { + uint8_t pFiled; + uint8_t seconds_hh; + uint8_t seconds_h; + uint8_t seconds_l; + uint8_t seconds_ll; + uint8_t subsecondsMSB; + uint8_t subsecondsLSB; + }; - struct TimevalLess { - bool operator()(const timeval& lhs, const timeval& rhs) const { - return (lhs < rhs); - } - }; + struct TimevalLess { + bool operator()(const timeval& lhs, const timeval& rhs) const { + return (lhs < rhs); + } + }; - static const uint8_t INTERFACE_ID = CLASS_ID::CCSDS_TIME_HELPER_CLASS; - static const ReturnValue_t UNSUPPORTED_TIME_FORMAT = MAKE_RETURN_CODE(0); - static const ReturnValue_t NOT_ENOUGH_INFORMATION_FOR_TARGET_FORMAT = - MAKE_RETURN_CODE(1); - static const ReturnValue_t LENGTH_MISMATCH = MAKE_RETURN_CODE(2); - static const ReturnValue_t INVALID_TIME_FORMAT = MAKE_RETURN_CODE(3); - static const ReturnValue_t INVALID_DAY_OF_YEAR = MAKE_RETURN_CODE(4); - static const ReturnValue_t TIME_DOES_NOT_FIT_FORMAT = MAKE_RETURN_CODE(5); + static const uint8_t INTERFACE_ID = CLASS_ID::CCSDS_TIME_HELPER_CLASS; + static const ReturnValue_t UNSUPPORTED_TIME_FORMAT = MAKE_RETURN_CODE(0); + static const ReturnValue_t NOT_ENOUGH_INFORMATION_FOR_TARGET_FORMAT = + MAKE_RETURN_CODE(1); + static const ReturnValue_t LENGTH_MISMATCH = MAKE_RETURN_CODE(2); + static const ReturnValue_t INVALID_TIME_FORMAT = MAKE_RETURN_CODE(3); + static const ReturnValue_t INVALID_DAY_OF_YEAR = MAKE_RETURN_CODE(4); + static const ReturnValue_t TIME_DOES_NOT_FIT_FORMAT = MAKE_RETURN_CODE(5); - /** - * convert a TimeofDay struct to ccs with seconds resolution - * - * @param to pointer to a CCS struct - * @param from pointer to a TimeOfDay Struct - * @return - * - @c RETURN_OK if OK - * - @c INVALID_TIMECODE if not OK - */ - static ReturnValue_t convertToCcsds(Ccs_seconds *to, - Clock::TimeOfDay_t const *from); + /** + * convert a TimeofDay struct to ccs with seconds resolution + * + * @param to pointer to a CCS struct + * @param from pointer to a TimeOfDay Struct + * @return + * - @c RETURN_OK if OK + * - @c INVALID_TIMECODE if not OK + */ + static ReturnValue_t convertToCcsds(Ccs_seconds *to, + Clock::TimeOfDay_t const *from); - /** - * Converts to CDS format from timeval. - * @param to pointer to the CDS struct to generate - * @param from pointer to a timeval struct which comprises a time of day since UNIX epoch. - * @return - * - @c RETURN_OK as it assumes a valid timeval. - */ - static ReturnValue_t convertToCcsds(CDS_short* to, timeval const *from); + /** + * Converts to CDS format from timeval. + * @param to pointer to the CDS struct to generate + * @param from pointer to a timeval struct which comprises a time of day since UNIX epoch. + * @return + * - @c RETURN_OK as it assumes a valid timeval. + */ + static ReturnValue_t convertToCcsds(CDS_short* to, timeval const *from); - static ReturnValue_t convertToCcsds(OBT_FLP* to, timeval const *from); + static ReturnValue_t convertToCcsds(OBT_FLP* to, timeval const *from); - /** - * convert a TimeofDay struct to ccs with 10E-3 seconds resolution - * - * The 10E-4 seconds in the CCS Struct are 0 as the TimeOfDay only has ms resolution - * - * @param to pointer to a CCS struct - * @param from pointer to a TimeOfDay Struct - * @return - * - @c RETURN_OK if OK - * - @c INVALID_TIMECODE if not OK - */ - static ReturnValue_t convertToCcsds(Ccs_mseconds *to, - Clock::TimeOfDay_t const *from); + /** + * convert a TimeofDay struct to ccs with 10E-3 seconds resolution + * + * The 10E-4 seconds in the CCS Struct are 0 as the TimeOfDay only has ms resolution + * + * @param to pointer to a CCS struct + * @param from pointer to a TimeOfDay Struct + * @return + * - @c RETURN_OK if OK + * - @c INVALID_TIMECODE if not OK + */ + static ReturnValue_t convertToCcsds(Ccs_mseconds *to, + Clock::TimeOfDay_t const *from); - /** - * SHOULDDO: can this be modified to recognize padding? - * Tries to interpret a Level 1 CCSDS time code - * - * It assumes binary formats contain a valid P Field and recognizes the ASCII format - * by the lack of one. - * - * @param to an empty TimeOfDay struct - * @param from pointer to an CCSDS Time code - * @param length length of the Time code - * @return - * - @c RETURN_OK if successful - * - @c UNSUPPORTED_TIME_FORMAT if a (possibly valid) time code is not supported - * - @c LENGTH_MISMATCH if the length does not match the P Field - * - @c INVALID_TIME_FORMAT if the format or a value is invalid - */ - static ReturnValue_t convertFromCcsds(Clock::TimeOfDay_t *to, - uint8_t const *from, size_t length); + /** + * SHOULDDO: can this be modified to recognize padding? + * Tries to interpret a Level 1 CCSDS time code + * + * It assumes binary formats contain a valid P Field and recognizes the ASCII format + * by the lack of one. + * + * @param to an empty TimeOfDay struct + * @param from pointer to an CCSDS Time code + * @param length length of the Time code + * @return + * - @c RETURN_OK if successful + * - @c UNSUPPORTED_TIME_FORMAT if a (possibly valid) time code is not supported + * - @c LENGTH_MISMATCH if the length does not match the P Field + * - @c INVALID_TIME_FORMAT if the format or a value is invalid + */ + static ReturnValue_t convertFromCcsds(Clock::TimeOfDay_t *to, + uint8_t const *from, size_t length); - /** - * not implemented yet - * - * @param to - * @param from - * @return - */ - static ReturnValue_t convertFromCcsds(timeval *to, uint8_t const *from, - size_t* foundLength, size_t maxLength); + /** + * not implemented yet + * + * @param to + * @param from + * @return + */ + static ReturnValue_t convertFromCcsds(timeval *to, uint8_t const *from, + size_t* foundLength, size_t maxLength); - static ReturnValue_t convertFromCUC(Clock::TimeOfDay_t *to, - uint8_t const *from, uint8_t length); + static ReturnValue_t convertFromCUC(Clock::TimeOfDay_t *to, + uint8_t const *from, uint8_t length); - static ReturnValue_t convertFromCUC(timeval *to, uint8_t const *from, - size_t* foundLength, size_t maxLength); + static ReturnValue_t convertFromCUC(timeval *to, uint8_t const *from, + size_t* foundLength, size_t maxLength); - static ReturnValue_t convertFromCUC(timeval *to, uint8_t pField, - uint8_t const *from, size_t* foundLength, size_t maxLength); + static ReturnValue_t convertFromCUC(timeval *to, uint8_t pField, + uint8_t const *from, size_t* foundLength, size_t maxLength); - static ReturnValue_t convertFromCCS(timeval *to, uint8_t const *from, - size_t* foundLength, size_t maxLength); + static ReturnValue_t convertFromCCS(timeval *to, uint8_t const *from, + size_t* foundLength, size_t maxLength); - static ReturnValue_t convertFromCCS(timeval *to, uint8_t pField, - uint8_t const *from, size_t* foundLength, size_t maxLength); + static ReturnValue_t convertFromCCS(timeval *to, uint8_t pField, + uint8_t const *from, size_t* foundLength, size_t maxLength); - static ReturnValue_t convertFromCDS(Clock::TimeOfDay_t *to, - uint8_t const *from, uint8_t length); + static ReturnValue_t convertFromCDS(Clock::TimeOfDay_t *to, + uint8_t const *from, uint8_t length); - static ReturnValue_t convertFromCDS(timeval *to, uint8_t const *from, - size_t* foundLength, size_t maxLength); + static ReturnValue_t convertFromCDS(timeval *to, uint8_t const *from, + size_t* foundLength, size_t maxLength); - static ReturnValue_t convertFromCCS(Clock::TimeOfDay_t *to, - uint8_t const *from, size_t* foundLength, size_t maxLength); + static ReturnValue_t convertFromCCS(Clock::TimeOfDay_t *to, + uint8_t const *from, size_t* foundLength, size_t maxLength); - static ReturnValue_t convertFromASCII(Clock::TimeOfDay_t *to, - uint8_t const *from, uint8_t length); + static ReturnValue_t convertFromASCII(Clock::TimeOfDay_t *to, + uint8_t const *from, uint8_t length); - static uint32_t subsecondsToMicroseconds(uint16_t subseconds); + static uint32_t subsecondsToMicroseconds(uint16_t subseconds); private: - CCSDSTime(); - virtual ~CCSDSTime(); - /** - * checks a ccs time stream for validity - * - * Stream may be longer than the actual timecode - * - * @param time pointer to an Ccs stream - * @param length length of stream - * @return - */ - static ReturnValue_t checkCcs(const uint8_t* time, uint8_t length); + CCSDSTime(); + virtual ~CCSDSTime(); + /** + * checks a ccs time stream for validity + * + * Stream may be longer than the actual timecode + * + * @param time pointer to an Ccs stream + * @param length length of stream + * @return + */ + static ReturnValue_t checkCcs(const uint8_t* time, uint8_t length); - static ReturnValue_t checkTimeOfDay(const Clock::TimeOfDay_t *time); + static ReturnValue_t checkTimeOfDay(const Clock::TimeOfDay_t *time); - static const uint32_t SECONDS_PER_DAY = 24 * 60 * 60; - static const uint32_t SECONDS_PER_NON_LEAP_YEAR = SECONDS_PER_DAY * 365; - static const uint32_t DAYS_CCSDS_TO_UNIX_EPOCH = 4383; //!< Time difference between CCSDS and POSIX epoch. This is exact, because leap-seconds where not introduced before 1972. - static const uint32_t SECONDS_CCSDS_TO_UNIX_EPOCH = DAYS_CCSDS_TO_UNIX_EPOCH - * SECONDS_PER_DAY; - /** - * @param dayofYear - * @param year - * @param month - * @param day - */ - static ReturnValue_t convertDaysOfYear(uint16_t dayofYear, uint16_t year, - uint8_t *month, uint8_t *day); + static const uint32_t SECONDS_PER_DAY = 24 * 60 * 60; + static const uint32_t SECONDS_PER_NON_LEAP_YEAR = SECONDS_PER_DAY * 365; + static const uint32_t DAYS_CCSDS_TO_UNIX_EPOCH = 4383; //!< Time difference between CCSDS and POSIX epoch. This is exact, because leap-seconds where not introduced before 1972. + static const uint32_t SECONDS_CCSDS_TO_UNIX_EPOCH = DAYS_CCSDS_TO_UNIX_EPOCH + * SECONDS_PER_DAY; + /** + * @param dayofYear + * @param year + * @param month + * @param day + */ + static ReturnValue_t convertDaysOfYear(uint16_t dayofYear, uint16_t year, + uint8_t *month, uint8_t *day); - static bool isLeapYear(uint32_t year); - static ReturnValue_t convertTimevalToTimeOfDay(Clock::TimeOfDay_t* to, - timeval* from); + static bool isLeapYear(uint32_t year); + static ReturnValue_t convertTimevalToTimeOfDay(Clock::TimeOfDay_t* to, + timeval* from); }; #endif /* FSFW_TIMEMANAGER_CCSDSTIME_H_ */ From 4250c7e022ede038d756d4a88e7b206377d93d3d Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 16:45:35 +0100 Subject: [PATCH 08/21] updated changelog --- CHANGELOG | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index fc46ee021..bd41de5a1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -82,6 +82,10 @@ now 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. +### ActionHelper + +- ActionHelper finish function now expects explicit information whether to report a success or failure message instead of deriving it implicitely from returnvalue + ### PUS Parameter Service 20 -Added PUS parameter service 20 (only custom subservices available). \ No newline at end of file +Added PUS parameter service 20 (only custom subservices available). From bd903b844776f80718ad74d6132c74046aae7607 Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 16:46:39 +0100 Subject: [PATCH 09/21] changelog update --- CHANGELOG | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bd41de5a1..235ed7d37 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,7 +22,9 @@ a C file without issues ### 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 @@ -40,7 +42,8 @@ important use-case) ### 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 @@ -52,7 +55,8 @@ ID for now. ### Device Handler Base - 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 - getTransitionDelayMs is now an abstract method @@ -69,7 +73,8 @@ now ### 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 @@ -82,9 +87,11 @@ now 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. -### ActionHelper +### ActionHelper and ActionMessage -- ActionHelper finish function now expects explicit information whether to report a success or failure message instead of deriving it implicitely from returnvalue +- 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 From 043d47e5e43348a79fc50a7550ddcc0caeab2dba Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 16:48:21 +0100 Subject: [PATCH 10/21] removing whitespaces --- CHANGELOG | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 235ed7d37..add8e1a5b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,8 +22,8 @@ a C file without issues ### 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 +- 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 @@ -42,7 +42,7 @@ important use-case) ### File System Interface -- A new interfaces specifies the functions for a software object which exposes the file system of +- 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 From 35825a6561379506e092a02266efe833743fc862 Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 17:27:03 +0100 Subject: [PATCH 11/21] new functions to set all vars read only --- datapool/PoolVariableIF.h | 3 ++- datapoollocal/LocalPoolDataSetBase.cpp | 7 ++++++- datapoollocal/LocalPoolDataSetBase.h | 6 ++++++ doc/README-localpools.md | 26 ++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/datapool/PoolVariableIF.h b/datapool/PoolVariableIF.h index dead6844a..dbb9db15c 100644 --- a/datapool/PoolVariableIF.h +++ b/datapool/PoolVariableIF.h @@ -46,6 +46,8 @@ public: * read-write or read-only. */ 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. */ @@ -59,7 +61,6 @@ public: * @brief With this call, the valid information of the variable is set. */ virtual void setValid(bool validity) = 0; - }; using pool_rwm_t = PoolVariableIF::ReadWriteMode_t; diff --git a/datapoollocal/LocalPoolDataSetBase.cpp b/datapoollocal/LocalPoolDataSetBase.cpp index d043d18f5..daa54fb33 100644 --- a/datapoollocal/LocalPoolDataSetBase.cpp +++ b/datapoollocal/LocalPoolDataSetBase.cpp @@ -288,7 +288,7 @@ bool LocalPoolDataSetBase::isValid() const { void LocalPoolDataSetBase::setValidity(bool valid, bool setEntriesRecursively) { if(setEntriesRecursively) { for(size_t idx = 0; idx < this->getFillCount(); idx++) { - registeredVariables[idx] -> setValid(valid); + registeredVariables[idx]->setValid(valid); } } this->valid = valid; @@ -301,3 +301,8 @@ object_id_t LocalPoolDataSetBase::getCreatorObjectId() { return objects::NO_OBJECT; } +void LocalPoolDataSetBase::setAllVariablesReadOnly() { + for(size_t idx = 0; idx < this->getFillCount(); idx++) { + registeredVariables[idx]->setReadWriteMode(pool_rwm_t::VAR_READ); + } +} diff --git a/datapoollocal/LocalPoolDataSetBase.h b/datapoollocal/LocalPoolDataSetBase.h index 9501f7821..404509ae5 100644 --- a/datapoollocal/LocalPoolDataSetBase.h +++ b/datapoollocal/LocalPoolDataSetBase.h @@ -109,6 +109,12 @@ public: LocalPoolDataSetBase(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); sid_t getSid() const; diff --git a/doc/README-localpools.md b/doc/README-localpools.md index 8dd83385e..57a41aa14 100644 --- a/doc/README-localpools.md +++ b/doc/README-localpools.md @@ -1,3 +1,29 @@ ## Local Data Pools +The local data pools can be used to store data like sensor values so they can be used +by other software objects like controllers as well. If a 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 class +2. Implement the `HasLocalDataPoolIF` + +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. +Two important framework classes 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 is done via proxy classes like pool variable classes or dataset classes. +Generally, a user will create a dataset class which in turn groups all cohesive pool variables. + +The user can then use this set class to `read` the variables and `commit` changed variables +back into the pool. + +### Using the local data pools of the `DeviceHandlerBase` + +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. The From 8de33f13014f059f53767968eb11f8fe8a1ad32d Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 17:46:42 +0100 Subject: [PATCH 12/21] added local pool doc --- doc/README-localpools.md | 134 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 130 insertions(+), 4 deletions(-) diff --git a/doc/README-localpools.md b/doc/README-localpools.md index 57a41aa14..9e7ddeacf 100644 --- a/doc/README-localpools.md +++ b/doc/README-localpools.md @@ -1,4 +1,4 @@ -## Local Data Pools +## Local Data Pools Developer Information The local data pools can be used to store data like sensor values so they can be used by other software objects like controllers as well. If a class should have a local pool which @@ -20,10 +20,136 @@ access to the pool data is done via proxy classes like pool variable classes or Generally, a user will create a dataset class which in turn groups all cohesive pool variables. The user can then use this set class to `read` the variables and `commit` changed variables -back into the pool. +back into the pool. The general approach is that a user will create a header containing the set +class. For example, the following code shows an implementation to access data from a Gyroscope: -### Using the local data pools of the `DeviceHandlerBase` +```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 angVelocityX = lp_var_t(sid.objectId, + gyrodefs::ANGULAR_VELOCITY_X, this); + lp_var_t angVelocityY = lp_var_t(sid.objectId, + gyrodefs::ANGULAR_VELOCITY_Y, this); + lp_var_t angVelocityZ = lp_var_t(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 constructor for users which sets all variables to read-only and there is the constructor +for the GyroHandler data creator. 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. The +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 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({0.0})); + localDataPoolMap.emplace(gyrodefs::ANGULAR_VELOCITY_Y, + new PoolEntry({0.0})); + localDataPoolMap.emplace(gyrodefs::ANGULAR_VELOCITY_Z, + new PoolEntry({0.0})); + localDataPoolMap.emplace(gyrodefs::GENERAL_CONFIG_REG42, + new PoolEntry({0})); + localDataPoolMap.emplace(gyrodefs::RANGE_CONFIG_REG43, + new PoolEntry({0})); + + poolManager.subscribeForPeriodicPacket(gyroData.getSid(), false, 4.0, false); + return HasReturnvaluesIF::RETURN_OK; +} +``` + +Now, we have received 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; +} +``` + +### Using the local data pools in a `ExtendedControllerBase` subclass + +Coming soon + + From 23873f6bc60cdef68e022e6bfd9d2ddd65b1723e Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 17:58:07 +0100 Subject: [PATCH 13/21] improved readme --- doc/README-localpools.md | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/doc/README-localpools.md b/doc/README-localpools.md index 9e7ddeacf..399400a4b 100644 --- a/doc/README-localpools.md +++ b/doc/README-localpools.md @@ -1,27 +1,37 @@ ## Local Data Pools Developer Information The local data pools can be used to store data like sensor values so they can be used -by other software objects like controllers as well. If a class should have a local pool which +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 class -2. Implement the `HasLocalDataPoolIF` +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. -Two important framework classes already perform the two steps shown above so the steps required -are altered slightly. +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 is done via proxy classes like pool variable classes or dataset classes. -Generally, a user will create a dataset class which in turn groups all cohesive pool variables. +access to the pool data from the point of a mission software developer is given in form of +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 user can then use this set class to `read` the variables and `commit` changed variables -back into the pool. The general approach is that a user will create a header containing the set -class. For example, the following code shows an implementation to access data from a Gyroscope: +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)> { @@ -53,8 +63,9 @@ private: }; ``` -There is a constructor for users which sets all variables to read-only and there is the constructor -for the GyroHandler data creator. Both the atittude controller and the `GyroHandler` can now +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 makring 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 @@ -148,6 +159,8 @@ if(readHelper.getReadResult() == HasReturnvaluesIF::RETURN_OK) { } ``` +The guard class will commit the changed data on destruction automatically. + ### Using the local data pools in a `ExtendedControllerBase` subclass Coming soon From a7878aaf0446b85a71da8522a7e3f6807407cbe1 Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 18:06:11 +0100 Subject: [PATCH 14/21] readme update --- doc/README-localpools.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/README-localpools.md b/doc/README-localpools.md index 399400a4b..223b9d2b4 100644 --- a/doc/README-localpools.md +++ b/doc/README-localpools.md @@ -103,8 +103,8 @@ GyroHandler::GyroHandler(object_id_t objectId, object_id_t comIF, 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 in some way so that the local pool manager -can access the custom data sets as well. +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, From d84003d62a08ddc722e0363bf4cfe381d3f44785 Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 18:07:10 +0100 Subject: [PATCH 15/21] updated READMe --- doc/README-localpools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/README-localpools.md b/doc/README-localpools.md index 223b9d2b4..8b1fc45bb 100644 --- a/doc/README-localpools.md +++ b/doc/README-localpools.md @@ -142,7 +142,7 @@ ReturnValue_t GyroHandler::initializeLocalDataPool(localpool::DataPool &localDat } ``` -Now, we have received some sensor data and converted them into the right format, +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: From 6df1abf570e57e134e46c50d1b8aeff2f6f6404d Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 18:11:10 +0100 Subject: [PATCH 16/21] added graph --- doc/README-localpools.md | 6 +++++- doc/images/PoolArchitecture.png | Bin 0 -> 53460 bytes 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 doc/images/PoolArchitecture.png diff --git a/doc/README-localpools.md b/doc/README-localpools.md index 8b1fc45bb..041167975 100644 --- a/doc/README-localpools.md +++ b/doc/README-localpools.md @@ -15,7 +15,11 @@ The two former tasks are related to the external interface using telemetry and t 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. +shown above so the steps required are altered slightly. The following diagram shows the +high-level architecture of the local data pools. + + + ### Storing and Accessing pool data diff --git a/doc/images/PoolArchitecture.png b/doc/images/PoolArchitecture.png new file mode 100644 index 0000000000000000000000000000000000000000..13ea85c8886ee64629152d6ea4a2b3e58f7085be GIT binary patch literal 53460 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 A;s5{u literal 0 HcmV?d00001 From 69b428222ac26611acbf9ef8938516de0c0e6884 Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 18:12:34 +0100 Subject: [PATCH 17/21] updated readme --- doc/README-localpools.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/README-localpools.md b/doc/README-localpools.md index 041167975..2f9bba7f8 100644 --- a/doc/README-localpools.md +++ b/doc/README-localpools.md @@ -15,11 +15,7 @@ The two former tasks are related to the external interface using telemetry and t 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. The following diagram shows the -high-level architecture of the local data pools. - - - +shown above so the steps required are altered slightly. ### Storing and Accessing pool data @@ -31,7 +27,10 @@ on a `read` call. Changed variables can then be written to the local pool with a 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. +`read` and `commit` functions of each variable. The following diagram shows the +high-level architecture of the local data pools. + +
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 e0d7363eed3bac2e2d1535be77c1b8f6e94025cc Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 18:14:29 +0100 Subject: [PATCH 18/21] better entry text --- doc/README-localpools.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/README-localpools.md b/doc/README-localpools.md index 2f9bba7f8..548fa2728 100644 --- a/doc/README-localpools.md +++ b/doc/README-localpools.md @@ -1,8 +1,9 @@ ## Local Data Pools Developer Information -The local data pools can be used 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: +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 @@ -67,7 +68,7 @@ private: ``` 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 makring it private and declaring the `GyroHandler` +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 From e501390d7be09ece647d829b63b81e2bc2dc6277 Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 18:20:58 +0100 Subject: [PATCH 19/21] readme typo --- doc/README-localpools.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/README-localpools.md b/doc/README-localpools.md index 548fa2728..4ac2a4d8b 100644 --- a/doc/README-localpools.md +++ b/doc/README-localpools.md @@ -21,7 +21,7 @@ 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 a mission software developer is given in form of +access to the pool data from the point of view of a mission software developer is in form of 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. From a56cf4389754b577b86ca14d47a5dc7bf97ff2d1 Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 18:22:32 +0100 Subject: [PATCH 20/21] another rwadme update --- doc/README-localpools.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/README-localpools.md b/doc/README-localpools.md index 4ac2a4d8b..96ae2d0af 100644 --- a/doc/README-localpools.md +++ b/doc/README-localpools.md @@ -21,8 +21,8 @@ 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 is in form of -proxy classes like pool variable classes. These classes store a copy +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 From 227074fd4d165a8433d55a4baeac18b5ef84d6da Mon Sep 17 00:00:00 2001 From: "Robin.Mueller" Date: Thu, 4 Mar 2021 18:45:32 +0100 Subject: [PATCH 21/21] increaed exception safety --- datapoollocal/LocalPoolObjectBase.cpp | 13 +++++++++---- datapoollocal/LocalPoolVariable.tpp | 26 ++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/datapoollocal/LocalPoolObjectBase.cpp b/datapoollocal/LocalPoolObjectBase.cpp index a64ed2b4a..b6db06089 100644 --- a/datapoollocal/LocalPoolObjectBase.cpp +++ b/datapoollocal/LocalPoolObjectBase.cpp @@ -37,15 +37,20 @@ LocalPoolObjectBase::LocalPoolObjectBase(object_id_t poolOwner, lp_id_t poolId, if(poolId == PoolVariableIF::NO_PARAMETER) { #if FSFW_CPP_OSTREAM_ENABLED == 1 sif::warning << "LocalPoolVar::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::LocalPoolVar: 0 passed as pool ID, " + "which is the NO_PARAMETER value!\n"); #endif } HasLocalDataPoolIF* hkOwner = objectManager->get(poolOwner); if(hkOwner == nullptr) { #if FSFW_CPP_OSTREAM_ENABLED == 1 - sif::error << "LocalPoolVariable: The supplied pool owner did not " - << "implement the correct interface" - << " HasLocalDataPoolIF!" << std::endl; + sif::error << "LocalPoolVariable: The supplied pool owner did not implement the correct " + "interface HasLocalDataPoolIF!" << std::endl; +#else + sif::printError( "LocalPoolVariable: The supplied pool owner did not implement the correct " + "interface HasLocalDataPoolIF!\n"); #endif return; } diff --git a/datapoollocal/LocalPoolVariable.tpp b/datapoollocal/LocalPoolVariable.tpp index aa789a6dd..ba7a413de 100644 --- a/datapoollocal/LocalPoolVariable.tpp +++ b/datapoollocal/LocalPoolVariable.tpp @@ -26,8 +26,17 @@ inline LocalPoolVariable::LocalPoolVariable(gp_id_t globalPoolId, template inline ReturnValue_t LocalPoolVariable::read( MutexIF::TimeoutType timeoutType, uint32_t timeoutMs) { - MutexHelper(LocalDpManagerAttorney::getMutexHandle(*hkManager), timeoutType, timeoutMs); - return readWithoutLock(); + if(hkManager == nullptr) { + 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 @@ -65,8 +74,17 @@ inline ReturnValue_t LocalPoolVariable::commit(bool setValid, template inline ReturnValue_t LocalPoolVariable::commit( MutexIF::TimeoutType timeoutType, uint32_t timeoutMs) { - MutexHelper(LocalDpManagerAttorney::getMutexHandle(*hkManager), timeoutType, timeoutMs); - return commitWithoutLock(); + if(hkManager == nullptr) { + 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