Merge branch 'main' into main-v6
All checks were successful
EIVE/eive-obsw/pipeline/head This commit looks good
All checks were successful
EIVE/eive-obsw/pipeline/head This commit looks good
This commit is contained in:
commit
d8efd05a88
28
CHANGELOG.md
28
CHANGELOG.md
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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";
|
||||||
|
@ -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;
|
||||||
|
@ -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
2
fsfw
@ -1 +1 @@
|
|||||||
Subproject commit 0f76cdb3ba54f5e90a8eee4316c49cf0f581f996
|
Subproject commit 8da89eba80f73cb05e5c38fc012456f1d9569af5
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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 };
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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(¤tMessage);
|
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(¤tMessage);
|
||||||
while (status == returnvalue::OK) {
|
while (status == returnvalue::OK) {
|
||||||
status = handlePacket(currentMessage);
|
status = handlePacket(currentMessage);
|
||||||
if (status != returnvalue::OK) {
|
if (status != returnvalue::OK) {
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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();
|
||||||
};
|
};
|
||||||
|
@ -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(¤tMessage);
|
result = tmQueue->receiveMessage(¤tMessage);
|
||||||
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.
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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
2
tmtc
@ -1 +1 @@
|
|||||||
Subproject commit ec0ebc365308198046addc94909b1bca8678aa5a
|
Subproject commit 18304c31fa423b1af6ff47764d4be81c7f20c8f2
|
@ -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
|
||||||
)
|
)
|
@ -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);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
43
unittest/testGenericFilesystem.cpp
Normal file
43
unittest/testGenericFilesystem.cpp
Normal 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);
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user