Merge branch 'main' into main-v6
All checks were successful
EIVE/eive-obsw/pipeline/head This commit looks good

This commit is contained in:
Robin Müller 2023-06-28 15:08:38 +02:00
commit d8efd05a88
No known key found for this signature in database
GPG Key ID: 11D4952C8CCEF814
29 changed files with 497 additions and 174 deletions

View File

@ -20,7 +20,28 @@ will consitute of a breaking change warranting a new major release:
- Important bugfixes for PTME. See `q7s-package` CHANGELOG. - Important bugfixes for PTME. See `q7s-package` CHANGELOG.
# [v5.0.0] to be released # [v5.1.0] to be released
- `eive-tmtc` version v5.1.0
## Changed
- Persistent TM store dumps are now performed in chronological order.
- Increase Syrlinks RX HK rate to 5.0 seconds during a pass.
- Various robustness improvements for the heater handler. The heater handler will now only
process the command queue if it is not busy with switch commanding which reduces the amount
of possible bugs.
- The heater handler is only able to process messages stricly sequentially now but is scheduled
twice in a 0.5 second slot so something like a consecutive heater ON or OFF command can still
be handled relatively quickly.
## Added
- Sequence counters for PUS and CFDP packets are now stored persistently across graceful reboots.
- The PUS packet message type counter will now be incremented properly for each PUS service.
- Internal error reporter set is now enabled by default and generated every 120 seconds.
# [v5.0.0] 2023-06-26
v3.3.1 and all following version will now be moved to v5.0.0 with the additional changes listed v3.3.1 and all following version will now be moved to v5.0.0 with the additional changes listed
here. This was done because the firmware update (v4.0.0) is not working right now and it is not here. This was done because the firmware update (v4.0.0) is not working right now and it is not
@ -28,10 +49,11 @@ known when and how it will be fixed. Because of that, all updates to make the SW
firmware, which are limited to a few files will be moved to a dev branch so regular development firmware, which are limited to a few files will be moved to a dev branch so regular development
compatible to the old firmware can continue. compatible to the old firmware can continue.
TLDR: This version is compatible to the old firmware and some changes which only work with the new
firmware have been reverted.
## Changed ## Changed
- This version is compatible to the old firmware and some changes which only work with the new
firmware have been reverted.
- Added `sync` syscall in graceful shutdown handler - Added `sync` syscall in graceful shutdown handler
- Graceful shutdown is now performed by the reboot watchdog - Graceful shutdown is now performed by the reboot watchdog
- There is now a separate file for the total reboot counter. The reboot watchdog has its own local - There is now a separate file for the total reboot counter. The reboot watchdog has its own local

View File

@ -68,7 +68,7 @@ void ObjectFactory::produce(void* args) {
#endif #endif
auto sdcMan = new DummySdCardManager("/tmp"); auto sdcMan = new DummySdCardManager("/tmp");
ObjectFactory::produceGenericObjects(nullptr, &pusFunnel, &cfdpFunnel, *sdcMan, &ipcStore, ObjectFactory::produceGenericObjects(nullptr, &pusFunnel, &cfdpFunnel, *sdcMan, &ipcStore,
&tmStore, persistentStores, 120); &tmStore, persistentStores, 120, enableHkSets);
new TmFunnelHandler(objects::LIVE_TM_TASK, *pusFunnel, *cfdpFunnel); new TmFunnelHandler(objects::LIVE_TM_TASK, *pusFunnel, *cfdpFunnel);
auto* dummyGpioIF = new DummyGpioIF(); auto* dummyGpioIF = new DummyGpioIF();

View File

@ -1224,6 +1224,10 @@ ReturnValue_t CoreController::actionReboot(const uint8_t *data, size_t size) {
ReturnValue_t CoreController::gracefulShutdownTasks(xsc::Chip chip, xsc::Copy copy, ReturnValue_t CoreController::gracefulShutdownTasks(xsc::Chip chip, xsc::Copy copy,
bool &protOpPerformed) { bool &protOpPerformed) {
// Store both sequence counters persistently.
core::SAVE_CFDP_SEQUENCE_COUNT = true;
core::SAVE_PUS_SEQUENCE_COUNT = true;
sdcMan->setBlocking(true); sdcMan->setBlocking(true);
sdcMan->markUnusable(); sdcMan->markUnusable();
// Wait two seconds to ensure no one uses the SD cards // Wait two seconds to ensure no one uses the SD cards

View File

@ -324,6 +324,10 @@ void scheduling::initTasks() {
if (result != returnvalue::OK) { if (result != returnvalue::OK) {
scheduling::printAddObjectError("HEATER_HANDLER", objects::HEATER_HANDLER); scheduling::printAddObjectError("HEATER_HANDLER", objects::HEATER_HANDLER);
} }
result = tcsSystemTask->addComponent(objects::HEATER_HANDLER);
if (result != returnvalue::OK) {
scheduling::printAddObjectError("HEATER_HANDLER", objects::HEATER_HANDLER);
}
#if OBSW_ADD_SYRLINKS == 1 #if OBSW_ADD_SYRLINKS == 1
PeriodicTaskIF* syrlinksCom = factory->createPeriodicTask( PeriodicTaskIF* syrlinksCom = factory->createPeriodicTask(

View File

@ -36,8 +36,8 @@ void ObjectFactory::produce(void* args) {
PersistentTmStores stores; PersistentTmStores stores;
ObjectFactory::produceGenericObjects(&healthTable, &pusFunnel, &cfdpFunnel, ObjectFactory::produceGenericObjects(&healthTable, &pusFunnel, &cfdpFunnel,
*SdCardManager::instance(), &ipcStore, &tmStore, stores, *SdCardManager::instance(), &ipcStore, &tmStore, stores, 200,
200); enableHkSets);
LinuxLibgpioIF* gpioComIF = nullptr; LinuxLibgpioIF* gpioComIF = nullptr;
SerialComIF* uartComIF = nullptr; SerialComIF* uartComIF = nullptr;

View File

@ -33,8 +33,8 @@ void ObjectFactory::produce(void* args) {
PersistentTmStores stores; PersistentTmStores stores;
ObjectFactory::produceGenericObjects(&healthTable, &pusFunnel, &cfdpFunnel, ObjectFactory::produceGenericObjects(&healthTable, &pusFunnel, &cfdpFunnel,
*SdCardManager::instance(), &ipcStore, &tmStore, stores, *SdCardManager::instance(), &ipcStore, &tmStore, stores, 200,
200); true);
LinuxLibgpioIF* gpioComIF = nullptr; LinuxLibgpioIF* gpioComIF = nullptr;
SerialComIF* uartComIF = nullptr; SerialComIF* uartComIF = nullptr;

View File

@ -11,6 +11,8 @@ static constexpr char SD_1_MOUNT_POINT[] = "/mnt/sd1";
static constexpr char OBSW_UPDATE_ARCHIVE_FILE_NAME[] = "eive-sw-update.tar.xz"; static constexpr char OBSW_UPDATE_ARCHIVE_FILE_NAME[] = "eive-sw-update.tar.xz";
static constexpr char STRIPPED_OBSW_BINARY_FILE_NAME[] = "eive-obsw-stripped"; static constexpr char STRIPPED_OBSW_BINARY_FILE_NAME[] = "eive-obsw-stripped";
static constexpr char OBSW_VERSION_FILE_NAME[] = "obsw_version.txt"; static constexpr char OBSW_VERSION_FILE_NAME[] = "obsw_version.txt";
static constexpr char PUS_SEQUENCE_COUNT_FILE[] = "pus-sequence-count.txt";
static constexpr char CFDP_SEQUENCE_COUNT_FILE[] = "cfdp-sequence-count.txt";
static constexpr char OBSW_PATH[] = "/usr/bin/eive-obsw"; static constexpr char OBSW_PATH[] = "/usr/bin/eive-obsw";
static constexpr char OBSW_VERSION_FILE_PATH[] = "/usr/share/eive-obsw/obsw_version.txt"; static constexpr char OBSW_VERSION_FILE_PATH[] = "/usr/share/eive-obsw/obsw_version.txt";

View File

@ -96,6 +96,25 @@ ReturnValue_t TemperatureSensorInserter::performOperation(uint8_t opCode) {
} }
break; break;
} }
case (TestCase::COLD_PLOC_CONSECUTIVE): {
if (cycles == 15) {
sif::debug << "Setting cold PLOC temperature" << std::endl;
max31865DummyMap[objects::RTD_0_IC3_PLOC_HEATSPREADER]->setTemperature(-15, true);
}
if (cycles == 30) {
sif::debug << "Setting warmer PLOC temperature" << std::endl;
max31865DummyMap[objects::RTD_0_IC3_PLOC_HEATSPREADER]->setTemperature(0, true);
}
if (cycles == 45) {
sif::debug << "Setting cold PLOC temperature again" << std::endl;
max31865DummyMap[objects::RTD_0_IC3_PLOC_HEATSPREADER]->setTemperature(-15, true);
}
if (cycles == 60) {
sif::debug << "Setting warmer PLOC temperature again" << std::endl;
max31865DummyMap[objects::RTD_0_IC3_PLOC_HEATSPREADER]->setTemperature(0, true);
}
break;
}
case (TestCase::COLD_CAMERA): { case (TestCase::COLD_CAMERA): {
if (cycles == 15) { if (cycles == 15) {
sif::debug << "Setting cold CAM temperature" << std::endl; sif::debug << "Setting cold CAM temperature" << std::endl;

View File

@ -32,6 +32,7 @@ class TemperatureSensorInserter : public ExecutableObjectIF, public SystemObject
COLD_STR = 4, COLD_STR = 4,
COLD_STR_CONSECUTIVE = 5, COLD_STR_CONSECUTIVE = 5,
COLD_CAMERA = 6, COLD_CAMERA = 6,
COLD_PLOC_CONSECUTIVE = 7,
}; };
int iteration = 0; int iteration = 0;
uint32_t cycles = 0; uint32_t cycles = 0;

2
fsfw

@ -1 +1 @@
Subproject commit 0f76cdb3ba54f5e90a8eee4316c49cf0f581f996 Subproject commit 8da89eba80f73cb05e5c38fc012456f1d9569af5

View File

@ -773,11 +773,13 @@ void SyrlinksHandler::doTransition(Mode_t modeFrom, Submode_t subModeFrom) {
auto txStandbyHandler = [&]() { auto txStandbyHandler = [&]() {
txDataset.setReportingEnabled(false); txDataset.setReportingEnabled(false);
poolManager.changeCollectionInterval(temperatureSet.getSid(), 60.0); poolManager.changeCollectionInterval(temperatureSet.getSid(), 60.0);
poolManager.changeCollectionInterval(rxDataset.getSid(), 60.0);
transState = TransitionState::SET_TX_STANDBY; transState = TransitionState::SET_TX_STANDBY;
internalState = InternalState::TX_TRANSITION; internalState = InternalState::TX_TRANSITION;
}; };
auto txOnHandler = [&](TransitionState tgtTransitionState) { auto txOnHandler = [&](TransitionState tgtTransitionState) {
txDataset.setReportingEnabled(true); txDataset.setReportingEnabled(true);
poolManager.changeCollectionInterval(rxDataset.getSid(), 5.0);
poolManager.changeCollectionInterval(txDataset.getSid(), 10.0); poolManager.changeCollectionInterval(txDataset.getSid(), 10.0);
poolManager.changeCollectionInterval(temperatureSet.getSid(), 5.0); poolManager.changeCollectionInterval(temperatureSet.getSid(), 5.0);
transState = tgtTransitionState; transState = tgtTransitionState;

View File

@ -91,19 +91,21 @@ EiveFaultHandler EIVE_FAULT_HANDLER;
} // namespace cfdp } // namespace cfdp
std::atomic_bool tcs::TCS_BOARD_SHORTLY_UNAVAILABLE = false; std::atomic_bool tcs::TCS_BOARD_SHORTLY_UNAVAILABLE = false;
std::atomic_bool core::SAVE_PUS_SEQUENCE_COUNT = false;
std::atomic_bool core::SAVE_CFDP_SEQUENCE_COUNT = false;
void ObjectFactory::produceGenericObjects(HealthTableIF** healthTable_, PusTmFunnel** pusFunnel, void ObjectFactory::produceGenericObjects(HealthTableIF** healthTable_, PusTmFunnel** pusFunnel,
CfdpTmFunnel** cfdpFunnel, SdCardMountedIF& sdcMan, CfdpTmFunnel** cfdpFunnel, SdCardMountedIF& sdcMan,
StorageManagerIF** ipcStore, StorageManagerIF** tmStore, StorageManagerIF** ipcStore, StorageManagerIF** tmStore,
PersistentTmStores& stores, PersistentTmStores& stores,
uint32_t eventManagerQueueDepth) { uint32_t eventManagerQueueDepth, bool enableHkSets) {
// Framework objects // Framework objects
new EventManager(objects::EVENT_MANAGER, eventManagerQueueDepth); new EventManager(objects::EVENT_MANAGER, eventManagerQueueDepth);
auto healthTable = new HealthTable(objects::HEALTH_TABLE); auto healthTable = new HealthTable(objects::HEALTH_TABLE);
if (healthTable_ != nullptr) { if (healthTable_ != nullptr) {
*healthTable_ = healthTable; *healthTable_ = healthTable;
} }
new InternalErrorReporter(objects::INTERNAL_ERROR_REPORTER); new InternalErrorReporter(objects::INTERNAL_ERROR_REPORTER, 5, enableHkSets, 120);
new VerificationReporter(); new VerificationReporter();
auto* timeStamper = new CdsShortTimeStamper(objects::TIME_STAMPER); auto* timeStamper = new CdsShortTimeStamper(objects::TIME_STAMPER);
StorageManagerIF* tcStore; StorageManagerIF* tcStore;
@ -155,9 +157,11 @@ void ObjectFactory::produceGenericObjects(HealthTableIF** healthTable_, PusTmFun
new PusDistributor(config::EIVE_PUS_APID, objects::PUS_PACKET_DISTRIBUTOR, ccsdsDistrib); new PusDistributor(config::EIVE_PUS_APID, objects::PUS_PACKET_DISTRIBUTOR, ccsdsDistrib);
PusTmFunnel::FunnelCfg pusFunnelCfg(objects::PUS_TM_FUNNEL, "PusTmFunnel", **tmStore, **ipcStore, PusTmFunnel::FunnelCfg pusFunnelCfg(objects::PUS_TM_FUNNEL, "PusTmFunnel", **tmStore, **ipcStore,
config::MAX_PUS_FUNNEL_QUEUE_DEPTH); config::MAX_PUS_FUNNEL_QUEUE_DEPTH, sdcMan,
config::PUS_SEQUENCE_COUNT_FILE,
core::SAVE_PUS_SEQUENCE_COUNT);
// The PUS funnel routes all live TM to the live destinations and to the TM stores. // The PUS funnel routes all live TM to the live destinations and to the TM stores.
*pusFunnel = new PusTmFunnel(pusFunnelCfg, *ramToFileStore, *timeStamper, sdcMan); *pusFunnel = new PusTmFunnel(pusFunnelCfg, *ramToFileStore, *timeStamper);
// MISC store and PUS funnel to MISC store routing // MISC store and PUS funnel to MISC store routing
{ {
@ -216,7 +220,9 @@ void ObjectFactory::produceGenericObjects(HealthTableIF** healthTable_, PusTmFun
stores.cfdpStore->getReportReceptionQueue(0)); stores.cfdpStore->getReportReceptionQueue(0));
} }
PusTmFunnel::FunnelCfg cfdpFunnelCfg(objects::CFDP_TM_FUNNEL, "CfdpTmFunnel", **tmStore, PusTmFunnel::FunnelCfg cfdpFunnelCfg(objects::CFDP_TM_FUNNEL, "CfdpTmFunnel", **tmStore,
**ipcStore, config::MAX_CFDP_FUNNEL_QUEUE_DEPTH); **ipcStore, config::MAX_CFDP_FUNNEL_QUEUE_DEPTH, sdcMan,
config::CFDP_SEQUENCE_COUNT_FILE,
core::SAVE_CFDP_SEQUENCE_COUNT);
*cfdpFunnel = new CfdpTmFunnel(cfdpFunnelCfg, stores.cfdpStore->getReportReceptionQueue(0), *cfdpFunnel = new CfdpTmFunnel(cfdpFunnelCfg, stores.cfdpStore->getReportReceptionQueue(0),
*ramToFileStore, config::EIVE_CFDP_APID); *ramToFileStore, config::EIVE_CFDP_APID);

View File

@ -45,7 +45,8 @@ namespace ObjectFactory {
void produceGenericObjects(HealthTableIF** healthTable, PusTmFunnel** pusFunnel, void produceGenericObjects(HealthTableIF** healthTable, PusTmFunnel** pusFunnel,
CfdpTmFunnel** cfdpFunnel, SdCardMountedIF& sdcMan, CfdpTmFunnel** cfdpFunnel, SdCardMountedIF& sdcMan,
StorageManagerIF** ipcStore, StorageManagerIF** tmStore, StorageManagerIF** ipcStore, StorageManagerIF** tmStore,
PersistentTmStores& stores, uint32_t eventManagerQueueDepth); PersistentTmStores& stores, uint32_t eventManagerQueueDepth,
bool enableHkSets);
void createGenericHeaterComponents(GpioIF& gpioIF, PowerSwitchIF& pwrSwitcher, void createGenericHeaterComponents(GpioIF& gpioIF, PowerSwitchIF& pwrSwitcher,
HeaterHandler*& heaterHandler); HeaterHandler*& heaterHandler);

View File

@ -34,6 +34,9 @@ enum Copy : int { COPY_0, COPY_1, NO_COPY, SELF_COPY, ALL_COPY };
namespace core { namespace core {
extern std::atomic_bool SAVE_PUS_SEQUENCE_COUNT;
extern std::atomic_bool SAVE_CFDP_SEQUENCE_COUNT;
// TODO: Support for status? Or maybe some command to quickly get information whether a unit // TODO: Support for status? Or maybe some command to quickly get information whether a unit
// is running. // is running.
enum SystemctlCmd : uint8_t { START = 0, STOP = 1, RESTART = 2, NUM_CMDS = 3 }; enum SystemctlCmd : uint8_t { START = 0, STOP = 1, RESTART = 2, NUM_CMDS = 3 };

View File

@ -51,9 +51,13 @@ ReturnValue_t HeaterHandler::performOperation(uint8_t operationCode) {
if (mainLineSwitcher->getSwitchState(mainLineSwitch) == SWITCH_OFF) { if (mainLineSwitcher->getSwitchState(mainLineSwitch) == SWITCH_OFF) {
waitForSwitchOff = false; waitForSwitchOff = false;
mode = MODE_OFF; mode = MODE_OFF;
busyWithSwitchCommanding = false;
modeHelper.modeChanged(mode, submode); modeHelper.modeChanged(mode, submode);
} }
} }
if (busyWithSwitchCommanding and heaterCmdBusyCd.hasTimedOut()) {
busyWithSwitchCommanding = false;
}
} catch (const std::out_of_range& e) { } catch (const std::out_of_range& e) {
sif::warning << "HeaterHandler::performOperation: " sif::warning << "HeaterHandler::performOperation: "
"Out of range error | " "Out of range error | "
@ -101,23 +105,23 @@ ReturnValue_t HeaterHandler::initializeHeaterMap() {
void HeaterHandler::readCommandQueue() { void HeaterHandler::readCommandQueue() {
ReturnValue_t result = returnvalue::OK; ReturnValue_t result = returnvalue::OK;
CommandMessage command; CommandMessage command;
do { if (not busyWithSwitchCommanding) {
result = commandQueue->receiveMessage(&command); result = commandQueue->receiveMessage(&command);
if (result == MessageQueueIF::EMPTY) { if (result == MessageQueueIF::EMPTY) {
break; return;
} else if (result != returnvalue::OK) { } else if (result != returnvalue::OK) {
sif::warning << "HeaterHandler::readCommandQueue: Message reception error" << std::endl; sif::warning << "HeaterHandler::readCommandQueue: Message reception error" << std::endl;
break; return;
}
result = actionHelper.handleActionMessage(&command);
if (result == returnvalue::OK) {
continue;
} }
result = modeHelper.handleModeCommand(&command); result = modeHelper.handleModeCommand(&command);
if (result == returnvalue::OK) { if (result == returnvalue::OK) {
continue; return;
} }
} while (result == returnvalue::OK); result = actionHelper.handleActionMessage(&command);
if (result == returnvalue::OK) {
return;
}
}
} }
ReturnValue_t HeaterHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy, ReturnValue_t HeaterHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy,
@ -167,6 +171,8 @@ ReturnValue_t HeaterHandler::executeAction(ActionId_t actionId, MessageQueueId_t
heater.action = action; heater.action = action;
heater.cmdActive = true; heater.cmdActive = true;
heater.replyQueue = commandedBy; heater.replyQueue = commandedBy;
busyWithSwitchCommanding = true;
heaterCmdBusyCd.resetTimer();
return returnvalue::OK; return returnvalue::OK;
} }
@ -249,6 +255,7 @@ void HeaterHandler::handleSwitchOnCommand(heater::Switch heaterIdx) {
sif::error << "HeaterHandler::handleSwitchOnCommand: Main switch setting on timeout" sif::error << "HeaterHandler::handleSwitchOnCommand: Main switch setting on timeout"
<< std::endl; << std::endl;
heater.cmdActive = false; heater.cmdActive = false;
busyWithSwitchCommanding = false;
heater.waitMainSwitchOn = false; heater.waitMainSwitchOn = false;
if (heater.replyQueue != commandQueue->getId()) { if (heater.replyQueue != commandQueue->getId()) {
actionHelper.finish(false, heater.replyQueue, heater.action, MAIN_SWITCH_SET_TIMEOUT); actionHelper.finish(false, heater.replyQueue, heater.action, MAIN_SWITCH_SET_TIMEOUT);
@ -259,27 +266,25 @@ void HeaterHandler::handleSwitchOnCommand(heater::Switch heaterIdx) {
// Check state of main line switch // Check state of main line switch
ReturnValue_t mainSwitchState = mainLineSwitcher->getSwitchState(mainLineSwitch); ReturnValue_t mainSwitchState = mainLineSwitcher->getSwitchState(mainLineSwitch);
if (mainSwitchState == PowerSwitchIF::SWITCH_ON) { if (mainSwitchState == PowerSwitchIF::SWITCH_ON) {
if (getSwitchState(heaterIdx) == SwitchState::OFF) { gpioId_t gpioId = heater.gpioId;
gpioId_t gpioId = heater.gpioId; result = gpioInterface->pullHigh(gpioId);
result = gpioInterface->pullHigh(gpioId); if (result != returnvalue::OK) {
if (result != returnvalue::OK) { sif::error << "HeaterHandler::handleSwitchOnCommand: Failed to pull GPIO with ID " << gpioId
sif::error << "HeaterHandler::handleSwitchOnCommand: Failed to pull gpio with id " << gpioId << " high" << std::endl;
<< " high" << std::endl; triggerEvent(GPIO_PULL_HIGH_FAILED, result);
triggerEvent(GPIO_PULL_HIGH_FAILED, result); }
} else { if (result == returnvalue::OK) {
triggerEvent(HEATER_WENT_ON, heaterIdx, 0); triggerEvent(HEATER_WENT_ON, heaterIdx, 0);
EventManagerIF::triggerEvent(helper.heaters[heaterIdx].first->getObjectId(), MODE_INFO, {
MODE_ON, 0); MutexGuard mg(handlerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX);
{ heater.switchState = ON;
MutexGuard mg(handlerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); }
heater.switchState = ON; EventManagerIF::triggerEvent(helper.heaters[heaterIdx].first->getObjectId(), MODE_INFO,
} MODE_ON, 0);
} busyWithSwitchCommanding = false;
} else { mode = HasModesIF::MODE_ON;
triggerEvent(SWITCH_ALREADY_ON, heaterIdx); modeHelper.modeChanged(mode, submode);
} }
mode = HasModesIF::MODE_ON;
modeHelper.modeChanged(mode, submode);
// There is no need to send action finish replies if the sender was the // There is no need to send action finish replies if the sender was the
// HeaterHandler itself // HeaterHandler itself
if (heater.replyQueue != commandQueue->getId()) { if (heater.replyQueue != commandQueue->getId()) {
@ -312,30 +317,33 @@ void HeaterHandler::handleSwitchOnCommand(heater::Switch heaterIdx) {
void HeaterHandler::handleSwitchOffCommand(heater::Switch heaterIdx) { void HeaterHandler::handleSwitchOffCommand(heater::Switch heaterIdx) {
ReturnValue_t result = returnvalue::OK; ReturnValue_t result = returnvalue::OK;
auto& heater = heaterVec.at(heaterIdx); auto& heater = heaterVec.at(heaterIdx);
// Check whether switch is already off gpioId_t gpioId = heater.gpioId;
if (getSwitchState(heaterIdx)) { result = gpioInterface->pullLow(gpioId);
gpioId_t gpioId = heater.gpioId; if (result != returnvalue::OK) {
result = gpioInterface->pullLow(gpioId); sif::error << "HeaterHandler::handleSwitchOffCommand: Failed to pull gpio with id" << gpioId
if (result != returnvalue::OK) { << " low" << std::endl;
sif::error << "HeaterHandler::handleSwitchOffCommand: Failed to pull gpio with id" << gpioId triggerEvent(GPIO_PULL_LOW_FAILED, result);
<< " low" << std::endl; }
triggerEvent(GPIO_PULL_LOW_FAILED, result); if (result == returnvalue::OK) {
} else { // Check whether switch is already off
if (getSwitchState(heaterIdx) == SwitchState::ON) {
{ {
MutexGuard mg(handlerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX); MutexGuard mg(handlerLock, LOCK_TYPE, LOCK_TIMEOUT, LOCK_CTX);
heater.switchState = OFF; heater.switchState = OFF;
} }
triggerEvent(HEATER_WENT_OFF, heaterIdx, 0); triggerEvent(HEATER_WENT_OFF, heaterIdx, 0);
EventManagerIF::triggerEvent(helper.heaters[heaterIdx].first->getObjectId(), MODE_INFO, } else {
MODE_OFF, 0); triggerEvent(SWITCH_ALREADY_OFF, heaterIdx);
// When all switches are off, also main line switch will be turned off }
if (allSwitchesOff()) { EventManagerIF::triggerEvent(helper.heaters[heaterIdx].first->getObjectId(), MODE_INFO,
mainLineSwitcher->sendSwitchCommand(mainLineSwitch, PowerSwitchIF::SWITCH_OFF); MODE_OFF, 0);
waitForSwitchOff = true; // When all switches are off, also main line switch will be turned off
} if (allSwitchesOff()) {
mainLineSwitcher->sendSwitchCommand(mainLineSwitch, PowerSwitchIF::SWITCH_OFF);
waitForSwitchOff = true;
} else {
busyWithSwitchCommanding = false;
} }
} else {
triggerEvent(SWITCH_ALREADY_OFF, heaterIdx);
} }
if (heater.replyQueue != NO_COMMANDER) { if (heater.replyQueue != NO_COMMANDER) {
// Report back switch command reply if necessary // Report back switch command reply if necessary

View File

@ -148,6 +148,7 @@ class HeaterHandler : public ExecutableObjectIF,
/** Size of command queue */ /** Size of command queue */
size_t cmdQueueSize = 20; size_t cmdQueueSize = 20;
bool waitForSwitchOff = true; bool waitForSwitchOff = true;
bool busyWithSwitchCommanding = false;
GpioIF* gpioInterface = nullptr; GpioIF* gpioInterface = nullptr;
@ -163,6 +164,7 @@ class HeaterHandler : public ExecutableObjectIF,
power::Switch_t mainLineSwitch; power::Switch_t mainLineSwitch;
ActionHelper actionHelper; ActionHelper actionHelper;
Countdown heaterCmdBusyCd = Countdown(2000);
StorageManagerIF* ipcStore = nullptr; StorageManagerIF* ipcStore = nullptr;

View File

@ -15,8 +15,16 @@ const char* CfdpTmFunnel::getName() const { return "CFDP TM Funnel"; }
ReturnValue_t CfdpTmFunnel::performOperation(uint8_t) { ReturnValue_t CfdpTmFunnel::performOperation(uint8_t) {
TmTcMessage currentMessage; TmTcMessage currentMessage;
ReturnValue_t status;
unsigned int count = 0; unsigned int count = 0;
ReturnValue_t status = tmQueue->receiveMessage(&currentMessage); if (saveSequenceCount) {
status = saveSequenceCountToFile();
if (status != returnvalue::OK) {
sif::error << "CfdpTmFunnel: Storing sequence count to file has failed" << std::endl;
}
saveSequenceCount = false;
}
status = tmQueue->receiveMessage(&currentMessage);
while (status == returnvalue::OK) { while (status == returnvalue::OK) {
status = handlePacket(currentMessage); status = handlePacket(currentMessage);
if (status != returnvalue::OK) { if (status != returnvalue::OK) {

View File

@ -3,6 +3,7 @@
#include <mission/tmtc/TmFunnelBase.h> #include <mission/tmtc/TmFunnelBase.h>
#include <atomic>
#include <vector> #include <vector>
#include "fsfw/objectmanager/SystemObject.h" #include "fsfw/objectmanager/SystemObject.h"
@ -23,7 +24,6 @@ class CfdpTmFunnel : public TmFunnelBase {
MessageQueueId_t fileStoreDest; MessageQueueId_t fileStoreDest;
StorageManagerIF& ramToFileStore; StorageManagerIF& ramToFileStore;
uint16_t sourceSequenceCount = 0;
uint16_t cfdpInCcsdsApid; uint16_t cfdpInCcsdsApid;
}; };
#endif // FSFW_EXAMPLE_COMMON_CFDPTMFUNNEL_H #endif // FSFW_EXAMPLE_COMMON_CFDPTMFUNNEL_H

View File

@ -17,6 +17,8 @@
using namespace returnvalue; using namespace returnvalue;
static constexpr bool DEBUG_DUMPS = false;
PersistentTmStore::PersistentTmStore(PersistentTmStoreArgs args) PersistentTmStore::PersistentTmStore(PersistentTmStoreArgs args)
: SystemObject(args.objectId), : SystemObject(args.objectId),
tmStore(args.tmStore), tmStore(args.tmStore),
@ -32,6 +34,91 @@ ReturnValue_t PersistentTmStore::cancelDump() {
return returnvalue::OK; return returnvalue::OK;
} }
ReturnValue_t PersistentTmStore::buildDumpSet(uint32_t fromUnixSeconds, uint32_t upToUnixSeconds) {
using namespace std::filesystem;
std::error_code e;
dumpParams.orderedDumpFilestamps.clear();
for (auto const& fileOrDir : directory_iterator(basePath)) {
if (not fileOrDir.is_regular_file(e)) {
continue;
}
dumpParams.fileSize = std::filesystem::file_size(fileOrDir.path(), e);
if (e) {
sif::error << "PersistentTmStore: Could not retrieve file size: " << e.message() << std::endl;
continue;
}
// File empty or can't even read CCSDS header.
if (dumpParams.fileSize <= 6) {
continue;
}
if (dumpParams.fileSize > fileBuf.size()) {
sif::error << "PersistentTmStore: File too large, is deleted" << std::endl;
triggerEvent(persTmStore::FILE_TOO_LARGE, dumpParams.fileSize, fileBuf.size());
std::filesystem::remove(fileOrDir.path(), e);
continue;
}
const path& file = fileOrDir.path();
struct tm fileTime {};
if (pathToTime(file, fileTime) != returnvalue::OK) {
sif::error << "Time extraction for file " << file << "failed" << std::endl;
continue;
}
auto fileEpoch = static_cast<uint32_t>(timegm(&fileTime));
if ((fileEpoch > dumpParams.fromUnixTime) and
(fileEpoch + rolloverDiffSeconds <= dumpParams.untilUnixTime)) {
std::ifstream ifile(file, std::ios::binary);
if (ifile.bad()) {
sif::error << "PersistentTmStore: File is bad" << std::endl;
// TODO: Consider deleting file here?
continue;
}
if (DEBUG_DUMPS) {
sif::debug << "Inserting file " << fileOrDir.path() << std::endl;
}
DumpIndex dumpIndex;
dumpIndex.epoch = fileEpoch;
// Multiple files for the same time are supported via a special suffix. We simply count the
// number of copies and later try to dump the same number of files with the additional
// suffixes
auto iter = dumpParams.orderedDumpFilestamps.find(dumpIndex);
if (iter != dumpParams.orderedDumpFilestamps.end()) {
dumpIndex.epoch = iter->epoch;
dumpIndex.additionalFiles = iter->additionalFiles + 1;
dumpParams.orderedDumpFilestamps.erase(dumpIndex);
} else {
dumpIndex.additionalFiles = 0;
}
dumpParams.orderedDumpFilestamps.emplace(dumpIndex);
}
}
return returnvalue::OK;
}
std::optional<uint8_t> PersistentTmStore::extractSuffix(const std::string& pathStr) {
std::string numberStr;
// Find the position of the dot at the end of the file path
size_t dotPos = pathStr.find_last_of('.');
if ((dotPos < pathStr.length()) and not std::isdigit(pathStr[dotPos + 1])) {
return std::nullopt;
}
// Extract the substring after the dot
numberStr = pathStr.substr(dotPos + 1);
std::optional<uint8_t> number;
try {
number = std::stoi(numberStr);
if (number.value() > std::numeric_limits<uint8_t>::max()) {
return std::nullopt;
}
} catch (std::invalid_argument& exception) {
sif::error << "PersistentTmStore::extractSuffix: Exception " << exception.what()
<< ", invald input string: " << numberStr << std::endl;
}
return number;
}
ReturnValue_t PersistentTmStore::assignAndOrCreateMostRecentFile() { ReturnValue_t PersistentTmStore::assignAndOrCreateMostRecentFile() {
if (not activeFile.has_value()) { if (not activeFile.has_value()) {
return createMostRecentFile(std::nullopt); return createMostRecentFile(std::nullopt);
@ -159,6 +246,12 @@ bool PersistentTmStore::updateBaseDir() {
if (not exists(basePath, e)) { if (not exists(basePath, e)) {
create_directories(basePath); create_directories(basePath);
} }
// Each file will have the base name as a prefix again
path preparedFullFilePath = basePath / baseName;
basePathSize = preparedFullFilePath.string().length();
std::memcpy(filePathBuf.data(), preparedFullFilePath.c_str(), basePathSize);
filePathBuf[basePathSize] = '_';
basePathSize += 1;
baseDirUninitialized = false; baseDirUninitialized = false;
return true; return true;
} }
@ -189,12 +282,20 @@ ReturnValue_t PersistentTmStore::startDumpFromUpTo(uint32_t fromUnixSeconds,
if (state == State::DUMPING) { if (state == State::DUMPING) {
return returnvalue::FAILED; return returnvalue::FAILED;
} }
dumpParams.dirIter = directory_iterator(basePath); auto dirIter = directory_iterator(basePath);
if (dumpParams.dirIter == directory_iterator()) { // Directory empty case.
if (dirIter == directory_iterator()) {
return returnvalue::FAILED; return returnvalue::FAILED;
} }
dumpParams.fromUnixTime = fromUnixSeconds; dumpParams.fromUnixTime = fromUnixSeconds;
dumpParams.untilUnixTime = upToUnixSeconds; dumpParams.untilUnixTime = upToUnixSeconds;
buildDumpSet(fromUnixSeconds, upToUnixSeconds);
// No files in time window found.
if (dumpParams.orderedDumpFilestamps.empty()) {
return DUMP_DONE;
}
dumpParams.dumpIter = dumpParams.orderedDumpFilestamps.begin();
dumpParams.currentSameFileIdx = std::nullopt;
state = State::DUMPING; state = State::DUMPING;
return loadNextDumpFile(); return loadNextDumpFile();
} }
@ -203,49 +304,54 @@ ReturnValue_t PersistentTmStore::loadNextDumpFile() {
using namespace std::filesystem; using namespace std::filesystem;
dumpParams.currentSize = 0; dumpParams.currentSize = 0;
std::error_code e; std::error_code e;
for (; dumpParams.dirIter != directory_iterator(); dumpParams.dirIter++) { // Handle iteration, which does not necessarily have to increment the set iterator
dumpParams.dirEntry = *dumpParams.dirIter; // if there are multiple files for a given timestamp.
if (dumpParams.dirEntry.is_directory(e)) { auto handleIteration = [&](DumpIndex& dumpIndex) {
continue; if (dumpIndex.additionalFiles > 0) {
} if (not dumpParams.currentSameFileIdx.has_value()) {
dumpParams.fileSize = std::filesystem::file_size(dumpParams.dirEntry.path(), e); // Initialize the file index and stay on same file
if (e) { dumpParams.currentSameFileIdx = 0;
sif::error << "PersistentTmStore: Could not retrieve file size: " << e.message() << std::endl; return;
continue; } else if (dumpParams.currentSameFileIdx.value() < dumpIndex.additionalFiles - 1) {
} dumpParams.currentSameFileIdx = dumpParams.currentSameFileIdx.value() + 1;
// sif::debug << "Path: " << dumpParams.dirEntry.path() << std::endl; return;
// File empty or can't even read CCSDS header.
if (dumpParams.fileSize <= 6) {
continue;
}
if (dumpParams.fileSize > fileBuf.size()) {
sif::error << "PersistentTmStore: File too large, is deleted" << std::endl;
triggerEvent(persTmStore::FILE_TOO_LARGE, dumpParams.fileSize, fileBuf.size());
std::filesystem::remove(dumpParams.dirEntry.path(), e);
continue;
}
const path& file = dumpParams.dirEntry.path();
struct tm fileTime {};
if (pathToTime(file, fileTime) != returnvalue::OK) {
sif::error << "Time extraction for file " << file << "failed" << std::endl;
continue;
}
auto fileEpoch = static_cast<uint32_t>(timegm(&fileTime));
if ((fileEpoch > dumpParams.fromUnixTime) and
(fileEpoch + rolloverDiffSeconds <= dumpParams.untilUnixTime)) {
dumpParams.currentFileUnixStamp = fileEpoch;
std::ifstream ifile(file, std::ios::binary);
if (ifile.bad()) {
sif::error << "PersistentTmStore: File is bad" << std::endl;
continue;
} }
ifile.read(reinterpret_cast<char*>(fileBuf.data()),
static_cast<std::streamsize>(dumpParams.fileSize));
// Increment iterator for next cycle.
dumpParams.dirIter++;
return returnvalue::OK;
} }
// File will change, reset this field for correct state-keeping.
dumpParams.currentSameFileIdx = std::nullopt;
// Increment iterator for next cycle.
dumpParams.dumpIter++;
};
while (dumpParams.dumpIter != dumpParams.orderedDumpFilestamps.end()) {
DumpIndex dumpIndex = *dumpParams.dumpIter;
timeval tv{};
tv.tv_sec = dumpIndex.epoch;
size_t fullPathLength = 0;
createFileName(tv, dumpParams.currentSameFileIdx, fullPathLength);
dumpParams.currentFile =
path(std::string(reinterpret_cast<const char*>(filePathBuf.data()), fullPathLength));
if (DEBUG_DUMPS) {
sif::debug << baseName << " dump: Loading " << dumpParams.currentFile << std::endl;
}
dumpParams.fileSize = std::filesystem::file_size(dumpParams.currentFile, e);
if (e) {
// TODO: Event?
sif::error << "PersistentTmStore: Could not load next dump file: " << e.message()
<< std::endl;
handleIteration(dumpIndex);
continue;
}
std::ifstream ifile(dumpParams.currentFile, std::ios::binary);
if (ifile.bad()) {
sif::error << "PersistentTmStore: File is bad. Loading next file" << std::endl;
handleIteration(dumpIndex);
continue;
}
ifile.read(reinterpret_cast<char*>(fileBuf.data()),
static_cast<std::streamsize>(dumpParams.fileSize));
// Next file is loaded, exit the loop.
handleIteration(dumpIndex);
return returnvalue::OK;
} }
// Directory iterator was consumed and we are done. // Directory iterator was consumed and we are done.
state = State::IDLE; state = State::IDLE;
@ -267,8 +373,8 @@ ReturnValue_t PersistentTmStore::getNextDumpPacket(PusTmReader& reader, bool& fi
// Delete the file and load next. Could use better algorithm to partially // Delete the file and load next. Could use better algorithm to partially
// restore the file dump, but for now do not trust the file. // restore the file dump, but for now do not trust the file.
std::error_code e; std::error_code e;
std::filesystem::remove(dumpParams.dirEntry.path().c_str(), e); std::filesystem::remove(dumpParams.currentFile.c_str(), e);
if (dumpParams.dirEntry.path() == activeFile) { if (dumpParams.currentFile == activeFile) {
activeFile == std::nullopt; activeFile == std::nullopt;
assignAndOrCreateMostRecentFile(); assignAndOrCreateMostRecentFile();
} }
@ -302,37 +408,9 @@ ReturnValue_t PersistentTmStore::pathToTime(const std::filesystem::path& path, s
ReturnValue_t PersistentTmStore::createMostRecentFile(std::optional<uint8_t> suffix) { ReturnValue_t PersistentTmStore::createMostRecentFile(std::optional<uint8_t> suffix) {
using namespace std::filesystem; using namespace std::filesystem;
unsigned currentIdx = 0; size_t currentIdx;
path pathStart = basePath / baseName; createFileName(currentTv, suffix, currentIdx);
memcpy(fileBuf.data() + currentIdx, pathStart.c_str(), pathStart.string().length()); path newPath(std::string(reinterpret_cast<const char*>(filePathBuf.data()), currentIdx));
currentIdx += pathStart.string().length();
fileBuf[currentIdx] = '_';
currentIdx += 1;
time_t epoch = currentTv.tv_sec;
struct tm* time = gmtime(&epoch);
size_t writtenBytes = strftime(reinterpret_cast<char*>(fileBuf.data() + currentIdx),
fileBuf.size(), config::FILE_DATE_FORMAT, time);
if (writtenBytes == 0) {
sif::error << "PersistentTmStore::createMostRecentFile: Could not create file timestamp"
<< std::endl;
return returnvalue::FAILED;
}
currentIdx += writtenBytes;
char* res = strcpy(reinterpret_cast<char*>(fileBuf.data() + currentIdx), ".bin");
if (res == nullptr) {
return returnvalue::FAILED;
}
currentIdx += 4;
if (suffix.has_value()) {
std::string fullSuffix = "." + std::to_string(suffix.value());
res = strcpy(reinterpret_cast<char*>(fileBuf.data() + currentIdx), fullSuffix.c_str());
if (res == nullptr) {
return returnvalue::FAILED;
}
currentIdx += fullSuffix.size();
}
path newPath(std::string(reinterpret_cast<const char*>(fileBuf.data()), currentIdx));
std::ofstream of(newPath, std::ios::binary); std::ofstream of(newPath, std::ios::binary);
activeFile = newPath; activeFile = newPath;
activeFileTv = currentTv; activeFileTv = currentTv;
@ -354,3 +432,33 @@ void PersistentTmStore::getStartAndEndTimeCurrentOrLastDump(uint32_t& startTime,
startTime = dumpParams.fromUnixTime; startTime = dumpParams.fromUnixTime;
endTime = dumpParams.untilUnixTime; endTime = dumpParams.untilUnixTime;
} }
ReturnValue_t PersistentTmStore::createFileName(timeval& tv, std::optional<uint8_t> suffix,
size_t& fullPathLength) {
unsigned currentIdx = basePathSize;
time_t epoch = tv.tv_sec;
struct tm* time = gmtime(&epoch);
size_t writtenBytes = strftime(reinterpret_cast<char*>(filePathBuf.data() + currentIdx),
filePathBuf.size(), config::FILE_DATE_FORMAT, time);
if (writtenBytes == 0) {
sif::error << "PersistentTmStore::createMostRecentFile: Could not create file timestamp"
<< std::endl;
return returnvalue::FAILED;
}
currentIdx += writtenBytes;
char* res = strcpy(reinterpret_cast<char*>(filePathBuf.data() + currentIdx), ".bin");
if (res == nullptr) {
return returnvalue::FAILED;
}
currentIdx += 4;
if (suffix.has_value()) {
std::string fullSuffix = "." + std::to_string(suffix.value());
res = strcpy(reinterpret_cast<char*>(filePathBuf.data() + currentIdx), fullSuffix.c_str());
if (res == nullptr) {
return returnvalue::FAILED;
}
currentIdx += fullSuffix.size();
}
fullPathLength = currentIdx;
return returnvalue::OK;
}

View File

@ -10,6 +10,7 @@
#include <mission/memory/SdCardMountedIF.h> #include <mission/memory/SdCardMountedIF.h>
#include <filesystem> #include <filesystem>
#include <set>
#include "eive/eventSubsystemIds.h" #include "eive/eventSubsystemIds.h"
#include "eive/resultClassIds.h" #include "eive/resultClassIds.h"
@ -37,6 +38,14 @@ struct PersistentTmStoreArgs {
SdCardMountedIF& sdcMan; SdCardMountedIF& sdcMan;
}; };
struct DumpIndex {
uint32_t epoch = 0;
// Number of additional files with a suffix like .0, .1 etc.
uint8_t additionalFiles = 0;
// Define a custom comparison function based on the epoch variable
bool operator<(const DumpIndex& other) const { return epoch < other.epoch; }
};
class PersistentTmStore : public TmStoreFrontendSimpleIF, public SystemObject { class PersistentTmStore : public TmStoreFrontendSimpleIF, public SystemObject {
public: public:
enum class State { IDLE, DUMPING }; enum class State { IDLE, DUMPING };
@ -96,7 +105,10 @@ class PersistentTmStore : public TmStoreFrontendSimpleIF, public SystemObject {
std::string baseName; std::string baseName;
uint8_t currentSameSecNumber = 0; uint8_t currentSameSecNumber = 0;
std::filesystem::path basePath; std::filesystem::path basePath;
// std::filesystem::path pathStart = basePath / baseName;
uint32_t rolloverDiffSeconds = 0; uint32_t rolloverDiffSeconds = 0;
std::array<uint8_t, 524> filePathBuf{};
size_t basePathSize;
std::array<uint8_t, MAX_FILESIZE> fileBuf{}; std::array<uint8_t, MAX_FILESIZE> fileBuf{};
timeval currentTv; timeval currentTv;
timeval activeFileTv{}; timeval activeFileTv{};
@ -106,8 +118,10 @@ class PersistentTmStore : public TmStoreFrontendSimpleIF, public SystemObject {
uint32_t fromUnixTime = 0; uint32_t fromUnixTime = 0;
uint32_t untilUnixTime = 0; uint32_t untilUnixTime = 0;
uint32_t currentFileUnixStamp = 0; uint32_t currentFileUnixStamp = 0;
std::filesystem::directory_iterator dirIter; std::filesystem::path currentFile;
std::filesystem::directory_entry dirEntry; std::set<DumpIndex> orderedDumpFilestamps{};
std::set<DumpIndex>::iterator dumpIter;
std::optional<uint8_t> currentSameFileIdx = 0;
size_t fileSize = 0; size_t fileSize = 0;
size_t currentSize = 0; size_t currentSize = 0;
}; };
@ -122,10 +136,13 @@ class PersistentTmStore : public TmStoreFrontendSimpleIF, public SystemObject {
[[nodiscard]] MessageQueueId_t getCommandQueue() const override; [[nodiscard]] MessageQueueId_t getCommandQueue() const override;
void calcDiffSeconds(RolloverInterval intervalUnit, uint32_t intervalCount); void calcDiffSeconds(RolloverInterval intervalUnit, uint32_t intervalCount);
ReturnValue_t buildDumpSet(uint32_t fromUnixSeconds, uint32_t upToUnixSeconds);
ReturnValue_t createMostRecentFile(std::optional<uint8_t> suffix); ReturnValue_t createMostRecentFile(std::optional<uint8_t> suffix);
static ReturnValue_t pathToTime(const std::filesystem::path& path, struct tm& time); static ReturnValue_t pathToTime(const std::filesystem::path& path, struct tm& time);
void fileToPackets(const std::filesystem::path& path, uint32_t unixStamp); void fileToPackets(const std::filesystem::path& path, uint32_t unixStamp);
ReturnValue_t loadNextDumpFile(); ReturnValue_t loadNextDumpFile();
ReturnValue_t createFileName(timeval& tv, std::optional<uint8_t> suffix, size_t& fullPathLength);
std::optional<uint8_t> extractSuffix(const std::string& pathStr);
bool updateBaseDir(); bool updateBaseDir();
ReturnValue_t assignAndOrCreateMostRecentFile(); ReturnValue_t assignAndOrCreateMostRecentFile();
}; };

View File

@ -1,5 +1,7 @@
#include "PusTmFunnel.h" #include "PusTmFunnel.h"
#include <limits>
#include "eive/definitions.h" #include "eive/definitions.h"
#include "eive/objects.h" #include "eive/objects.h"
#include "fsfw/ipc/CommandMessage.h" #include "fsfw/ipc/CommandMessage.h"
@ -11,8 +13,8 @@
#include "tmtc/pusIds.h" #include "tmtc/pusIds.h"
PusTmFunnel::PusTmFunnel(TmFunnelBase::FunnelCfg cfg, StorageManagerIF &ramToFileStore, PusTmFunnel::PusTmFunnel(TmFunnelBase::FunnelCfg cfg, StorageManagerIF &ramToFileStore,
TimeReaderIF &timeReader, SdCardMountedIF &sdcMan) TimeReaderIF &timeReader)
: TmFunnelBase(cfg), ramToFileStore(ramToFileStore), timeReader(timeReader), sdcMan(sdcMan) {} : TmFunnelBase(cfg), ramToFileStore(ramToFileStore), timeReader(timeReader) {}
PusTmFunnel::~PusTmFunnel() = default; PusTmFunnel::~PusTmFunnel() = default;
@ -21,6 +23,13 @@ ReturnValue_t PusTmFunnel::performOperation(uint8_t) {
ReturnValue_t result; ReturnValue_t result;
TmTcMessage currentMessage; TmTcMessage currentMessage;
unsigned int count = 0; unsigned int count = 0;
if (saveSequenceCount) {
result = saveSequenceCountToFile();
if (result != returnvalue::OK) {
sif::error << "PusTmFunnel: Storing sequence count to file has failed" << std::endl;
}
saveSequenceCount = false;
}
result = tmQueue->receiveMessage(&currentMessage); result = tmQueue->receiveMessage(&currentMessage);
while (result == returnvalue::OK) { while (result == returnvalue::OK) {
result = handleTmPacket(currentMessage); result = handleTmPacket(currentMessage);
@ -59,7 +68,33 @@ ReturnValue_t PusTmFunnel::handleTmPacket(TmTcMessage &message) {
return result; return result;
} }
packet.setSequenceCount(sourceSequenceCount++); packet.setSequenceCount(sourceSequenceCount++);
// NOTE: This only works because the limit value bit width is below 16 bits!
sourceSequenceCount = sourceSequenceCount % ccsds::LIMIT_SEQUENCE_COUNT; sourceSequenceCount = sourceSequenceCount % ccsds::LIMIT_SEQUENCE_COUNT;
// Message type counter handling.
uint8_t service = packet.getService();
bool insertionFailed = false;
auto mapIter = msgCounterMap.find(service);
if (mapIter == msgCounterMap.end()) {
auto iterPair = msgCounterMap.emplace(service, 0);
if (iterPair.second) {
mapIter = iterPair.first;
} else {
// Should really never never happen but you never know..
insertionFailed = true;
}
}
if (not insertionFailed) {
packet.setMessageCount(mapIter->second);
// Sane overflow handling.
if (mapIter->second == std::numeric_limits<uint16_t>::max()) {
mapIter->second = 0;
} else {
mapIter->second++;
}
}
// Re-calculate CRC after changing the fields. This operation HAS to come last!
packet.updateErrorControl(); packet.updateErrorControl();
// Send to persistent TM store if the packet matches some filter. // Send to persistent TM store if the packet matches some filter.

View File

@ -9,6 +9,8 @@
#include <mission/tmtc/PusTmRouteByFilterHelper.h> #include <mission/tmtc/PusTmRouteByFilterHelper.h>
#include <mission/tmtc/TmFunnelBase.h> #include <mission/tmtc/TmFunnelBase.h>
#include <atomic>
#include <map>
#include <vector> #include <vector>
#include "PersistentTmStore.h" #include "PersistentTmStore.h"
@ -25,7 +27,7 @@
class PusTmFunnel : public TmFunnelBase { class PusTmFunnel : public TmFunnelBase {
public: public:
PusTmFunnel(TmFunnelBase::FunnelCfg cfg, StorageManagerIF &ramToFileStore, PusTmFunnel(TmFunnelBase::FunnelCfg cfg, StorageManagerIF &ramToFileStore,
TimeReaderIF &timeReader, SdCardMountedIF &sdcMan); TimeReaderIF &timeReader);
[[nodiscard]] const char *getName() const override; [[nodiscard]] const char *getName() const override;
~PusTmFunnel() override; ~PusTmFunnel() override;
@ -36,11 +38,10 @@ class PusTmFunnel : public TmFunnelBase {
// Update TV stamp every 5 minutes // Update TV stamp every 5 minutes
static constexpr dur_millis_t TV_UPDATE_INTERVAL_SECS = 60 * 5; static constexpr dur_millis_t TV_UPDATE_INTERVAL_SECS = 60 * 5;
uint16_t sourceSequenceCount = 0; std::map<uint8_t, uint16_t> msgCounterMap;
StorageManagerIF &ramToFileStore; StorageManagerIF &ramToFileStore;
TimeReaderIF &timeReader; TimeReaderIF &timeReader;
bool storesInitialized = false; bool storesInitialized = false;
SdCardMountedIF &sdcMan;
PusTmRouteByFilterHelper persistentTmMap; PusTmRouteByFilterHelper persistentTmMap;
ReturnValue_t handleTmPacket(TmTcMessage &message); ReturnValue_t handleTmPacket(TmTcMessage &message);

View File

@ -2,6 +2,10 @@
#include <fsfw/tmtcservices/TmTcMessage.h> #include <fsfw/tmtcservices/TmTcMessage.h>
#include <atomic>
#include <filesystem>
#include <fstream>
#include "fsfw/ipc/QueueFactory.h" #include "fsfw/ipc/QueueFactory.h"
TmFunnelBase::TmFunnelBase(FunnelCfg cfg) TmFunnelBase::TmFunnelBase(FunnelCfg cfg)
@ -10,7 +14,10 @@ TmFunnelBase::TmFunnelBase(FunnelCfg cfg)
tmStore(cfg.tmStore), tmStore(cfg.tmStore),
ipcStore(cfg.ipcStore), ipcStore(cfg.ipcStore),
tmQueue(QueueFactory::instance()->createMessageQueue(cfg.tmMsgDepth)), tmQueue(QueueFactory::instance()->createMessageQueue(cfg.tmMsgDepth)),
liveDemux(*tmQueue) {} liveDemux(*tmQueue),
sdcMan(cfg.sdcMan),
sequenceCounterFilename(cfg.sequenceCounterFilename),
saveSequenceCount(cfg.saveSequenceCount) {}
ReturnValue_t TmFunnelBase::demultiplexLivePackets(store_address_t origStoreId, ReturnValue_t TmFunnelBase::demultiplexLivePackets(store_address_t origStoreId,
const uint8_t *tmData, size_t tmSize) { const uint8_t *tmData, size_t tmSize) {
@ -27,3 +34,38 @@ void TmFunnelBase::addLiveDestination(const char *name,
const AcceptsTelemetryIF &downlinkDestination, uint8_t vcid) { const AcceptsTelemetryIF &downlinkDestination, uint8_t vcid) {
liveDemux.addDestination(name, downlinkDestination, vcid); liveDemux.addDestination(name, downlinkDestination, vcid);
} }
ReturnValue_t TmFunnelBase::initialize() {
using namespace std::filesystem;
// The filesystem should always be available at the start.. Let's assume it always is, otherwise
// we just live with a regular 0 initialization. It simplifies a lot.
std::error_code e;
path filePath =
path(path(sdcMan.getCurrentMountPrefix()) / path("conf") / path(sequenceCounterFilename));
if (exists(filePath, e)) {
std::ifstream ifile(filePath);
if (ifile.bad()) {
sif::error << "TmFunnelBase::initialize: Faulty file open for sequence counter initialization"
<< std::endl;
return returnvalue::OK;
}
if (not(ifile >> sourceSequenceCount)) {
// Initialize to 0 in any case if reading a number failed.
sourceSequenceCount = 0;
}
}
return returnvalue::OK;
}
ReturnValue_t TmFunnelBase::saveSequenceCountToFile() {
using namespace std::filesystem;
std::error_code e;
path filePath =
path(path(sdcMan.getCurrentMountPrefix()) / path("conf") / path(sequenceCounterFilename));
std::ofstream ofile(filePath);
if (ofile.bad()) {
return returnvalue::FAILED;
}
ofile << sourceSequenceCount << "\n";
return returnvalue::OK;
}

View File

@ -6,25 +6,34 @@
#include <fsfw/tmstorage/TmStoreFrontendSimpleIF.h> #include <fsfw/tmstorage/TmStoreFrontendSimpleIF.h>
#include <fsfw/tmtcservices/AcceptsTelemetryIF.h> #include <fsfw/tmtcservices/AcceptsTelemetryIF.h>
#include <fsfw/tmtcservices/TmTcMessage.h> #include <fsfw/tmtcservices/TmTcMessage.h>
#include <mission/memory/SdCardMountedIF.h>
#include <mission/tmtc/PusLiveDemux.h> #include <mission/tmtc/PusLiveDemux.h>
#include <atomic>
#include <vector> #include <vector>
class TmFunnelBase : public AcceptsTelemetryIF, public SystemObject { class TmFunnelBase : public AcceptsTelemetryIF, public SystemObject {
public: public:
struct FunnelCfg { struct FunnelCfg {
FunnelCfg(object_id_t objId, const char* name, StorageManagerIF& tmStore, FunnelCfg(object_id_t objId, const char* name, StorageManagerIF& tmStore,
StorageManagerIF& ipcStore, uint32_t tmMsgDepth) StorageManagerIF& ipcStore, uint32_t tmMsgDepth, SdCardMountedIF& sdcMan,
const char* sequenceCounterFilename, std::atomic_bool& saveSequenceCount)
: objectId(objId), : objectId(objId),
name(name), name(name),
tmStore(tmStore), tmStore(tmStore),
ipcStore(ipcStore), ipcStore(ipcStore),
tmMsgDepth(tmMsgDepth) {} tmMsgDepth(tmMsgDepth),
sdcMan(sdcMan),
sequenceCounterFilename(sequenceCounterFilename),
saveSequenceCount(saveSequenceCount) {}
object_id_t objectId; object_id_t objectId;
const char* name; const char* name;
StorageManagerIF& tmStore; StorageManagerIF& tmStore;
StorageManagerIF& ipcStore; StorageManagerIF& ipcStore;
uint32_t tmMsgDepth; uint32_t tmMsgDepth;
SdCardMountedIF& sdcMan;
const char* sequenceCounterFilename;
std::atomic_bool& saveSequenceCount;
}; };
explicit TmFunnelBase(FunnelCfg cfg); explicit TmFunnelBase(FunnelCfg cfg);
[[nodiscard]] MessageQueueId_t getReportReceptionQueue(uint8_t virtualChannel) const override; [[nodiscard]] MessageQueueId_t getReportReceptionQueue(uint8_t virtualChannel) const override;
@ -32,6 +41,9 @@ class TmFunnelBase : public AcceptsTelemetryIF, public SystemObject {
uint8_t vcid = 0); uint8_t vcid = 0);
ReturnValue_t demultiplexLivePackets(store_address_t origStoreId, const uint8_t* tmData, ReturnValue_t demultiplexLivePackets(store_address_t origStoreId, const uint8_t* tmData,
size_t tmSize); size_t tmSize);
ReturnValue_t initialize() override;
ReturnValue_t saveSequenceCountToFile();
~TmFunnelBase() override; ~TmFunnelBase() override;
@ -41,6 +53,10 @@ class TmFunnelBase : public AcceptsTelemetryIF, public SystemObject {
StorageManagerIF& ipcStore; StorageManagerIF& ipcStore;
MessageQueueIF* tmQueue = nullptr; MessageQueueIF* tmQueue = nullptr;
PusLiveDemux liveDemux; PusLiveDemux liveDemux;
SdCardMountedIF& sdcMan;
const char* sequenceCounterFilename;
std::atomic_bool& saveSequenceCount;
uint16_t sourceSequenceCount = 0;
}; };
#endif /* MISSION_TMTC_TMFUNNELBASE_H_ */ #endif /* MISSION_TMTC_TMFUNNELBASE_H_ */

2
tmtc

@ -1 +1 @@
Subproject commit ec0ebc365308198046addc94909b1bca8678aa5a Subproject commit 18304c31fa423b1af6ff47764d4be81c7f20c8f2

View File

@ -4,7 +4,7 @@ add_subdirectory(mocks)
target_sources(${UNITTEST_NAME} PRIVATE target_sources(${UNITTEST_NAME} PRIVATE
main.cpp main.cpp
testEnvironment.cpp testEnvironment.cpp
testStampInFilename.cpp testGenericFilesystem.cpp
hdlcEncodingRw.cpp hdlcEncodingRw.cpp
printChar.cpp printChar.cpp
) )

View File

@ -27,7 +27,7 @@ void factory(void* args) {
new HouseKeepingMock(); new HouseKeepingMock();
eventManager = new EventManagerMock(); eventManager = new EventManagerMock();
new HealthTable(objects::HEALTH_TABLE); new HealthTable(objects::HEALTH_TABLE);
new InternalErrorReporter(objects::INTERNAL_ERROR_REPORTER); new InternalErrorReporter(objects::INTERNAL_ERROR_REPORTER, 5, false, 60.0);
new CdsShortTimeStamper(objects::TIME_STAMPER); new CdsShortTimeStamper(objects::TIME_STAMPER);
{ {

View File

@ -0,0 +1,43 @@
#include <catch2/catch_test_macros.hpp>
#include <cinttypes>
#include "fsfw/timemanager/Clock.h"
uint8_t extractSuffix(const std::string& pathStr) {
std::string numberStr;
// Find the position of the dot at the end of the file path
size_t dotPos = pathStr.find_last_of('.');
if (dotPos != std::string::npos && dotPos < pathStr.length() - 1) {
// Extract the substring after the dot
numberStr = pathStr.substr(dotPos + 1);
}
int number = std::stoi(numberStr);
if (number < 0 or number > std::numeric_limits<uint8_t>::max()) {
return 0;
}
return static_cast<uint8_t>(number);
}
TEST_CASE("Stamp in Filename", "[Stamp In Filename]") {
Clock::TimeOfDay_t tod;
std::string baseName = "verif";
std::string pathStr = "verif_2022-05-25T16:55:23Z.bin";
unsigned int underscorePos = pathStr.find_last_of('_');
std::string stampStr = pathStr.substr(underscorePos + 1);
float seconds = 0.0;
char* prefix = nullptr;
int count =
sscanf(stampStr.c_str(),
"%4" SCNu32 "-%2" SCNu32 "-%2" SCNu32 "T%2" SCNu32 ":%2" SCNu32 ":%2" SCNu32 "Z",
&tod.year, &tod.month, &tod.day, &tod.hour, &tod.minute, &tod.second);
static_cast<void>(count);
CHECK(count == 6);
}
TEST_CASE("Suffix Extraction") {
std::string pathStr = "/mnt/sd0/tm/hk/hk-some-stamp.bin.0";
CHECK(extractSuffix(pathStr) == 0);
pathStr = "/mnt/sd0/tm/hk/hk-some-stamp.bin.2";
CHECK(extractSuffix(pathStr) == 2);
}

View File

@ -1,21 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include <cinttypes>
#include "fsfw/timemanager/Clock.h"
TEST_CASE("Stamp in Filename", "[Stamp In Filename]") {
Clock::TimeOfDay_t tod;
std::string baseName = "verif";
std::string pathStr = "verif_2022-05-25T16:55:23Z.bin";
unsigned int underscorePos = pathStr.find_last_of('_');
std::string stampStr = pathStr.substr(underscorePos + 1);
float seconds = 0.0;
char* prefix = nullptr;
int count =
sscanf(stampStr.c_str(),
"%4" SCNu32 "-%2" SCNu32 "-%2" SCNu32 "T%2" SCNu32 ":%2" SCNu32 ":%2" SCNu32 "Z",
&tod.year, &tod.month, &tod.day, &tod.hour, &tod.minute, &tod.second);
static_cast<void>(count);
CHECK(count == 6);
}