F Prime Component Framework (avionics/fsw/fprime-component)
Use when the task is NASA JPL F Prime (F´) flight software architecture:
component kinds, typed input and output ports, command registration by
opcode, telemetry channels, severity-flagged events, the topology that
connects components, or the rate group schedule that drives them. The
module validates a component model and runs a deterministic clocked
dispatch simulation in pure Python: you define components, connect
producer outputs to consumer inputs, schedule input ports in rate
groups, and the simulation records invocations, deliveries, commands
and telemetry samples with sequence counters, then expands any clean
definition into a scaffold manifest for code generation. F´ is NASA
JPL open-source flight software (github/nasa/fprime, Apache-2.0), a
typed-port framework; the sibling avionics/fsw/cfs-architecture leaf
models NASA cFS, the publish/subscribe software bus framework. The two
mechanisms differ: cFS routes messages by message ID over a central
bus, F´ invokes typed ports along direct point-to-point connections
scheduled by rate groups.
Domain quick reference
- Component kinds: an active component owns a thread and a message
queue, so the framework dispatches it; a queued component owns a
queue without a thread, so its input invocations are dispatched in
arrival order by the clocked framework; a passive component owns
neither, so its handlers run inline in the caller context.
- Ports: typed input and output ports carry one payload type from the
supported set U8/U16/U32/I32/F32/F64/string; an input port may
declare data_type "serial", a serial interface whose dynamic payload
matches any partner type. Connections run output to input only.
- Dispatch drivers: every input port of an active or queued component
must be dispatched by exactly one driver, either one incoming
connection (data-driven) or membership in exactly one rate group
(time-driven). A port with zero drivers is orphaned; more than one
is double dispatch. Passive input ports run inline, so a passive
port invoked by two rate groups only warns (two timing contexts),
and a passive port nothing calls is dead code (warning).
- Active rule: an active component must declare at least one input
port, because the framework can only dispatch it through an input.
Queued components may be pure command sinks.
- Commands: registered per component with unique opcodes in
0x0000..0xFFFF and dispatched asynchronously on the command path,
which requires a queue; passive components must not declare
commands. Components receive commands by name or opcode.
- Telemetry: declared channels carry typed samples; the simulation
stamps a monotonic sequence counter per channel, so the ground can
detect dropped samples. Events carry an F´ style severity from
(FATAL, HIGH, LOW, INFO, DEBUG).
- Rate groups: a group ticks its listed input ports every period
derived from base_hz / hz master clock ticks. The master clock runs
at the fastest declared group rate by default (base_hz = max hz).
- Scaffold manifest: generate_manifest expands a clean definition into
the class name, header guard, port method stubs, command dispatch
entries and telemetry channel list a developer would codegen from.
This proves the model; it is not full F´ code generation.
Workflow
- Classify each software unit: active when it needs its own dispatch
thread, queued when it buffers async input work, passive when it is
pure logic called inline. Build definitions with
validate_component (or the component() builder) and fix every
issue: missing name, unsupported kind, duplicate port names,
duplicate command opcodes or names, telemetry types outside the
supported set, event severities outside FATAL/HIGH/LOW/INFO/DEBUG,
an active component with no input port, or a passive component
that owns commands.
- Connect the topology with conn() and validate_connections: every
connection must run output to input, both ends must exist, data
types must match (serial interfaces match anything), and self-loops
are rejected.
- Schedule dispatch with rate_group() and validate_rate_groups:
list the input port each group ticks at its hz rate, then check
the (issues, warnings) pair. Every active or queued input port must
have exactly one dispatch driver; passive ports invoked from two
groups or invoked by nothing warn.
- Run the umbrella check validate_topology over all three artifacts;
a clean verdict means issues and warnings are both empty (or only
warnings when passive timing is accepted).
- Simulate deterministically: build Simulation(defs, connections,
rate_groups), call run(cycles) for the master clock, then read
invocations (rate group dispatches), deliveries (connection data
flow), samples (telemetry with per-channel sequence counters),
command_log and event_log. Deliver commands with send_command and
raise declared events with raise_event; record extra telemetry with
record_telemetry.
- Codegen from a clean definition: generate_manifest returns the
scaffold files (model text, header, implementation) plus the
structured class name, header guard, stubs, dispatch entries and
channel list.
- Confirm the deterministic checks with the contract test
scripts/test_fprime_component.py.
Worked example
A 1 Hz topology: active SignalGen ticks its run input at 1 Hz and
emits a U32 ramp on tlmOut; queued DataLogger receives each value on
logIn and logs it to telemetry.
import fprime_component_logic as fprime
sg = fprime.component("SignalGen", "active",
ports=[{"direction": "input", "name": "run", "data_type": "U32"},
{"direction": "output", "name": "tlmOut", "data_type": "U32"}],
commands=[{"name": "reset", "opcode": 0x01}],
events=[{"name": "fault", "severity": "HIGH"}])
dl = fprime.component("DataLogger", "queued",
ports=[{"direction": "input", "name": "logIn", "data_type": "U32"}],
telemetry=[{"name": "logIn", "type": "U32"}])
defs = [sg, dl]
conns = [fprime.conn("SignalGen", "tlmOut", "DataLogger", "logIn")]
groups = [fprime.rate_group("1Hz", 1.0, [("SignalGen", "run")])]
fprime.validate_topology(defs, conns, groups)
# {'issues': [], 'warnings': []}
Simulation over three master cycles, base clock 1 Hz:
sim = fprime.Simulation(defs, conns, groups)
sim.run(3)
[(i["cycle"], i["comp"], i["port"]) for i in sim.invocations]
# [(0, 'SignalGen', 'run'), (1, 'SignalGen', 'run'), (2, 'SignalGen', 'run')]
[(d["cycle"], d["to_comp"], d["value"]) for d in sim.deliveries]
# [(0, 'DataLogger', 0), (1, 'DataLogger', 1), (2, 'DataLogger', 2)]
[(s["cycle"], s["channel"], s["value"], s["seq"]) for s in sim.samples]
# [(0, 'logIn', 0, 0), (1, 'logIn', 1, 1), (2, 'logIn', 2, 2)]
sim.send_command("SignalGen", "reset")
# {'cycle': 3, 'comp': 'SignalGen', 'name': 'reset', 'opcode': 1}
Negative checks: registering a second command with opcode 0x01 raises
the issue "duplicate command opcode 0x0001 (commands 'reset' and
'reboot')"; retyping DataLogger.logIn as F32 raises "connection type
mismatch 'SignalGen.tlmOut' (U32) -> 'DataLogger.logIn' (F32)".
Manifest for SignalGen:
m = fprime.generate_manifest(sg)
m["class_name"] # 'SignalGenComponent'
m["header_guard"] # 'SIGNALGEN_COMPONENT_HPP'
[f["path"] for f in m["files"]]
# ['SignalGen.fpp', 'SignalGenComponentBase.hpp', 'SignalGenComponentBase.cpp']
Verification checklist
- Every active component declares at least one input port; passive
components declare no commands; command opcodes are unique and in
0x0000..0xFFFF; telemetry types and event severities come from the
supported sets.
- Every connection runs from a declared output to a declared input
with matching data types (serial matches any); no dangling
components or ports; no self-loops.
- Every active and queued input port has exactly one dispatch driver:
one incoming connection or one rate group. A passive input port in
two rate groups, or in none, warns.
- Simulation construction raises ValueError listing every topology
issue, so the clocked loop only ever runs over a clean model.
- run(cycles) is deterministic: invocations and deliveries are
recorded in schedule order, emitted output values equal the master
cycle index, and telemetry samples carry a monotonic per-channel
sequence counter.
- ValueError rejections: Simulation on an invalid topology,
send_command to an unknown component, unknown command, unknown
opcode, or a passive component, raise_event on an undeclared event,
record_telemetry on an undeclared channel or a value that does not
fit the declared type, and generate_manifest on an unclean
definition.
- generate_manifest expands a clean definition only, and its header
guard, port stubs, opcode entries and channel list match the
definition exactly.
Pitfalls
- Mixing up the component kinds: an active component owns a thread
and message queue, a queued component owns a queue without a
thread, and a passive component runs its handlers inline in the
caller context - the framework requires every active component to
declare at least one input port (it can only be dispatched through
an input) and forbids commands on passive components, whose
handlers have no queue to receive them.
- Breaking command uniqueness: opcodes must be unique per component
in 0x0000..0xFFFF and names unique too - registering a second
command at opcode 0x01 raises the "duplicate command opcode
0x0001" issue naming both commands, and components receive commands
by name or opcode, so a collision silently routes to the wrong
handler.
- Connecting ports carelessly: connections run output to input only,
both ends must exist, and the payload types must match - retyping
DataLogger.logIn as F32 raises the connection type mismatch issue
against SignalGen.tlmOut (U32), while a "serial" data_type matches
any partner; self-loops are rejected outright.
- Leaving dispatch drivers ambiguous: every active or queued input
port needs exactly one dispatch driver, one incoming connection or
one rate group - zero drivers orphan the port and more than one
double-dispatches it (both are issues), while a passive port in two
rate groups or in none only warns, because passive handlers run
inline.
- Driving the simulation blindly: Simulation construction raises
ValueError listing every topology issue, send_command rejects
unknown components, commands, opcodes, and passive components, and
raise_event or record_telemetry on undeclared targets - or a value
that does not fit the declared channel type - fails, so the clocked
loop only ever runs over a clean model.
- Treating the manifest as full code generation: generate_manifest
expands a clean definition into the scaffold (class name, header
guard, stubs, dispatch entries, channel list) that proves the
model - it is not the F´ codegen pipeline, and it refuses unclean
definitions.
Behavior contract (gate 3)
The component rules, connection checks, rate group coverage, dispatch
simulation and manifest generator are exercised by the contract test:
scripts/test_fprime_component.py against scripts/fprime_component_logic.py
(stdlib unittest, offline, deterministic). Run:
python3 scripts/test_fprime_component.py
References
- scripts/fprime_component_logic.py: validate_component,
validate_connections, validate_rate_groups, validate_topology,
Simulation, generate_manifest, and the component(), conn(),
rate_group() builders.
- scripts/test_fprime_component.py: contract test, 35 cases.
Related skills
- avionics/fsw/cfs-architecture: NASA cFS sibling in the same pack;
cFS routes messages by message ID over a software bus, F´ invokes
typed ports along direct connections. Route software bus questions
there, topology and rate group questions here.
- avionics/do178c/development and avionics/do178c/software-testing:
F´ flight software is developed and verified under DO-178C; the
requirements traceability and test case flows start there.
- avionics/ima/ima-partitioning: ARINC 653 partitioning is the
hardware-level isolation context in which component topologies run
on integrated avionics platforms.
Compliance
- Standards referenced, not reproduced: F´ is NASA JPL open-source
software (Apache-2.0), not a certification standard; this leaf keys
to do-178c (the governing airborne software standard for flight
software built on F´) listed reference-only per standards-map.yaml.
- The model implements the F´ component vocabulary in summary form:
component kinds, ports, commands, telemetry, events, rate groups.
No proprietary standard text is reproduced.
- compliance: STANDARDS-REF, gated: false.
1---2name: fprime-component3description: Use when designing or reviewing an F Prime topology, checking opcode and port-type consistency, or generating the component scaffold manifest. Model and validate a NASA JPL F Prime (F´) flight software component architecture: define components as active, queued or passive, attach typed input and output ports plus serial interfaces, register commands with unique opcodes, declare telemetry channels and severity-flagged events, connect producer outputs to consumer inputs across a topology, schedule component input ports in rate groups, and run a deterministic clocked dispatch simulation that records invocations, deliveries, command log entries and telemetry samples with per-channel sequence counters. Produces the validated component model, the connection and rate group report, and a scaffold manifest for code generation. Trigger: F Prime, F´, component framework, topology, rate group, command dispatch, telemetry channel, port connection, flight software modeling.4license: Apache-2.05---67# F Prime Component Framework (avionics/fsw/fprime-component)89Use when the task is NASA JPL F Prime (F´) flight software architecture:10component kinds, typed input and output ports, command registration by11opcode, telemetry channels, severity-flagged events, the topology that12connects components, or the rate group schedule that drives them. The13module validates a component model and runs a deterministic clocked14dispatch simulation in pure Python: you define components, connect15producer outputs to consumer inputs, schedule input ports in rate16groups, and the simulation records invocations, deliveries, commands17and telemetry samples with sequence counters, then expands any clean18definition into a scaffold manifest for code generation. F´ is NASA19JPL open-source flight software (github/nasa/fprime, Apache-2.0), a20typed-port framework; the sibling avionics/fsw/cfs-architecture leaf21models NASA cFS, the publish/subscribe software bus framework. The two22mechanisms differ: cFS routes messages by message ID over a central23bus, F´ invokes typed ports along direct point-to-point connections24scheduled by rate groups.2526## Domain quick reference2728- Component kinds: an active component owns a thread and a message29 queue, so the framework dispatches it; a queued component owns a30 queue without a thread, so its input invocations are dispatched in31 arrival order by the clocked framework; a passive component owns32 neither, so its handlers run inline in the caller context.33- Ports: typed input and output ports carry one payload type from the34 supported set U8/U16/U32/I32/F32/F64/string; an input port may35 declare data_type "serial", a serial interface whose dynamic payload36 matches any partner type. Connections run output to input only.37- Dispatch drivers: every input port of an active or queued component38 must be dispatched by exactly one driver, either one incoming39 connection (data-driven) or membership in exactly one rate group40 (time-driven). A port with zero drivers is orphaned; more than one41 is double dispatch. Passive input ports run inline, so a passive42 port invoked by two rate groups only warns (two timing contexts),43 and a passive port nothing calls is dead code (warning).44- Active rule: an active component must declare at least one input45 port, because the framework can only dispatch it through an input.46 Queued components may be pure command sinks.47- Commands: registered per component with unique opcodes in48 0x0000..0xFFFF and dispatched asynchronously on the command path,49 which requires a queue; passive components must not declare50 commands. Components receive commands by name or opcode.51- Telemetry: declared channels carry typed samples; the simulation52 stamps a monotonic sequence counter per channel, so the ground can53 detect dropped samples. Events carry an F´ style severity from54 (FATAL, HIGH, LOW, INFO, DEBUG).55- Rate groups: a group ticks its listed input ports every period56 derived from base_hz / hz master clock ticks. The master clock runs57 at the fastest declared group rate by default (base_hz = max hz).58- Scaffold manifest: generate_manifest expands a clean definition into59 the class name, header guard, port method stubs, command dispatch60 entries and telemetry channel list a developer would codegen from.61 This proves the model; it is not full F´ code generation.6263## Workflow64651. Classify each software unit: active when it needs its own dispatch66 thread, queued when it buffers async input work, passive when it is67 pure logic called inline. Build definitions with68 validate_component (or the component() builder) and fix every69 issue: missing name, unsupported kind, duplicate port names,70 duplicate command opcodes or names, telemetry types outside the71 supported set, event severities outside FATAL/HIGH/LOW/INFO/DEBUG,72 an active component with no input port, or a passive component73 that owns commands.742. Connect the topology with conn() and validate_connections: every75 connection must run output to input, both ends must exist, data76 types must match (serial interfaces match anything), and self-loops77 are rejected.783. Schedule dispatch with rate_group() and validate_rate_groups:79 list the input port each group ticks at its hz rate, then check80 the (issues, warnings) pair. Every active or queued input port must81 have exactly one dispatch driver; passive ports invoked from two82 groups or invoked by nothing warn.834. Run the umbrella check validate_topology over all three artifacts;84 a clean verdict means issues and warnings are both empty (or only85 warnings when passive timing is accepted).865. Simulate deterministically: build Simulation(defs, connections,87 rate_groups), call run(cycles) for the master clock, then read88 invocations (rate group dispatches), deliveries (connection data89 flow), samples (telemetry with per-channel sequence counters),90 command_log and event_log. Deliver commands with send_command and91 raise declared events with raise_event; record extra telemetry with92 record_telemetry.936. Codegen from a clean definition: generate_manifest returns the94 scaffold files (model text, header, implementation) plus the95 structured class name, header guard, stubs, dispatch entries and96 channel list.977. Confirm the deterministic checks with the contract test98 scripts/test_fprime_component.py.99100## Worked example101102A 1 Hz topology: active SignalGen ticks its run input at 1 Hz and103emits a U32 ramp on tlmOut; queued DataLogger receives each value on104logIn and logs it to telemetry.105106```python107import fprime_component_logic as fprime108109sg = fprime.component("SignalGen", "active",110 ports=[{"direction": "input", "name": "run", "data_type": "U32"},111 {"direction": "output", "name": "tlmOut", "data_type": "U32"}],112 commands=[{"name": "reset", "opcode": 0x01}],113 events=[{"name": "fault", "severity": "HIGH"}])114dl = fprime.component("DataLogger", "queued",115 ports=[{"direction": "input", "name": "logIn", "data_type": "U32"}],116 telemetry=[{"name": "logIn", "type": "U32"}])117118defs = [sg, dl]119conns = [fprime.conn("SignalGen", "tlmOut", "DataLogger", "logIn")]120groups = [fprime.rate_group("1Hz", 1.0, [("SignalGen", "run")])]121122fprime.validate_topology(defs, conns, groups)123# {'issues': [], 'warnings': []}124```125126Simulation over three master cycles, base clock 1 Hz:127128```python129sim = fprime.Simulation(defs, conns, groups)130sim.run(3)131[(i["cycle"], i["comp"], i["port"]) for i in sim.invocations]132# [(0, 'SignalGen', 'run'), (1, 'SignalGen', 'run'), (2, 'SignalGen', 'run')]133[(d["cycle"], d["to_comp"], d["value"]) for d in sim.deliveries]134# [(0, 'DataLogger', 0), (1, 'DataLogger', 1), (2, 'DataLogger', 2)]135[(s["cycle"], s["channel"], s["value"], s["seq"]) for s in sim.samples]136# [(0, 'logIn', 0, 0), (1, 'logIn', 1, 1), (2, 'logIn', 2, 2)]137sim.send_command("SignalGen", "reset")138# {'cycle': 3, 'comp': 'SignalGen', 'name': 'reset', 'opcode': 1}139```140141Negative checks: registering a second command with opcode 0x01 raises142the issue "duplicate command opcode 0x0001 (commands 'reset' and143'reboot')"; retyping DataLogger.logIn as F32 raises "connection type144mismatch 'SignalGen.tlmOut' (U32) -> 'DataLogger.logIn' (F32)".145146Manifest for SignalGen:147148```python149m = fprime.generate_manifest(sg)150m["class_name"] # 'SignalGenComponent'151m["header_guard"] # 'SIGNALGEN_COMPONENT_HPP'152[f["path"] for f in m["files"]]153# ['SignalGen.fpp', 'SignalGenComponentBase.hpp', 'SignalGenComponentBase.cpp']154```155156## Verification checklist157158- Every active component declares at least one input port; passive159 components declare no commands; command opcodes are unique and in160 0x0000..0xFFFF; telemetry types and event severities come from the161 supported sets.162- Every connection runs from a declared output to a declared input163 with matching data types (serial matches any); no dangling164 components or ports; no self-loops.165- Every active and queued input port has exactly one dispatch driver:166 one incoming connection or one rate group. A passive input port in167 two rate groups, or in none, warns.168- Simulation construction raises ValueError listing every topology169 issue, so the clocked loop only ever runs over a clean model.170- run(cycles) is deterministic: invocations and deliveries are171 recorded in schedule order, emitted output values equal the master172 cycle index, and telemetry samples carry a monotonic per-channel173 sequence counter.174- ValueError rejections: Simulation on an invalid topology,175 send_command to an unknown component, unknown command, unknown176 opcode, or a passive component, raise_event on an undeclared event,177 record_telemetry on an undeclared channel or a value that does not178 fit the declared type, and generate_manifest on an unclean179 definition.180- generate_manifest expands a clean definition only, and its header181 guard, port stubs, opcode entries and channel list match the182 definition exactly.183184## Pitfalls185186- Mixing up the component kinds: an active component owns a thread187 and message queue, a queued component owns a queue without a188 thread, and a passive component runs its handlers inline in the189 caller context - the framework requires every active component to190 declare at least one input port (it can only be dispatched through191 an input) and forbids commands on passive components, whose192 handlers have no queue to receive them.193- Breaking command uniqueness: opcodes must be unique per component194 in 0x0000..0xFFFF and names unique too - registering a second195 command at opcode 0x01 raises the "duplicate command opcode196 0x0001" issue naming both commands, and components receive commands197 by name or opcode, so a collision silently routes to the wrong198 handler.199- Connecting ports carelessly: connections run output to input only,200 both ends must exist, and the payload types must match - retyping201 DataLogger.logIn as F32 raises the connection type mismatch issue202 against SignalGen.tlmOut (U32), while a "serial" data_type matches203 any partner; self-loops are rejected outright.204- Leaving dispatch drivers ambiguous: every active or queued input205 port needs exactly one dispatch driver, one incoming connection or206 one rate group - zero drivers orphan the port and more than one207 double-dispatches it (both are issues), while a passive port in two208 rate groups or in none only warns, because passive handlers run209 inline.210- Driving the simulation blindly: Simulation construction raises211 ValueError listing every topology issue, send_command rejects212 unknown components, commands, opcodes, and passive components, and213 raise_event or record_telemetry on undeclared targets - or a value214 that does not fit the declared channel type - fails, so the clocked215 loop only ever runs over a clean model.216- Treating the manifest as full code generation: generate_manifest217 expands a clean definition into the scaffold (class name, header218 guard, stubs, dispatch entries, channel list) that proves the219 model - it is not the F´ codegen pipeline, and it refuses unclean220 definitions.221222## Behavior contract (gate 3)223224The component rules, connection checks, rate group coverage, dispatch225simulation and manifest generator are exercised by the contract test:226scripts/test_fprime_component.py against scripts/fprime_component_logic.py227(stdlib unittest, offline, deterministic). Run:228python3 scripts/test_fprime_component.py229230## References231232- scripts/fprime_component_logic.py: validate_component,233 validate_connections, validate_rate_groups, validate_topology,234 Simulation, generate_manifest, and the component(), conn(),235 rate_group() builders.236- scripts/test_fprime_component.py: contract test, 35 cases.237238## Related skills239240- avionics/fsw/cfs-architecture: NASA cFS sibling in the same pack;241 cFS routes messages by message ID over a software bus, F´ invokes242 typed ports along direct connections. Route software bus questions243 there, topology and rate group questions here.244- avionics/do178c/development and avionics/do178c/software-testing:245 F´ flight software is developed and verified under DO-178C; the246 requirements traceability and test case flows start there.247- avionics/ima/ima-partitioning: ARINC 653 partitioning is the248 hardware-level isolation context in which component topologies run249 on integrated avionics platforms.250251## Compliance252253- Standards referenced, not reproduced: F´ is NASA JPL open-source254 software (Apache-2.0), not a certification standard; this leaf keys255 to do-178c (the governing airborne software standard for flight256 software built on F´) listed reference-only per standards-map.yaml.257- The model implements the F´ component vocabulary in summary form:258 component kinds, ports, commands, telemetry, events, rate groups.259 No proprietary standard text is reproduced.260- compliance: STANDARDS-REF, gated: false.