diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b702cbf..4ea572c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,6 @@ will consitute of a breaking change warranting a new major release: ## Changed -- Internal error reporter set is now enabled by default and generated every 120 seconds. - 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 @@ -34,6 +33,12 @@ will consitute of a breaking change warranting a new major release: 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 diff --git a/bsp_q7s/core/CoreController.cpp b/bsp_q7s/core/CoreController.cpp index 9e285c4f..4387e788 100644 --- a/bsp_q7s/core/CoreController.cpp +++ b/bsp_q7s/core/CoreController.cpp @@ -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, bool &protOpPerformed) { + // Store both sequence counters persistently. + core::SAVE_CFDP_SEQUENCE_COUNT = true; + core::SAVE_PUS_SEQUENCE_COUNT = true; + sdcMan->setBlocking(true); sdcMan->markUnusable(); // Wait two seconds to ensure no one uses the SD cards diff --git a/common/config/eive/definitions.h b/common/config/eive/definitions.h index 2083962a..b369f512 100644 --- a/common/config/eive/definitions.h +++ b/common/config/eive/definitions.h @@ -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 STRIPPED_OBSW_BINARY_FILE_NAME[] = "eive-obsw-stripped"; 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_VERSION_FILE_PATH[] = "/usr/share/eive-obsw/obsw_version.txt"; diff --git a/fsfw b/fsfw index 6aff3250..8da89eba 160000 --- a/fsfw +++ b/fsfw @@ -1 +1 @@ -Subproject commit 6aff3250c29f5243eb4a6111ba0a5c0cc1a3033c +Subproject commit 8da89eba80f73cb05e5c38fc012456f1d9569af5 diff --git a/mission/genericFactory.cpp b/mission/genericFactory.cpp index 5edee640..0466a980 100644 --- a/mission/genericFactory.cpp +++ b/mission/genericFactory.cpp @@ -91,6 +91,8 @@ EiveFaultHandler EIVE_FAULT_HANDLER; } // namespace cfdp 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, CfdpTmFunnel** cfdpFunnel, SdCardMountedIF& sdcMan, @@ -155,9 +157,11 @@ void ObjectFactory::produceGenericObjects(HealthTableIF** healthTable_, PusTmFun new PusDistributor(config::EIVE_PUS_APID, objects::PUS_PACKET_DISTRIBUTOR, ccsdsDistrib); 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. - *pusFunnel = new PusTmFunnel(pusFunnelCfg, *ramToFileStore, *timeStamper, sdcMan); + *pusFunnel = new PusTmFunnel(pusFunnelCfg, *ramToFileStore, *timeStamper); // MISC store and PUS funnel to MISC store routing { @@ -216,7 +220,9 @@ void ObjectFactory::produceGenericObjects(HealthTableIF** healthTable_, PusTmFun stores.cfdpStore->getReportReceptionQueue(0)); } 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), *ramToFileStore, config::EIVE_CFDP_APID); diff --git a/mission/sysDefs.h b/mission/sysDefs.h index fe572144..9750f3fd 100644 --- a/mission/sysDefs.h +++ b/mission/sysDefs.h @@ -34,6 +34,9 @@ enum Copy : int { COPY_0, COPY_1, NO_COPY, SELF_COPY, ALL_COPY }; 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 // is running. enum SystemctlCmd : uint8_t { START = 0, STOP = 1, RESTART = 2, NUM_CMDS = 3 }; diff --git a/mission/tmtc/CfdpTmFunnel.cpp b/mission/tmtc/CfdpTmFunnel.cpp index 46915b82..93c294a4 100644 --- a/mission/tmtc/CfdpTmFunnel.cpp +++ b/mission/tmtc/CfdpTmFunnel.cpp @@ -15,8 +15,16 @@ const char* CfdpTmFunnel::getName() const { return "CFDP TM Funnel"; } ReturnValue_t CfdpTmFunnel::performOperation(uint8_t) { TmTcMessage currentMessage; + ReturnValue_t status; 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) { status = handlePacket(currentMessage); if (status != returnvalue::OK) { diff --git a/mission/tmtc/CfdpTmFunnel.h b/mission/tmtc/CfdpTmFunnel.h index a4d1bc70..7b9efd34 100644 --- a/mission/tmtc/CfdpTmFunnel.h +++ b/mission/tmtc/CfdpTmFunnel.h @@ -3,6 +3,7 @@ #include +#include #include #include "fsfw/objectmanager/SystemObject.h" @@ -23,7 +24,6 @@ class CfdpTmFunnel : public TmFunnelBase { MessageQueueId_t fileStoreDest; StorageManagerIF& ramToFileStore; - uint16_t sourceSequenceCount = 0; uint16_t cfdpInCcsdsApid; }; #endif // FSFW_EXAMPLE_COMMON_CFDPTMFUNNEL_H diff --git a/mission/tmtc/PusTmFunnel.cpp b/mission/tmtc/PusTmFunnel.cpp index dbc8f1c4..87648e55 100644 --- a/mission/tmtc/PusTmFunnel.cpp +++ b/mission/tmtc/PusTmFunnel.cpp @@ -1,5 +1,7 @@ #include "PusTmFunnel.h" +#include + #include "eive/definitions.h" #include "eive/objects.h" #include "fsfw/ipc/CommandMessage.h" @@ -11,8 +13,8 @@ #include "tmtc/pusIds.h" PusTmFunnel::PusTmFunnel(TmFunnelBase::FunnelCfg cfg, StorageManagerIF &ramToFileStore, - TimeReaderIF &timeReader, SdCardMountedIF &sdcMan) - : TmFunnelBase(cfg), ramToFileStore(ramToFileStore), timeReader(timeReader), sdcMan(sdcMan) {} + TimeReaderIF &timeReader) + : TmFunnelBase(cfg), ramToFileStore(ramToFileStore), timeReader(timeReader) {} PusTmFunnel::~PusTmFunnel() = default; @@ -21,6 +23,13 @@ ReturnValue_t PusTmFunnel::performOperation(uint8_t) { ReturnValue_t result; TmTcMessage currentMessage; 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); while (result == returnvalue::OK) { result = handleTmPacket(currentMessage); @@ -59,7 +68,33 @@ ReturnValue_t PusTmFunnel::handleTmPacket(TmTcMessage &message) { return result; } packet.setSequenceCount(sourceSequenceCount++); + // NOTE: This only works because the limit value bit width is below 16 bits! 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::max()) { + mapIter->second = 0; + } else { + mapIter->second++; + } + } + + // Re-calculate CRC after changing the fields. This operation HAS to come last! packet.updateErrorControl(); // Send to persistent TM store if the packet matches some filter. diff --git a/mission/tmtc/PusTmFunnel.h b/mission/tmtc/PusTmFunnel.h index ba6e8d3e..3696fdcc 100644 --- a/mission/tmtc/PusTmFunnel.h +++ b/mission/tmtc/PusTmFunnel.h @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include "PersistentTmStore.h" @@ -25,7 +27,7 @@ class PusTmFunnel : public TmFunnelBase { public: PusTmFunnel(TmFunnelBase::FunnelCfg cfg, StorageManagerIF &ramToFileStore, - TimeReaderIF &timeReader, SdCardMountedIF &sdcMan); + TimeReaderIF &timeReader); [[nodiscard]] const char *getName() const override; ~PusTmFunnel() override; @@ -36,11 +38,10 @@ class PusTmFunnel : public TmFunnelBase { // Update TV stamp every 5 minutes static constexpr dur_millis_t TV_UPDATE_INTERVAL_SECS = 60 * 5; - uint16_t sourceSequenceCount = 0; + std::map msgCounterMap; StorageManagerIF &ramToFileStore; TimeReaderIF &timeReader; bool storesInitialized = false; - SdCardMountedIF &sdcMan; PusTmRouteByFilterHelper persistentTmMap; ReturnValue_t handleTmPacket(TmTcMessage &message); diff --git a/mission/tmtc/TmFunnelBase.cpp b/mission/tmtc/TmFunnelBase.cpp index eb480b03..fc2e4b76 100644 --- a/mission/tmtc/TmFunnelBase.cpp +++ b/mission/tmtc/TmFunnelBase.cpp @@ -2,6 +2,10 @@ #include +#include +#include +#include + #include "fsfw/ipc/QueueFactory.h" TmFunnelBase::TmFunnelBase(FunnelCfg cfg) @@ -10,7 +14,10 @@ TmFunnelBase::TmFunnelBase(FunnelCfg cfg) tmStore(cfg.tmStore), ipcStore(cfg.ipcStore), 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, const uint8_t *tmData, size_t tmSize) { @@ -27,3 +34,38 @@ void TmFunnelBase::addLiveDestination(const char *name, const AcceptsTelemetryIF &downlinkDestination, uint8_t 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; +} diff --git a/mission/tmtc/TmFunnelBase.h b/mission/tmtc/TmFunnelBase.h index 51d16626..72d91103 100644 --- a/mission/tmtc/TmFunnelBase.h +++ b/mission/tmtc/TmFunnelBase.h @@ -6,25 +6,34 @@ #include #include #include +#include #include +#include #include class TmFunnelBase : public AcceptsTelemetryIF, public SystemObject { public: struct FunnelCfg { 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), name(name), tmStore(tmStore), ipcStore(ipcStore), - tmMsgDepth(tmMsgDepth) {} + tmMsgDepth(tmMsgDepth), + sdcMan(sdcMan), + sequenceCounterFilename(sequenceCounterFilename), + saveSequenceCount(saveSequenceCount) {} object_id_t objectId; const char* name; StorageManagerIF& tmStore; StorageManagerIF& ipcStore; uint32_t tmMsgDepth; + SdCardMountedIF& sdcMan; + const char* sequenceCounterFilename; + std::atomic_bool& saveSequenceCount; }; explicit TmFunnelBase(FunnelCfg cfg); [[nodiscard]] MessageQueueId_t getReportReceptionQueue(uint8_t virtualChannel) const override; @@ -32,6 +41,9 @@ class TmFunnelBase : public AcceptsTelemetryIF, public SystemObject { uint8_t vcid = 0); ReturnValue_t demultiplexLivePackets(store_address_t origStoreId, const uint8_t* tmData, size_t tmSize); + ReturnValue_t initialize() override; + + ReturnValue_t saveSequenceCountToFile(); ~TmFunnelBase() override; @@ -41,6 +53,10 @@ class TmFunnelBase : public AcceptsTelemetryIF, public SystemObject { StorageManagerIF& ipcStore; MessageQueueIF* tmQueue = nullptr; PusLiveDemux liveDemux; + SdCardMountedIF& sdcMan; + const char* sequenceCounterFilename; + std::atomic_bool& saveSequenceCount; + uint16_t sourceSequenceCount = 0; }; #endif /* MISSION_TMTC_TMFUNNELBASE_H_ */ diff --git a/tmtc b/tmtc index ec0ebc36..8caa408c 160000 --- a/tmtc +++ b/tmtc @@ -1 +1 @@ -Subproject commit ec0ebc365308198046addc94909b1bca8678aa5a +Subproject commit 8caa408cbdf7eebdd441cff580063283ab81ffe1