Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ permissions:
contents: read

jobs:
python-tests:
name: "Python Tool Tests"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: "Install fprime-cfs package"
run: pip install "./python[test]"
- name: "Run pytest"
run: pytest python/tests

unit-tests:
name: "Unit Tests"
runs-on: ubuntu-22.04
Expand Down
5 changes: 4 additions & 1 deletion FPrimeCfs/CfsBridge/CfsBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "FPrimeCfs/CfsBridge/CfsBridge.hpp"
#include "Fw/Logger/Logger.hpp"
#include "config/FppConstantsAc.hpp"
#include <cstring>
#include <limits>

Expand Down Expand Up @@ -234,7 +235,9 @@ CFE_Status_t CfsBridge ::transmitWrappedCommand(const U8* packet, const FwSizeTy
wrapped[CFS_BRIDGE_SPACE_PACKET_LENGTH_OFFSET] = static_cast<U8>((lengthToken >> 8) & 0xFF);
wrapped[CFS_BRIDGE_SPACE_PACKET_LENGTH_OFFSET + 1] = static_cast<U8>(lengthToken & 0xFF);
// cFS command secondary header: function code, then checksum (computed below over the whole packet)
wrapped[CFS_BRIDGE_SPACE_PACKET_HEADER_SIZE] = CFS_BRIDGE_FPRIME_COMMAND_FUNCTION_CODE;
static_assert(ComCfg::FprimeCommandFunctionCode >= 0 && ComCfg::FprimeCommandFunctionCode <= 0x7F,
"ComCfg.FprimeCommandFunctionCode must fit in the 7-bit cFS command function code field");
wrapped[CFS_BRIDGE_SPACE_PACKET_HEADER_SIZE] = static_cast<U8>(ComCfg::FprimeCommandFunctionCode);
wrapped[CFS_BRIDGE_SPACE_PACKET_HEADER_SIZE + 1] = 0;
(void)std::memcpy(&wrapped[CFS_BRIDGE_SPACE_PACKET_HEADER_SIZE + CFS_BRIDGE_CMD_SEC_HDR_SIZE],
&packet[CFS_BRIDGE_SPACE_PACKET_HEADER_SIZE], size - CFS_BRIDGE_SPACE_PACKET_HEADER_SIZE);
Expand Down
4 changes: 1 addition & 3 deletions FPrimeCfs/CfsBridge/CfsBridge.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ constexpr U32 CFS_BRIDGE_SPACE_PACKET_SEC_HDR_MASK = 0x0800;
constexpr FwSizeType CFS_BRIDGE_SPACE_PACKET_LENGTH_OFFSET = 4;
//! Size in bytes of the cFS command secondary header: {U8 FunctionCode, U8 Checksum}
constexpr FwSizeType CFS_BRIDGE_CMD_SEC_HDR_SIZE = 2;
//! Function code carried by F Prime passthrough commands wrapped as cFS command packets
constexpr U8 CFS_BRIDGE_FPRIME_COMMAND_FUNCTION_CODE = 0;
//! Maximum total size in bytes of a wrapped cFS command packet (primary header + secondary header + payload).
//! Sized to bound the component's internal wrap storage: large enough for the largest F Prime uplink command
//! packet, and well under the software bus limit CFE_MISSION_SB_MAX_SB_MSG_SIZE (default 32768).
Expand Down Expand Up @@ -73,7 +71,7 @@ class CfsBridge final : public CfsBridgeComponentBase
//! message is sent per received signal. When `wrapFprimeCommands` is true, F Prime command space packets received
//! on `dataIn` (packet type command, no secondary header) are transmitted as valid cFS command packets: the
//! secondary header flag is set, and a cFS command secondary header (function code
//! `CFS_BRIDGE_FPRIME_COMMAND_FUNCTION_CODE` and a valid cFS XOR checksum) is inserted ahead of the payload.
//! `ComCfg.FprimeCommandFunctionCode` and a valid cFS XOR checksum) is inserted ahead of the payload.
CFE_Status_t configure(const FwSizeType pipeDepth,
const char* pipeName = "CFS_BRIDGE_PIPE",
bool paused = false,
Expand Down
5 changes: 3 additions & 2 deletions FPrimeCfs/CfsBridge/docs/sdd.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ match the downstream pipeline (e.g. a deframer/router stack).
| FPRIMECFS-CFSBRIDGE-009 | `CfsBridge` shall return ownership of every buffer received on `dataIn` via `dataReturnOut` and shall emit a `comStatusOut` SUCCESS after every transmission attempt, regardless of outcome | Unit test |
| FPRIMECFS-CFSBRIDGE-010 | `CfsBridge` shall subscribe to native cFS messages when `subscribeCfs()` is called, forming the message ID with the secondary header flag set and the packet type bit set for `CfsMessageType::COMMAND` and clear for `CfsMessageType::TELEMETRY` | Unit test |
| FPRIMECFS-CFSBRIDGE-011 | `CfsBridge` shall reject a `configure()` pipe depth exceeding the cFE `uint16` pipe depth range with `CFE_SB_BAD_ARGUMENT` rather than silently truncating | Unit test |
| FPRIMECFS-CFSBRIDGE-012 | When configured with F Prime command wrapping enabled, `CfsBridge` shall transmit each F Prime command space packet (packet type command, no secondary header) as a valid cFS command packet: secondary header flag set, length field adjusted, and a 2-byte cFS command secondary header (function code `CFS_BRIDGE_FPRIME_COMMAND_FUNCTION_CODE` and a valid cFS XOR checksum) inserted between the primary header and the payload; telemetry packets and packets already carrying a secondary header shall be transmitted unmodified, and packets too large to wrap shall be dropped with a logged error | Unit test |
| FPRIMECFS-CFSBRIDGE-012 | When configured with F Prime command wrapping enabled, `CfsBridge` shall transmit each F Prime command space packet (packet type command, no secondary header) as a valid cFS command packet: secondary header flag set, length field adjusted, and a 2-byte cFS command secondary header (function code `ComCfg.FprimeCommandFunctionCode` and a valid cFS XOR checksum) inserted between the primary header and the payload; telemetry packets and packets already carrying a secondary header shall be transmitted unmodified, and packets too large to wrap shall be dropped with a logged error | Unit test |
| FPRIMECFS-CFSBRIDGE-013 | `CfsBridge` shall perform one `process()` call (drain the message queue and poll the software bus pipe once) for each invocation of `schedIn`, allowing rate-group driven operation | Unit test |
| FPRIMECFS-CFSBRIDGE-014 | When `bufferAllocate` is connected, `CfsBridge` shall copy each received software bus message into an allocated buffer before emitting it on `dataOut`, shall deallocate the buffer when returned on `dataReturnIn`, and shall drop the message with a logged error when allocation fails | Unit test |

Expand All @@ -95,7 +95,7 @@ cFS command packet before transmission: the packet is copied into internal stora
header flag is set in the primary header, the 16-bit length field is increased by two, and a 2-byte
cFS command secondary header `{U8 FunctionCode, U8 Checksum}` is inserted between the primary header
and the F Prime command payload. The function code is the fixed
`CFS_BRIDGE_FPRIME_COMMAND_FUNCTION_CODE` used for all F Prime passthrough commands, and the checksum
`ComCfg.FprimeCommandFunctionCode` used for all F Prime passthrough commands, and the checksum
is computed per `CFE_MSG` conventions (the XOR of every packet byte with 0xFF equals zero). Telemetry
packets and packets that already carry a secondary header are transmitted unmodified. The internal
wrap storage is bounded by `CFS_BRIDGE_MAX_WRAPPED_PACKET_SIZE` (2048 bytes, well under the software
Expand Down Expand Up @@ -162,3 +162,4 @@ Coverage: 100% lines, 100% functions.
| 2026-07-30 | Optional F Prime command wrapping: transmit F Prime command packets as valid cFS command packets (secondary header flag, function code, checksum) |
| 2026-08-04 | Add `schedIn` port for rate-group driven operation (one `process()` per tick); bound the `dataIn` packet loop by the maximum packet count |
| 2026-08-04 | Add `bufferAllocate`/`bufferDeallocate` ports: received SB messages are copied into allocated buffers so downstream consumers may hold them past the polling cycle |
| 2026-08-05 | Source the wrapped-command function code from the `ComCfg.FprimeCommandFunctionCode` dictionary constant instead of a local constant |
5 changes: 3 additions & 2 deletions FPrimeCfs/CfsBridge/test/ut/CfsBridgeTester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// ======================================================================

#include "CfsBridgeTester.hpp"
#include "config/FppConstantsAc.hpp"
#include <STest/Pick/Pick.hpp>
#include <cstring>
#include <limits>
Expand Down Expand Up @@ -260,7 +261,7 @@ void CfsBridgeTester ::testTransmitWrappedCommand() {
ASSERT_EQ(call.totalSize, HEADER_SIZE + CFS_BRIDGE_CMD_SEC_HDR_SIZE + sizeof(payload));
ASSERT_EQ(call.payloadSize, CFS_BRIDGE_CMD_SEC_HDR_SIZE + sizeof(payload));
// The secondary header carries the F Prime passthrough function code, then the checksum
ASSERT_EQ(call.payload[0], CFS_BRIDGE_FPRIME_COMMAND_FUNCTION_CODE);
ASSERT_EQ(call.payload[0], ComCfg::FprimeCommandFunctionCode);
// The original payload follows the secondary header unmodified
ASSERT_EQ(std::memcmp(&call.payload[CFS_BRIDGE_CMD_SEC_HDR_SIZE], payload, sizeof(payload)), 0);
// The checksum is valid per CFE_MSG conventions: XOR of every packet byte with 0xFF equals zero
Expand Down Expand Up @@ -338,7 +339,7 @@ void CfsBridgeTester ::testTransmitWrapExactBoundary() {
ASSERT_EQ(CfeStub::state().transmitCount, 1u);
const CfeStub::TransmitCall& call = CfeStub::state().transmitCalls[0];
ASSERT_EQ(call.totalSize, CFS_BRIDGE_MAX_WRAPPED_PACKET_SIZE);
ASSERT_EQ(call.payload[0], CFS_BRIDGE_FPRIME_COMMAND_FUNCTION_CODE);
ASSERT_EQ(call.payload[0], ComCfg::FprimeCommandFunctionCode);
ASSERT_EQ(std::memcmp(&call.payload[CFS_BRIDGE_CMD_SEC_HDR_SIZE], payload, payloadSize), 0);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ module FPrimeCfs {
constant CFS_CMD_ROUTER_ROUTE_TABLE_SIZE = 2

@ The function code routing table: function code -> (route type, output port index).
@ This default configuration routes function code 0 (the CfsBridge F Prime command
@ wrapping function code) as a bare com; projects override with their own codes.
@ This default configuration routes function code 0 (ComCfg.FprimeCommandFunctionCode,
@ the CfsBridge F Prime command wrapping function code) as a bare com; projects
@ override with their own codes. Projects overriding ComCfg.FprimeCommandFunctionCode
@ must override this table to match: the literal here cannot reference the ComCfg
@ constant as ComCfg is not visible to this configuration module.
constant CFS_CMD_ROUTER_ROUTE_TABLE = [
{ functionCode = 0, routeType = CfsCmdRouteType.COM, portIndex = 0 },
{ functionCode = 1, routeType = CfsCmdRouteType.BUFFER, portIndex = 0 },
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ list(APPEND MISSION_GLOBAL_APPLIST fprime fprime_cfs ...)

This will integrate the library into the cFS build and make the targets available for the F Prime build as well.

## Ground Tooling

The [python/](./python) directory provides the `fprime-cfs` Python package: tools that generate cFS GroundSystem configuration from the F Prime dictionary and run the cFS GroundSystem as the GDS for an F Prime deployment. See [python/README.md](./python/README.md).

## Work To Go

This library still needs to demonstrate how to subscribe to cFS messages that aren't strictly commands (e.g. telemetry) and how to rout these messages.
Expand Down
8 changes: 7 additions & 1 deletion config/FPrimeCfsConfig/ComCfg.fpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ module ComCfg {
@ Fixed size of CCSDS TM frames
dictionary constant TmFrameFixedSize = 1024 # Needs to be at least COM_BUFFER_MAX_SIZE + (2 * SpacePacketHeaderSize) + 1

@ Function code carried in the cFS command secondary header of F Prime passthrough commands
dictionary constant FprimeCommandFunctionCode = 0

@ Upper Bound on Fixed size of CCSDS AOS frames
constant AosMaxFrameFixedSize = 1536

Expand All @@ -34,7 +37,8 @@ module ComCfg {
FW_PACKET_TELEM = 0x0001 @< Telemetry packet type - outgoing
FW_PACKET_LOG = 0x0002 @< Log type - outgoing
FW_PACKET_FILE = 0x0003 @< File type - incoming and outgoing
FW_PACKET_PACKETIZED_TLM = 0x0004 @< Packetized telemetry packet type
# 0x020 keeps the message id (0x0800 | APID) clear of the default cFE core telemetry topics
FW_PACKET_PACKETIZED_TLM = 0x0020 @< Packetized telemetry packet type
FW_PACKET_DP = 0x0005 @< Data Product packet type
FW_PACKET_IDLE = 0x0006 @< F Prime idle
FW_PACKET_HAND = 0x00FE @< F Prime handshake
Expand All @@ -48,6 +52,7 @@ module ComCfg {
comQueueIndex: FwIndexType @< Queue Index used by the ComQueue, other components shall not modify
apid: Apid @< 11 bits APID in CCSDS
hasSecHdr: bool @< Secondary header flag for SpacePacketFramer
sequenceFlags: U8 @< 2 bit Sequence flags (0b00=continuation, 0b01=first, 0b10=last, 0b11=unsegmented)
sequenceCount: U16 @< 14 bit Sequence count - sequence count is incremented per APID
vcId: U8 @< 6 bit Virtual Channel ID - used for AOS, TC, and TM Protocols
pvn: Pvn @< Packet Version Number - used for AOS deframing to identify packet type
Expand All @@ -58,6 +63,7 @@ module ComCfg {
comQueueIndex = 0
apid = Apid.FW_PACKET_UNKNOWN
hasSecHdr = false
sequenceFlags = 0x3
sequenceCount = 0
vcId = 1
pvn = Pvn.INVALID_UNINITIALIZED
Expand Down
2 changes: 2 additions & 0 deletions python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__/
*.egg-info/
83 changes: 83 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# fprime-cfs

Tooling for running the [cFS GroundSystem](https://github.com/nasa/cFS-GroundSystem) as the
GDS for an F Prime deployment integrated through `fprime_cfs`.

> **Note**: the CI_LAB/TO_LAB path used by this tooling carries commands and telemetry
> over plain UDP with no authentication or integrity protection. It is intended for
> isolated lab and bench networks only, not as an operational uplink.

## Installation

```bash
pip install ./python # from the fprime_cfs checkout root
```

## Tools

### `fprime-cfs`

Runs the full cFS GroundSystem GDS: generates the GroundSystem configuration from the
F Prime dictionary, optionally launches the cFS application, and launches the
GroundSystem GUI. The GroundSystem talks directly to the cFS software bus over UDP
through the standard lab applications: commands go to CI_LAB (default port 1234) and
telemetry arrives from TO_LAB (default port 2234). On startup the runner sends the
TO_LAB enable-output command (suppress with `--no-enable-telemetry`; the destination IP
may be changed with `--telemetry-destination`).

Connecting the F Prime GDS instead is done through the GdsBridge cFS application (from
the [fprime-community/fprime_gds](https://github.com/fprime-community/fprime_gds) cFS
library), which exposes a TCP server for `fprime-gds`; that path does not use this
runner.

```bash
fprime-cfs --dictionary <deployment>/dict/*TopologyDictionary.json \
--ground-system-dir <cfs-checkout>/tools/cFS-GroundSystem
```

By default the cFS-GroundSystem checkout is copied to a temporary directory (removed on
exit) and the configuration is generated there, leaving the checkout pristine; pass
`--no-copy` to generate the configuration in place instead.

The cFS application itself may be launched by the runner by passing `--app
<path-to-core-binary>` (with `--deployment` pointing at its directory), mirroring
`fprime-gds`; pass `--no-app`/`-n` to manage the application yourself. Extra application
arguments may be given with `--application-arguments`.

The GroundSystem GUI has its own dependencies (PyQt5, pyzmq); install them from the
cFS-GroundSystem checkout before running.

Telemetry decoding assumes the default `Fw.Time` serialization (U16 time base, U8 time
context, U32 seconds, U32 microseconds); deployments overriding the time base/context
storage types are not supported.

### `fprime-cfs-config`

Generates the cFS GroundSystem configuration only:

- `Subsystems/tlmGUI/telemetry-pages.txt` and `Subsystems/tlmGUI/fprime-tlm.txt`: a
telemetry page for the single fixed telemetry packet defined by the dictionary. Exactly
one packet must be defined; the tool errors otherwise. Multi-byte items use an explicit
big-endian (`>`) struct prefix as F Prime serializes values big-endian.
- `Subsystems/cmdGui/command-pages.txt`, `CommandFiles/`, and `ParameterFiles/`: a command
page exposing every F Prime command. All commands share the single cFS command function
code published in the dictionary as `ComCfg.FprimeCommandFunctionCode`. The F Prime
framing descriptor and the command opcode are prepended as the first two parameters of
every command; their fixed required values are given in each parameter description.
Commands with arguments that MiniCmdUtil cannot represent (e.g. strings) are skipped
with a warning. A second `Telemetry Output` command page (the GroundSystem's stock
TO_LAB command definitions) and a `quick-buttons.txt` with an `Enable Tlm` quick button
are also written so telemetry output can be managed from the GUI.

Limitations inherited from the GroundSystem GUI:

- The tlmGUI telemetry page displays at most 40 rows; the tool errors if the packet
defines more channels/fields than fit.
- MiniCmdUtil has no float or boolean entry: float arguments must be entered as their
raw IEEE-754 bit pattern and booleans as 0 (false) / 255 (true), as noted in each
generated parameter description.

```bash
fprime-cfs-config --dictionary <dictionary.json> --ground-system <cFS-GroundSystem dir>
```

24 changes: 24 additions & 0 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[build-system]
requires = ["setuptools>=64"]
build-backend = "setuptools.build_meta"

[project]
name = "fprime-cfs"
version = "0.1.0"
description = "F Prime dictionary conversion and runner tooling for the cFS GroundSystem GDS"
readme = "README.md"
license = { text = "Apache-2.0" }
requires-python = ">=3.9"
dependencies = [
"fprime-gds>=4.2.1,<5",
]

[project.scripts]
fprime-cfs = "fprime_cfs.__main__:main"
fprime-cfs-config = "fprime_cfs.generate:main"

[tool.setuptools.packages.find]
where = ["src"]

[project.optional-dependencies]
test = ["pytest>=6"]
3 changes: 3 additions & 0 deletions python/src/fprime_cfs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""fprime_cfs: F Prime dictionary conversion tooling for the cFS GroundSystem"""

__version__ = "0.1.0"
Loading
Loading