Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
598d901
initial commit
hrfarmer Jul 21, 2026
5f38c96
logs
hrfarmer Jul 21, 2026
a3080a9
file downilnk test
hrfarmer Jul 21, 2026
bd45c99
Merge branch 'main' into tlm-archive
hrfarmer Jul 21, 2026
ad90a15
fix packet
hrfarmer Jul 21, 2026
c9d5171
test update
hrfarmer Jul 21, 2026
d7e0a49
higher tlm delay pleaes don't die
hrfarmer Jul 21, 2026
5d66a29
decrease downlink delay during test
hrfarmer Jul 22, 2026
f132ee3
file count and pruning
hrfarmer Jul 24, 2026
c1a8448
Merge branch 'main' into tlm-archive
hrfarmer Jul 24, 2026
3746d9d
add configurable timeout to send and assert
hrfarmer Jul 24, 2026
6f262e6
Merge branch 'tlm-archive' of https://github.com/open-source-space-fo…
hrfarmer Jul 24, 2026
a80e238
nuke everything
hrfarmer Jul 25, 2026
6b82a52
coderabbit recommendations
hrfarmer Jul 25, 2026
139991e
Merge branch 'main' into tlm-archive
hrfarmer Jul 26, 2026
d8b3c1a
add archive write events
hrfarmer Jul 26, 2026
6218823
stop attempting directory creation once successful
hrfarmer Jul 26, 2026
4e6d07e
add filesystem failure limit
hrfarmer Jul 26, 2026
b4e359c
add max file size limit to component
hrfarmer Jul 26, 2026
fc06f35
ensure state changes are scoped with mutex
hrfarmer Jul 26, 2026
44a37e4
only send one event when starting telemetry archival
hrfarmer Jul 27, 2026
c5b3349
updates
hrfarmer Jul 27, 2026
46a5269
check file size only once
hrfarmer Jul 27, 2026
46d00ca
update events
hrfarmer Jul 27, 2026
44be94f
update sdd
hrfarmer Jul 27, 2026
29ec02d
Merge branch 'main' into tlm-archive
hrfarmer Jul 27, 2026
aff88a5
ensure file is created before checking size
hrfarmer Jul 27, 2026
2571b1a
get rid of size remaining check
hrfarmer Jul 28, 2026
2e04e37
use atomics for telemetry archive state
hrfarmer Jul 30, 2026
1e4dfe0
switch to using a csv format
hrfarmer Aug 2, 2026
b2ef327
bump max size to 25000
hrfarmer Aug 2, 2026
3a72539
delete random test
hrfarmer Aug 2, 2026
4100a6c
queue and filter updates
hrfarmer Aug 2, 2026
2eb632b
increase size
hrfarmer Aug 2, 2026
8165f28
remove queue empty event
hrfarmer Aug 2, 2026
eabfedf
add parameters for file size limit and error limit
hrfarmer Aug 2, 2026
7739320
ensure file limit event is fired and queue is cleared on fileSize limit
hrfarmer Aug 2, 2026
7e79bdd
Merge branch 'main' into tlm-archive
hrfarmer Aug 3, 2026
c1aecfa
bump command table size
hrfarmer Aug 3, 2026
06bba3b
wait 30 seconds to start writing to disk
hrfarmer Aug 3, 2026
5dbd15e
i am a stupid man
hrfarmer Aug 3, 2026
4ccceba
test codex recommendation for radio test setup
hrfarmer Aug 3, 2026
f2a1a9d
Revert "test codex recommendation for radio test setup"
hrfarmer Aug 3, 2026
21309ec
test i guess
hrfarmer Aug 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ void AntennaDeployer ::schedIn_handler(FwIndexType portNum, U32 context) {
}
}

bool AntennaDeployer ::deploymentStateGet_handler(FwIndexType portNum) {
(void)portNum;
return this->readDeploymentState();
}
Comment on lines +89 to +92

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep failed deployments marked as not deployed.

finishDeployment() writes true for both DEPLOY_RESULT_SUCCESS and DEPLOY_RESULT_FAILED (Lines 226-231). After retries are exhausted, this handler reports deployed, so TlmArchive stops archiving and future DEPLOY commands are rejected.

Proposed fix
-    if (result == Components::DeployResult::DEPLOY_RESULT_SUCCESS ||
-        result == Components::DeployResult::DEPLOY_RESULT_FAILED) {
+    if (result == Components::DeployResult::DEPLOY_RESULT_SUCCESS) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@PROVESFlightControllerReference/Components/AntennaDeployer/AntennaDeployer.cpp`
around lines 89 - 92, Update finishDeployment() so the deployment state is
written true only for DEPLOY_RESULT_SUCCESS; preserve false for
DEPLOY_RESULT_FAILED and exhausted retries. Ensure deploymentStateGet_handler()
continues reporting readDeploymentState() so failed deployments remain eligible
for archiving and future DEPLOY commands.


// ----------------------------------------------------------------------
// Command handler implementations
// ----------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ module Components {
DEPLOY_RESULT_ABORT @< Deployment aborted via command
DEPLOY_RESULT_FAILED @< Deployment failed after exhausting retries
}

@ Port for querying the persistent antenna deployment state
port GetDeploymentState -> bool
}

module Components {
Expand Down Expand Up @@ -79,6 +82,9 @@ module Components {
@ Port receiving calls from the rate group
sync input port schedIn: Svc.Sched

@ Port returning whether antenna deployment has completed
sync input port deploymentStateGet: Components.GetDeploymentState

@ Port signaling the burnwire component to start heating
output port burnStart: Fw.Signal
Comment thread
ineskhou marked this conversation as resolved.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class AntennaDeployer final : public AntennaDeployerComponentBase {
// Handler implementations
// ----------------------------------------------------------------------
void schedIn_handler(FwIndexType portNum, U32 context) override;
bool deploymentStateGet_handler(FwIndexType portNum) override;

// ----------------------------------------------------------------------
// Command handlers
Expand Down
1 change: 1 addition & 0 deletions PROVESFlightControllerReference/Components/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/StartupManager/")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/TcSecurityDeframer/")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/ThermalManager/")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Watchdog")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/TlmArchive/")
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
register_fprime_library(
AUTOCODER_INPUTS
"${CMAKE_CURRENT_LIST_DIR}/TlmArchive.fpp"
SOURCES
"${CMAKE_CURRENT_LIST_DIR}/TlmArchive.cpp"
)
226 changes: 226 additions & 0 deletions PROVESFlightControllerReference/Components/TlmArchive/TlmArchive.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// ======================================================================
// \title TlmArchive.cpp
// \author aychar
// \brief cpp file for TlmArchive component implementation class
// ======================================================================

#include "PROVESFlightControllerReference/Components/TlmArchive/TlmArchive.hpp"

#include <cstdio>
#include <cstring>

#include "Fw/Com/ComPacket.hpp"
#include "Os/File.hpp"
#include "Os/FileSystem.hpp"
#include "Os/Models/FileStatusEnumAc.hpp"

namespace Components {

namespace {

constexpr const char* TLM_DIRECTORY = "//tlm";
constexpr const char* PRE_DEPLOYMENT_TLM_PATH = "//tlm/pre_deployment.csv";
constexpr char CSV_HEADER[] = "format_version,packet_size_bytes,packet_hex\n";
constexpr char HEX_DIGITS[] = "0123456789ABCDEF";
constexpr FwSizeType CSV_RECORD_BUFFER_SIZE = (FW_COM_BUFFER_MAX_SIZE * 2) + sizeof(CSV_HEADER) + 32;

// Only packetized telemetry IDs in this list are stored in the archive.
constexpr FwTlmPacketizeIdType STORED_PACKET_IDS[] = {
1, // Beacon
7, // Imu
};

bool shouldStorePacket(const Fw::ComBuffer& data) {
Fw::ComBuffer packet = data;
FwPacketDescriptorType descriptor = 0;
FwTlmPacketizeIdType packetId = 0;

if ((packet.deserializeTo(descriptor) != Fw::FW_SERIALIZE_OK) ||
(descriptor != static_cast<FwPacketDescriptorType>(Fw::ComPacketType::FW_PACKET_PACKETIZED_TLM)) ||
(packet.deserializeTo(packetId) != Fw::FW_SERIALIZE_OK)) {
return false;
}

for (const FwTlmPacketizeIdType storedPacketId : STORED_PACKET_IDS) {
if (packetId == storedPacketId) {
return true;
}
}
return false;
}

} // namespace

TlmArchive ::TlmArchive(const char* const compName) : TlmArchiveComponentBase(compName) {}

TlmArchive ::~TlmArchive() {}

void TlmArchive::clearPacketQueue() {
Os::ScopeLock lock(this->m_queueMutex);
this->m_queueHead = 0;
this->m_queueTail = 0;
this->m_queueSize = 0;
}

void TlmArchive::comIn_handler(FwIndexType portNum, Fw::ComBuffer& data, U32 context) {
(void)portNum;
(void)context;

Fw::ParamValid maxFailuresValid;
const U32 maxFailures = this->paramGet_MAX_FAILURES(maxFailuresValid);
FW_ASSERT(maxFailuresValid == Fw::ParamValid::VALID || maxFailuresValid == Fw::ParamValid::DEFAULT);

Fw::ParamValid maxFileSizeValid;
const U32 maxFileSize = this->paramGet_MAX_FILE_SIZE(maxFileSizeValid);
FW_ASSERT(maxFileSizeValid == Fw::ParamValid::VALID || maxFileSizeValid == Fw::ParamValid::DEFAULT);

if (this->m_failures.load() >= maxFailures) {
this->log_WARNING_HI_FailureLimitReached(maxFailures);
return;
} else if (this->m_fileSize.load() >= maxFileSize) {
this->clearPacketQueue();
this->log_WARNING_LO_SizeLimitReached(maxFileSize);
return;
} else if (this->m_antennasDeployed.load()) {
this->log_WARNING_LO_AntennasDeployed();
return;
}

if (!shouldStorePacket(data)) {
return;
}

bool queueFull = false;
{
Os::ScopeLock lock(this->m_queueMutex);
Comment thread
ineskhou marked this conversation as resolved.
if (this->m_queueSize >= PACKET_QUEUE_CAPACITY) {
queueFull = true;
} else {
this->m_packetQueue[this->m_queueTail] = data;
this->m_queueTail = (this->m_queueTail + 1) % PACKET_QUEUE_CAPACITY;
this->m_queueSize++;
}
}

if (queueFull) {
this->log_WARNING_HI_QueueFull(PACKET_QUEUE_CAPACITY);
} else {
this->log_WARNING_HI_QueueFull_ThrottleClear();
}
}

void TlmArchive::run_handler(FwIndexType portNum, U32 context) {
(void)portNum;
(void)context;

// Don't write for the first 30s of boot to attempt to mitigate
// any conflicts with other filesystem writes/actions
if (this->m_ticks <= 30) {
this->m_ticks++;
return;
}

Fw::ComBuffer packet;
{
Os::ScopeLock lock(this->m_queueMutex);
if (this->m_queueSize == 0) {
return;
}
packet = this->m_packetQueue[this->m_queueHead];
this->m_queueHead = (this->m_queueHead + 1) % PACKET_QUEUE_CAPACITY;
this->m_queueSize--;
}

if (!this->m_antennasDeployed.load()) {
this->m_antennasDeployed.store(this->deploymentStateGet_out(0));
}

if (this->m_antennasDeployed.load()) {
return;
}

if (!this->m_directoryInitialized) {
if (Os::FileSystem::createDirectory(TLM_DIRECTORY, false) != Os::FileSystem::OP_OK) {
this->log_WARNING_HI_FileError(Fw::LogStringArg("create_directory"));
this->m_failures.fetch_add(1);
return;
}
if (Os::FileSystem::touch(PRE_DEPLOYMENT_TLM_PATH) != Os::FileSystem::OP_OK) {
this->log_WARNING_HI_FileError(Fw::LogStringArg("create_file"));
this->m_failures.fetch_add(1);
return;
}
this->m_directoryInitialized = true;
}

const FwSizeType packetSize = packet.getSize();
FwSizeType currentSize = this->m_fileSize.load();
Fw::ParamValid maxFileSizeValid;
const U32 maxFileSize = this->paramGet_MAX_FILE_SIZE(maxFileSizeValid);
FW_ASSERT(maxFileSizeValid == Fw::ParamValid::VALID || maxFileSizeValid == Fw::ParamValid::DEFAULT);
if (!this->m_fileSizeInitialized) {
const Os::FileSystem::Status sizeStatus = Os::FileSystem::getFileSize(PRE_DEPLOYMENT_TLM_PATH, currentSize);
if (sizeStatus != Os::FileSystem::OP_OK) {
this->log_WARNING_HI_FileError(Fw::LogStringArg("get_file_size"));
this->m_failures.fetch_add(1);
return;
}
this->m_fileSize.store(static_cast<U32>(currentSize));
this->m_fileSizeInitialized = true;
}

char csvRecord[CSV_RECORD_BUFFER_SIZE];
FwSizeType recordSize = 0;
if (currentSize == 0) {
static_assert(sizeof(CSV_HEADER) > 1, "CSV header must not be empty");
(void)std::memcpy(csvRecord, CSV_HEADER, sizeof(CSV_HEADER) - 1);
recordSize = sizeof(CSV_HEADER) - 1;
}

const int prefixSize = std::snprintf(&csvRecord[recordSize], sizeof(csvRecord) - recordSize, "1,%llu,",
static_cast<unsigned long long>(packetSize));
if ((prefixSize < 0) || (static_cast<FwSizeType>(prefixSize) >= (sizeof(csvRecord) - recordSize))) {
this->log_WARNING_HI_FileError(Fw::LogStringArg("format_record"));
this->m_failures.fetch_add(1);
return;
}
recordSize += static_cast<FwSizeType>(prefixSize);

const U8* const packetBytes = packet.getBuffAddr();
for (FwSizeType index = 0; index < packetSize; index++) {
csvRecord[recordSize++] = HEX_DIGITS[(packetBytes[index] >> 4) & 0x0F];
csvRecord[recordSize++] = HEX_DIGITS[packetBytes[index] & 0x0F];
}
csvRecord[recordSize++] = '\n';

if (currentSize > maxFileSize) {
this->clearPacketQueue();
this->log_WARNING_LO_SizeLimitReached(maxFileSize);
return;
}

this->log_ACTIVITY_LO_WriteStart();
Os::File file;
const Os::File::Status openStatus = file.open(PRE_DEPLOYMENT_TLM_PATH, Os::File::OPEN_APPEND);
if (openStatus != Os::File::OP_OK) {
this->log_WARNING_HI_FileError(Fw::LogStringArg("open_append"));
this->m_failures.fetch_add(1);
return;
}

FwSizeType writtenSize = recordSize;
const Os::File::Status writeStatus =
file.write(reinterpret_cast<const U8*>(csvRecord), writtenSize, Os::File::WaitType::WAIT);
if ((writeStatus != Os::File::OP_OK) || (writtenSize != recordSize)) {
this->log_WARNING_HI_WriteError(Os::FileStatus(static_cast<Os::FileStatus::T>(writeStatus)), recordSize,
writtenSize);
this->m_failures.fetch_add(1);
file.close();
return;
}
file.close();

this->m_fileSize.fetch_add(static_cast<U32>(writtenSize));
}

} // namespace Components
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
module Components {
@ Stores telemetry generated before antenna deployment
passive component TlmArchive {
@ Telemetry packet to enqueue for deferred archival
sync input port comIn: Fw.Com

@ Drains one queued telemetry packet and performs filesystem work
sync input port run: Svc.Sched

@ Port for checking whether antenna deployment has completed
output port deploymentStateGet: Components.GetDeploymentState

@ Maximum archive size in bytes
param MAX_FILE_SIZE: U32 default 50000 id 0

@ Number of counted filesystem failures that disables archiving
param MAX_FAILURES: U32 default 3 id 1

@ Report when file write has started
event WriteStart severity activity low format "Beginning telemetry archival to pre_deployment.csv" throttle 1

@ Reports that a packet could not be enqueued because the queue is full
event QueueFull(
capacity: FwSizeType @< Maximum number of queued telemetry packets
) severity warning high format "Telemetry archive packet queue is full at {} packets" throttle 1

@ Reports archive initialization, size, and open failures
event FileError(
operation: string @< Filesystem operation that failed
) severity warning high \
format "Pre-deployment telemetry archive operation failed: {}"

@ Reports failed and incomplete archive writes
event WriteError(
status: Os.FileStatus @< File write status
requested: FwSizeType @< Requested byte count
written: FwSizeType @< Reported byte count
) severity warning high \
format "Pre-deployment telemetry archive write failed: status {}, requested {}, wrote {}"

@ Reports when telemetry archiving is disabled due to hitting the failure limit
event FailureLimitReached(
count: U32
) severity warning high format "{} filesystem failures counted; disabling further telemetry writes." throttle 1

@ Reports when telemetry archiving is disabled due to antennas being deployed
event AntennasDeployed() severity warning low format "Antennas deployed; disabling further telemetry writes." throttle 1

@ Reports when telemetry archiving is disabled due to pre_deployment.csv hitting the size limit
event SizeLimitReached(
maxSize: U32
) severity warning low format "pre_deployment.csv file size limit of {}b reached; disabling further telemetry writes." throttle 1

@ Port for requesting the current time
time get port timeCaller

@ Port for sending command registrations
command reg port cmdRegOut

@ Port for receiving commands
command recv port cmdIn

@ Port for sending command responses
command resp port cmdResponseOut

@ Port for sending textual representation of events
text event port logTextOut

@ Port for sending events to downlink
event port logOut

@ Port for getting parameter values
param get port prmGetOut

@ Port for setting parameter values
param set port prmSetOut
}
}
Loading
Loading