mirror of
https://github.com/nasa/fpp.git
synced 2025-12-11 03:05:32 -06:00
2885 lines
84 KiB
Plaintext
2885 lines
84 KiB
Plaintext
== Defining Components
|
|
|
|
In F Prime, the *component* is the basic unit of FSW function.
|
|
An F Prime component is similar to a class in an object-oriented language.
|
|
An F Prime FSW application is divided into several
|
|
*component instances*, each of which instantiates a component.
|
|
The component instances communicate by sending and receiving
|
|
invocations on their
|
|
<<Defining-Ports, ports>>.
|
|
|
|
In F Prime, there are three kinds of components:
|
|
active, queued, and passive.
|
|
An active component has a thread of control
|
|
and a message queue.
|
|
A queued component has a message queue, but no thread
|
|
of control; control runs on another thread, such as
|
|
a rate group thread.
|
|
A passive component has no thread of control and no
|
|
message queue; it is like a non-threaded function library.
|
|
|
|
=== Component Definitions
|
|
|
|
An FPP *component definition* defines an F Prime component.
|
|
To write a component definition, you write the following:
|
|
|
|
* The component kind: one of `active`, `passive`,
|
|
or `queued`.
|
|
* The keyword `component`.
|
|
* The <<Defining-Constants_Names,name>> of the component.
|
|
* A sequence of *component members* enclosed in curly braces
|
|
`{` ... `}`.
|
|
|
|
As an example, here is a passive component `C` with no members:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ An empty passive component
|
|
passive component C {
|
|
|
|
}
|
|
----
|
|
|
|
A component definition and each of its members is an
|
|
<<Writing-Comments-and-Annotations_Annotations,annotatable element>>.
|
|
For example, you can annotate the component as shown above.
|
|
The members of a component form an
|
|
<<Defining-Constants_Multiple-Definitions-and-Element-Sequences,
|
|
element sequence>> with a semicolon as the optional
|
|
terminating punctuation.
|
|
The following sections describe the available component members
|
|
except for importing port interfaces, which we describe in
|
|
<<Defining-and-Using-Port-Interfaces_Using-Port-Interfaces-in-Interface-Definitions,
|
|
a separate section>>.
|
|
|
|
=== Port Instances
|
|
|
|
A *port instance* is a component member that specifies an instance of an FPP
|
|
<<Defining-Ports, port>> used by the instances of the component.
|
|
Component instances use their port instances to communicate
|
|
with other component instances.
|
|
|
|
A port instance instantiates a port.
|
|
The port definition provides information common to all uses of the port, such as
|
|
the kind of data carried on the port.
|
|
The port instance provides use-specific information, such
|
|
as the name of the instance and the direction of invocation
|
|
(input or output).
|
|
|
|
==== Basic Port Instances
|
|
|
|
The simplest port instance specifies a kind, a name, and a type.
|
|
The kind is one of the following:
|
|
|
|
* `async` `input`: Input to this component that arrives on a message queue, to
|
|
be dispatched on this component's thread (if this component is active)
|
|
or on the thread of another port invocation (if this component is queued).
|
|
|
|
* `sync` `input`: Input that invokes a handler defined in this component,
|
|
and run on the thread of the caller.
|
|
|
|
* `guarded` `input`: Similar to sync input, but the handler is
|
|
guarded by a mutual exclusion lock.
|
|
|
|
* `output`: Output transmitted by this component.
|
|
|
|
The name is the name of the port instance.
|
|
The type refers to a <<Defining-Ports,port definition>>.
|
|
|
|
As an example, here is a passive component `F32Adder` that
|
|
adds two `F32` values and produces an `F32` value.
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A port for carrying an F32 value
|
|
port F32Value(value: F32)
|
|
|
|
@ A passive component for adding two F32 values
|
|
passive component F32Adder {
|
|
|
|
@ Input 1
|
|
sync input port f32ValueIn1: F32Value
|
|
|
|
@ Input 2
|
|
sync input port f32ValueIn2: F32Value
|
|
|
|
@ Output
|
|
output port f32ValueOut: F32Value
|
|
|
|
}
|
|
----
|
|
|
|
There are two sync input port instances and one output port
|
|
instance.
|
|
The kind appears first, followed by the keyword `port`, the port instance
|
|
name, a colon, and the type.
|
|
Each port instance is an
|
|
<<Writing-Comments-and-Annotations_Annotations,annotatable element>>,
|
|
so you can annotate the instances as shown.
|
|
|
|
As another example, here is an active version of `F32Adder`
|
|
with `async` input ports:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A port for carrying an F32 value
|
|
port F32Value(value: F32)
|
|
|
|
@ An active component for adding two F32 values
|
|
active component ActiveF32Adder {
|
|
|
|
@ Input 1
|
|
async input port f32ValueIn1: F32Value
|
|
|
|
@ Input 2
|
|
async input port f32ValueIn2: F32Value
|
|
|
|
@ Output
|
|
output port f32ValueOut: F32Value
|
|
|
|
}
|
|
----
|
|
|
|
In each case, the adding is done in the target language.
|
|
For example, in the {cpp} implementation, you would generate a
|
|
base class with a virtual handler function, and then override that virtual
|
|
function in a derived class that you write.
|
|
For further details about implementing F Prime components, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/[F Prime User Manual].
|
|
|
|
*Note on terminology:* As explained above, there is a technical
|
|
distinction between a _port type_ (defined outside any component, and providing
|
|
the type of a port instance)
|
|
and a _port instance_ (specified inside a component and instantiating
|
|
a port type).
|
|
However, it is sometimes useful to refer to a port instance with
|
|
the shorter term "`port`" when there is no danger of confusion.
|
|
We will do that in this manual.
|
|
For example, we will say that the `F32Adder` component has three
|
|
ports: two async input ports of type `F32Value` and one output port
|
|
of type `F32Value`.
|
|
|
|
==== Rules for Port Instances
|
|
|
|
The port instances appearing in a component definition must
|
|
satisfy certain rules.
|
|
These rules ensure that the FPP model makes sense.
|
|
|
|
First, no passive component may have an `async` `input`
|
|
port.
|
|
This is because a passive component has no message queue,
|
|
so asynchronous input is not possible.
|
|
As an example, if we modify the input ports of our `F32Adder`
|
|
to make them `async`, we get an error.
|
|
|
|
[source,fpp]
|
|
--------
|
|
port F32Value(value: F32)
|
|
|
|
# Error: Passive component may not have async input
|
|
passive component ErroneousF32Adder {
|
|
|
|
async input port f32ValueIn1: F32Value
|
|
|
|
async input port f32ValueIn2: F32Value
|
|
|
|
output port f32ValueOut: F32Value
|
|
|
|
}
|
|
--------
|
|
|
|
Try presenting this code to `fpp-check` and observe what happens.
|
|
|
|
Second, an active or queued component _must_ have asynchronous input.
|
|
That means it must have at least one async input port;
|
|
or it must have an internal port;
|
|
or it must have at least one async command; or it must have
|
|
at least one state machine instance.
|
|
Internal ports, async commands, and state machine instances
|
|
are described below.
|
|
As an example, if we modify the input ports of our `ActiveF32Adder`
|
|
to make them `sync`, we get an error, because
|
|
there is no async input.
|
|
|
|
[source,fpp]
|
|
--------
|
|
port F32Value(value: F32)
|
|
|
|
# Error: Active component must have async input
|
|
active component ErroneousActiveF32Adder {
|
|
|
|
sync input port f32ValueIn1: F32Value
|
|
|
|
sync input port f32ValueIn2: F32Value
|
|
|
|
output port f32ValueOut: F32Value
|
|
|
|
}
|
|
--------
|
|
|
|
Third, a port type appearing in an `async` `input` port
|
|
may not have a return type.
|
|
This is because returning a value
|
|
makes sense only for synchronous input.
|
|
As an example, this component definition is illegal:
|
|
|
|
[source,fpp]
|
|
--------
|
|
port P -> U32
|
|
|
|
active component Error {
|
|
|
|
# Error: port instance p: P is async input and
|
|
# port P has a return type
|
|
async input port p: P
|
|
|
|
}
|
|
--------
|
|
|
|
==== Arrays of Port Instances
|
|
|
|
When you specify a port instance as part of an FPP component, you
|
|
are actually specifying an _array_ of port instances.
|
|
Each instance has a *port number*, where the port numbers start at zero
|
|
and go up by one at each successive element.
|
|
(Another way to say this is that the port numbers are the array indices,
|
|
and the indices start at zero.)
|
|
|
|
If you don't specify a size for the array, as shown in
|
|
the previous sections, then the array has size one, and there is a single port
|
|
instance with port number zero.
|
|
Thus a port instance specifier with no array size acts like a singleton
|
|
element.
|
|
Alternatively, you can specify an explicit array size.
|
|
You do that by writing an <<Defining-Constants_Expressions,expression>>
|
|
enclosed in square brackets `[` ... `]` denoting the size (number of elements)
|
|
of the array.
|
|
The size expression must evaluate to a numeric value.
|
|
As with
|
|
<<Defining-Types_Array-Type-Definitions_Writing-an-Array-Type-Definition,
|
|
array type definitions>>,
|
|
the size goes before the element type.
|
|
As an example, here is another version of the `F32Adder` component, this time
|
|
using a single array of two input ports instead of two named ports.
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A port for carrying an F32 value
|
|
port F32Value(value: F32)
|
|
|
|
@ A passive component for adding two F32 values
|
|
passive component F32Adder {
|
|
|
|
@ Inputs 0 and 1
|
|
sync input port f32ValueIn: [2] F32Value
|
|
|
|
@ Output
|
|
output port f32ValueOut: F32Value
|
|
|
|
}
|
|
----
|
|
|
|
==== Priority
|
|
|
|
For `async` `input` ports, you may specify a priority.
|
|
The priority specification is not allowed for other kinds of ports.
|
|
To specify a priority, you write the keyword `priority` and an
|
|
expression that evaluates to a numeric value after the port type.
|
|
As an example, here is a modified version of the `ActiveF32Adder`
|
|
with specified priorities:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A port for carrying an F32 value
|
|
port F32Value(value: F32)
|
|
|
|
@ An active component for adding two F32 values
|
|
@ Uses specified priorities
|
|
active component ActiveF32Adder {
|
|
|
|
@ Input 1 at priority 10
|
|
async input port f32ValueIn1: F32Value priority 10
|
|
|
|
@ Input 2 at priority 20
|
|
async input port f32ValueIn2: F32Value priority 20
|
|
|
|
@ Output
|
|
output port f32ValueOut: F32Value
|
|
|
|
}
|
|
----
|
|
|
|
If an `async` `input` port has no specified priority, then the
|
|
translator uses a default priority.
|
|
The precise meaning of the default priority and of the numeric priorities is
|
|
implementation-specific.
|
|
In general the priorities regulate the order in which elements are dispatched
|
|
from the message queue.
|
|
|
|
==== Queue Full Behavior
|
|
|
|
By default, if an invocation of an `async` `input` port causes
|
|
a message queue to overflow, then a *FSW assertion* fails.
|
|
A FSW assertion is a condition that must be true in order
|
|
for FSW execution to proceed safely.
|
|
The behavior of a FSW assertion failure is configurable in the {cpp}
|
|
implementation of the F Prime framework; typically it causes a FSW
|
|
abort and system reset.
|
|
|
|
Optionally, you can specify the behavior when a message
|
|
received on an `async` `input` port causes a queue overflow.
|
|
There are three possible behaviors:
|
|
|
|
. `assert`: Fail a FSW assertion (the default behavior).
|
|
. `block`: Block the sender until the queue is available.
|
|
. `drop`: Drop the incoming message and proceed.
|
|
. `hook`: Call a user-specified function and proceed.
|
|
|
|
To specify queue full behavior, you write one of the keywords `assert`,
|
|
`block`, `drop`, or `hook` after the port type and after the priority
|
|
(if any).
|
|
As an example, here is the `ActiveF32Adder` updated with explicit
|
|
queue full behavior.
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A port for carrying an F32 value
|
|
port F32Value(value: F32)
|
|
|
|
@ An active component for adding two F32 values
|
|
@ Uses specified priorities
|
|
active component ActiveF32Adder {
|
|
|
|
@ Input 1 at priority 10: Block on queue full
|
|
async input port f32ValueIn1: F32Value priority 10 block
|
|
|
|
@ Input 2: Drop on queue full
|
|
async input port f32ValueIn2: F32Value drop
|
|
|
|
@ Input 3: Call hook function on queue full
|
|
async input port f32ValueIn3: F32Value hook
|
|
|
|
@ Output
|
|
output port f32ValueOut: F32Value
|
|
|
|
}
|
|
----
|
|
|
|
As for priority specifiers, queue full specifiers are allowed only
|
|
for `async` `input` ports.
|
|
|
|
==== Serial Port Instances
|
|
|
|
When writing a port instance, instead of specifying a named port type,
|
|
you may write the keyword `serial`.
|
|
Doing this specifies a *serial port instance*.
|
|
A serial port instance does not specify the type of data that it carries.
|
|
It may be connected to a port of any type.
|
|
Serial data passes through the port; the data may be converted to or from a
|
|
specific type at the other end of the connection.
|
|
|
|
As an example, here is a passive component for taking a stream
|
|
of serial data and splitting it (i.e., repeating it by copy)
|
|
onto several streams:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ Split factor
|
|
constant splitFactor = 10
|
|
|
|
@ Component for splitting a serial data stream
|
|
passive component SerialSplitter {
|
|
|
|
@ Input
|
|
sync input port serialIn: serial
|
|
|
|
@ Output
|
|
output port serialOut: [splitFactor] serial
|
|
|
|
}
|
|
----
|
|
|
|
By using serial ports, you can send several unrelated types
|
|
of data over the same port connection.
|
|
This technique is useful when communicating across
|
|
a network: on each side of the network connection, a single component
|
|
can act as a hub that routs all data to and from components
|
|
on that side.
|
|
This flexibility comes at the cost that you lose the type
|
|
compile-time type checking provided by port connections with named types.
|
|
For more information about serial ports and their use, see
|
|
the https://fprime.jpl.nasa.gov/devel/docs/user-manual/[F Prime User Manual].
|
|
|
|
=== Special Port Instances
|
|
|
|
A *special port instance* is a port instance that has a special
|
|
behavior in F Prime.
|
|
As discussed <<Defining-Components_Port-Instances,above>>,
|
|
when writing a general port instance,
|
|
you specify a port kind, a port type, and possibly other
|
|
information such as array size and priority.
|
|
Writing a special port instance is a bit different.
|
|
In this case you specify a predefined behavior
|
|
provided by the F Prime framework.
|
|
The special port behaviors fall into six groups:
|
|
commands, events, telemetry, parameters, time,
|
|
and data products.
|
|
|
|
==== Command Ports
|
|
|
|
A *command* is an instruction to the spacecraft to perform an action.
|
|
Each component instance _C_ that specifies commands has the following
|
|
high-level behaviors:
|
|
|
|
. At FSW startup time, _C_ registers its commands with a component
|
|
instance called the *command dispatcher*.
|
|
|
|
. During FSW execution, _C_ receives commands from the command
|
|
dispatcher.
|
|
For each command received, _C_ executes the command and
|
|
sends a response back to the command dispatcher.
|
|
|
|
In FPP, the keywords for the special command behaviors are as follows:
|
|
|
|
* `command` `reg`: A port for sending command registration requests.
|
|
* `command` `recv`: A port for receiving commands.
|
|
* `command` `resp`: A port for sending command responses.
|
|
|
|
Collectively, these ports are known as *command ports*.
|
|
To specify a command port, you write one of the keyword pairs
|
|
shown above followed by the keyword `port` and the port name.
|
|
|
|
As an example, here is a passive component `CommandPorts` with each
|
|
of the command ports:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A component for illustrating command ports
|
|
passive component CommandPorts {
|
|
|
|
@ A port for receiving commands
|
|
command recv port cmdIn
|
|
|
|
@ A port for sending command registration requests
|
|
command reg port cmdRegOut
|
|
|
|
@ A port for sending command responses
|
|
command resp port cmdResponseOut
|
|
|
|
}
|
|
----
|
|
|
|
Any component may have at most one of each kind of command
|
|
port.
|
|
If a component receives commands (more on this below),
|
|
then all three ports are required.
|
|
The port names shown in the example above are standard but not
|
|
required; you can use any names you wish.
|
|
|
|
During translation, each command port is converted into
|
|
a typed port instance with a predefined port type, as follows:
|
|
|
|
* `command` `recv` uses the port `Fw.Cmd`
|
|
* `command` `reg` uses the port `Fw.CmdReg`
|
|
* `command` `resp` uses the port `Fw.CmdResponse`
|
|
|
|
The F Prime framework provides definitions for these ports
|
|
in the directory `Fw/Cmd`.
|
|
For checking simple examples, you can use the following
|
|
simplified definitions of these ports:
|
|
|
|
[source,fpp]
|
|
--------
|
|
module Fw {
|
|
port Cmd
|
|
port CmdReg
|
|
port CmdResponse
|
|
}
|
|
--------
|
|
|
|
For example, to check the `CommandPorts` component, you can
|
|
add these lines before the component definition.
|
|
If you don't do this, or something similar, then the component
|
|
definition won't pass through `fpp-check` because of the missing ports.
|
|
(Try it and see.)
|
|
|
|
Note that the port definitions shown above are for conveniently checking
|
|
simple examples only.
|
|
They are not correct for the F Prime framework and will not work
|
|
properly with F Prime {cpp} code generation.
|
|
|
|
For further information about command registration, receipt, and
|
|
response, and implementing command handlers, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/[F Prime User Manual].
|
|
|
|
==== Event Ports
|
|
|
|
An *event* is a report that something happened, for example,
|
|
that a file was successfully uplinked.
|
|
The special event behaviors, and their keywords, are as follows:
|
|
|
|
* `event`: A port for emitting events as serialized bytes.
|
|
* `text` `event`: A port for emitting events as human-readable
|
|
text (usually used for testing and debugging on the ground).
|
|
|
|
Collectively, these ports are known as *event ports*.
|
|
To specify an event port, you write one of the keyword groups
|
|
shown above followed by the keyword `port` and the port name.
|
|
|
|
As an example, here is a passive component `EventPorts` with each
|
|
of the event ports:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A component for illustrating event ports
|
|
passive component EventPorts {
|
|
|
|
@ A port for emitting events
|
|
event port eventOut
|
|
|
|
@ A port for emitting text events
|
|
text event port textEventOut
|
|
|
|
}
|
|
----
|
|
|
|
Any component may have at most one of each kind of event
|
|
port.
|
|
If a component emits events (more on this below),
|
|
then both event ports are required.
|
|
|
|
During translation, each event port is converted into
|
|
a typed port instance with a predefined port type, as follows:
|
|
|
|
* `event` uses the port `Fw.Log`
|
|
* `text` `event` uses the port `Fw.LogText`
|
|
|
|
The name `Log` refers to an event log.
|
|
The F Prime framework provides definitions for these ports
|
|
in the directory `Fw/Log`.
|
|
For checking simple examples, you can use the following
|
|
simplified definitions of these ports:
|
|
|
|
[source,fpp]
|
|
--------
|
|
module Fw {
|
|
port Log
|
|
port LogText
|
|
}
|
|
--------
|
|
|
|
For further information about events in F Prime, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/[F Prime User Manual].
|
|
|
|
==== Telemetry Ports
|
|
|
|
*Telemetry* is data regarding the state of the system.
|
|
A *telemetry port* allows a component to emit telemetry.
|
|
To specify a telemetry port, you write the keyword `telemetry`,
|
|
the keyword `port`, and the port name.
|
|
|
|
As an example, here is a passive component `TelemetryPorts` with
|
|
a telemetry port:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A component for illustrating telemetry ports
|
|
passive component TelemetryPorts {
|
|
|
|
@ A port for emitting telemetry
|
|
telemetry port tlmOut
|
|
|
|
}
|
|
----
|
|
|
|
Any component may have at most one telemetry port.
|
|
If a component emits telemetry (more on this below),
|
|
then a telemetry port is required.
|
|
|
|
During translation, each telemetry port is converted into
|
|
a typed port instance with the predefined port type
|
|
`Fw.Tlm`.
|
|
The F Prime framework provides a definition for this port
|
|
in the directory `Fw/Tlm`.
|
|
For checking simple examples, you can use the following
|
|
simplified definition of this port:
|
|
|
|
[source,fpp]
|
|
--------
|
|
module Fw {
|
|
port Tlm
|
|
}
|
|
--------
|
|
|
|
For further information about telemetry in F Prime, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/[F Prime User Manual].
|
|
|
|
==== Parameter Ports
|
|
|
|
A *parameter* is a configurable constant that may be updated
|
|
from the ground.
|
|
The current parameter values are stored in an F Prime component
|
|
called the *parameter database*.
|
|
|
|
The special parameter behaviors, and their keywords, are as follows:
|
|
|
|
* `param` `get`: A port for getting the current value of a parameter
|
|
from the parameter database.
|
|
* `param` `set`: A port for setting the current value of a parameter
|
|
in the parameter database.
|
|
|
|
Collectively, these ports are known as *parameter ports*.
|
|
To specify a parameter port, you write one of the keyword groups
|
|
shown above followed by the keyword `port` and the port name.
|
|
|
|
As an example, here is a passive component `ParamPorts` with each
|
|
of the parameter ports:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A component for illustrating parameter ports
|
|
passive component ParamPorts {
|
|
|
|
@ A port for getting parameter values
|
|
param get port prmGetOut
|
|
|
|
@ A port for setting parameter values
|
|
param set port prmSetOut
|
|
|
|
}
|
|
----
|
|
|
|
Any component may have at most one of each kind of parameter
|
|
port.
|
|
If a component has parameters (more on this below),
|
|
then both parameter ports are required.
|
|
|
|
During translation, each parameter port is converted into
|
|
a typed port instance with a predefined port type, as follows:
|
|
|
|
* `param` `get` uses the port `Fw.PrmGet`
|
|
* `param` `set` uses the port `Fw.PrmSet`
|
|
|
|
The F Prime framework provides definitions for these ports
|
|
in the directory `Fw/Prm`.
|
|
For checking simple examples, you can use the following
|
|
simplified definitions of these ports:
|
|
|
|
[source,fpp]
|
|
--------
|
|
module Fw {
|
|
port PrmGet
|
|
port PrmSet
|
|
}
|
|
--------
|
|
|
|
For further information about parameters in F Prime, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/[F Prime User Manual].
|
|
|
|
==== Time Get Ports
|
|
|
|
A *time get port* allows a component to get the system time from a
|
|
time component.
|
|
To specify a time get port, you write the keywords `time` `get`,
|
|
the keyword `port`, and the port name.
|
|
|
|
As an example, here is a passive component `TimeGetPorts` with
|
|
a time get port:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A component for illustrating time get ports
|
|
passive component TimeGetPorts {
|
|
|
|
@ A port for getting the time
|
|
time get port timeGetOut
|
|
|
|
}
|
|
----
|
|
|
|
Any component may have at most one time get port.
|
|
If a component emits events or telemetry (more on this below),
|
|
then a time get port is required, so that the events
|
|
and telemetry points can be time stamped.
|
|
|
|
During translation, each time get port is converted into
|
|
a typed port instance with the predefined port type
|
|
`Fw.Time`.
|
|
The F Prime framework provides a definition for this port
|
|
in the directory `Fw/Time`.
|
|
For checking simple examples, you can use the following
|
|
simplified definition of this port:
|
|
|
|
[source,fpp]
|
|
--------
|
|
module Fw {
|
|
port Time
|
|
}
|
|
--------
|
|
|
|
For further information about time in F Prime, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/[F Prime User Manual].
|
|
|
|
==== Data Product Ports
|
|
|
|
A *data product* is a collection of data that can be stored to an
|
|
onboard file system, given a priority, and downlinked in priority
|
|
order.
|
|
For example, a data product may be an image or a unit of
|
|
science data.
|
|
Data products are stored in *containers* that contain
|
|
*records*.
|
|
A record is a unit of data.
|
|
A container stores (1) a header that describes the container
|
|
and (2) a list of records.
|
|
|
|
The special data product behaviors, and their keywords, are as follows:
|
|
|
|
* `product` `get`: A port for synchronously requesting a
|
|
memory buffer to store a container.
|
|
|
|
* `product` `request`: A port for asynchronously requesting
|
|
a buffer to store a container.
|
|
|
|
* `product` `recv`: A port for receiving a response to an
|
|
asynchronous buffer request.
|
|
|
|
* `product` `send`: A port for sending a buffer that stores
|
|
a container, after the container has been filled with data.
|
|
|
|
Collectively, these ports are known as *data product ports*.
|
|
To specify a data product port, you write one of the keyword groups
|
|
shown above followed by the keyword `port` and the port name.
|
|
To specify a product receive port, you must first write
|
|
`async`, `sync` or `guarded` to specify whether the input port
|
|
is asynchronous, synchronous, or guarded, as described in
|
|
the section on <<Defining-Components_Port-Instances_Basic-Port-Instances,
|
|
basic port instances>>.
|
|
When specifying an `async` product receive port, you may
|
|
specify a <<Defining-Components_Port-Instances_Priority,priority behavior>>
|
|
or <<Defining-Components_Port-Instances_Queue-Full-Behavior,queue full behavior>>.
|
|
|
|
As an example, here is a passive component `DataProductPorts` with each
|
|
of the data product ports:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A component for illustrating data product ports
|
|
active component DataProductPorts {
|
|
|
|
@ A port for getting a data product container
|
|
product get port productGetOut
|
|
|
|
@ A port for requesting a data product container
|
|
product request port productRequestOut
|
|
|
|
@ An async port for receiving a requested data product container
|
|
async product recv port productRecvIn priority 10 assert
|
|
|
|
@ A port for sending a filled data product container
|
|
product send port productSendOut
|
|
|
|
}
|
|
----
|
|
|
|
Any component may have at most one of each kind of data product
|
|
port.
|
|
If a component defines data products (more on this below),
|
|
then there must be (1) a product get port or a product request port
|
|
and (2) a product send port.
|
|
If there is a product request port, then there must be a product
|
|
receive port.
|
|
|
|
During translation, each data product port is converted into
|
|
a typed port instance with a predefined port type, as follows:
|
|
|
|
* `product` `get` uses the port `Fw.DpGet`
|
|
* `product` `request` uses the port `Fw.DpRequest`
|
|
* `product` `recv` uses the port `Fw.DpResponse`
|
|
* `product` `send` uses the port `Fw.DpSend`
|
|
|
|
The F Prime framework provides definitions for these ports
|
|
in the directory `Fw/Dp`.
|
|
For checking simple examples, you can use the following
|
|
simplified definitions of these ports:
|
|
|
|
[source,fpp]
|
|
--------
|
|
module Fw {
|
|
port DpGet
|
|
port DpRequest
|
|
port DpResponse
|
|
port DpSend
|
|
}
|
|
--------
|
|
|
|
For further information about data products in F Prime, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/framework/data-products/[F
|
|
Prime design documentation].
|
|
|
|
|
|
=== Internal Ports
|
|
|
|
An *internal port* is a port that a component can use to send a
|
|
message to itself.
|
|
In the ordinary case, when a component sends a message, it invokes an
|
|
output port that is connected to an async input port.
|
|
When the output port and input port reside in the same component,
|
|
it is simpler to use an internal port.
|
|
|
|
As an example, suppose we have a component
|
|
that needs to send a message to itself.
|
|
We could construct such a component in the following way:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A data type T
|
|
type T
|
|
|
|
@ A port for sending data of type T
|
|
port P(t: T)
|
|
|
|
@ A component that sends data to itself on an async input port
|
|
active component ExternalSelfMessage {
|
|
|
|
@ A port for sending data of type T
|
|
async input port pIn: P
|
|
|
|
@ A port for receiving data of type T
|
|
output port pOut: P
|
|
|
|
}
|
|
----
|
|
|
|
This works, but if the only user of `pIn` is
|
|
`ExternalSelfMessage`, it is cumbersome.
|
|
We need to declare two ports and connect them.
|
|
Instead, we can use an internal port, like this:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A data type T
|
|
type T
|
|
|
|
@ A component that sends data to itself on an internal port
|
|
active component InternalSelfMessage {
|
|
|
|
@ An internal port for sending data of type T
|
|
internal port pInternal(t: T)
|
|
|
|
}
|
|
----
|
|
|
|
When the implementation of `InternalSelfMessage` invokes
|
|
the port `pInternal`, a message goes on its queue.
|
|
This corresponds to the behavior of `pOut` in
|
|
`ExternalSelfMessage`.
|
|
Later, when the framework dispatches the message, it
|
|
calls a handler function associated with the port.
|
|
This corresponds to the behavior of `pIn` in
|
|
`ExternalSelfMessage`.
|
|
So an internal port is like two ports (an output port
|
|
and an async input port) fused into one.
|
|
|
|
When writing an internal port, you do not use a named
|
|
port definition.
|
|
Instead, you provide the formal parameters directly.
|
|
Notice that when defining `ExternalSelfMessage` we
|
|
defined and used the port `P`, but when defining
|
|
`InternalSelfMessage` we did not.
|
|
The formal parameters of an internal port work in the same way
|
|
as for a <<Defining-Ports_Formal-Parameters,port definition>>,
|
|
except that none of the parameters may be a
|
|
<<Defining-Ports_Reference-Parameters,reference parameter>>.
|
|
|
|
When specifying an internal port, you may specify
|
|
<<Defining-Components_Port-Instances_Priority,priority>> and
|
|
<<Defining-Components_Port-Instances_Queue-Full-Behavior,queue full behavior>>
|
|
as for an async input port.
|
|
For example, we can add priority and queue full behavior
|
|
to `pInternal` as follows:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A data type T
|
|
type T
|
|
|
|
@ A component that sends data to itself on an internal port,
|
|
@ with priority and queue full behavior
|
|
active component InternalSelfMessage {
|
|
|
|
@ An internal port for sending data of type T
|
|
internal port pInternal(t: T) priority 10 drop
|
|
|
|
}
|
|
----
|
|
|
|
Internal ports generate async input, so they make sense
|
|
only for `active` and `queued` components.
|
|
As an example, consider the following component
|
|
definition:
|
|
|
|
[source,fpp]
|
|
--------
|
|
type T
|
|
|
|
passive component PassiveInternalPort {
|
|
|
|
# Internal ports don't make sense for passive components
|
|
internal port pInternal(t: T)
|
|
|
|
}
|
|
--------
|
|
|
|
What do you think will happen if you run `fpp-check`
|
|
on this code?
|
|
Try it and see.
|
|
|
|
=== Commands
|
|
|
|
When defining an F Prime component, you may specify one or more commands.
|
|
When you are operating the FSW, you use the F Prime Ground Data System
|
|
or another ground data system to send commands to the FSW.
|
|
On receipt of a command _C_, a Command Dispatcher component instance
|
|
dispatches _C_ to the component instance where that command is implemented.
|
|
The command is handled in a {cpp} command handler that you write
|
|
as part of the component implementation.
|
|
|
|
For complete information about F Prime command dispatch and
|
|
handling, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/[F Prime User Manual].
|
|
Here we concentrate on how to specify commands in FPP.
|
|
|
|
==== Basic Commands
|
|
|
|
The simplest command consists of a kind followed by the keyword
|
|
`command` and a name.
|
|
The kind is one of the following:
|
|
|
|
* `async`: The command arrives on a message queue, to
|
|
be dispatched on this component's thread (if this component is active)
|
|
or on the thread of a port invocation (if this component is queued).
|
|
|
|
* `sync`: The command invokes a handler defined in this component,
|
|
and run on the thread of the caller.
|
|
|
|
* `guarded`: Similar to sync input, but the handler is
|
|
guarded by a mutual exclusion lock.
|
|
|
|
Notice that the kinds of commands are similar to the kinds of
|
|
<<Defining-Components_Port-Instances_Basic-Port-Instances,
|
|
input ports>>.
|
|
The name is the name of the command.
|
|
|
|
As an example, here is an active component called `Action`
|
|
with two commands: an async command `START` and a sync
|
|
command `STOP`.
|
|
|
|
[source,fpp]
|
|
----
|
|
@ An active component for performing an action
|
|
active component Action {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command input
|
|
command recv port cmdIn
|
|
|
|
@ Command registration
|
|
command reg port cmdRegOut
|
|
|
|
@ Command response
|
|
command resp port cmdResponseOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Commands
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Start the action
|
|
async command START
|
|
|
|
@ Stop the action
|
|
sync command STOP
|
|
|
|
}
|
|
----
|
|
|
|
Command `START` is declared `async`.
|
|
That means that when a `START` command is dispatched
|
|
to an instance of this component, it arrives on a queue.
|
|
Later, the F Prime framework takes the message off the queue
|
|
and calls the corresponding handler on the thread
|
|
of the component.
|
|
|
|
Command `STOP` is declared `sync`.
|
|
That means that the command runs immediately on the
|
|
thread of the invoking component (for example,
|
|
a command dispatcher component).
|
|
Because the command runs immediately, its handler
|
|
should be very short.
|
|
For example, it could set a stop flag and then exit.
|
|
|
|
Notice that we defined the three
|
|
<<Defining-Components_Special-Port-Instances_Command-Ports,
|
|
command ports>>
|
|
for this component.
|
|
All three ports are required for any component that has commands.
|
|
As an example, try deleting one or more of the command ports from the
|
|
code above and running the result through `fpp-check`.
|
|
|
|
`async` commands require a message queue, so
|
|
they are allowed only for active and queued
|
|
components.
|
|
As an example, try making the `Action` component passive and
|
|
running the result through `fpp-check`.
|
|
|
|
==== Formal Parameters
|
|
|
|
When specifying a command, you may specify one or more
|
|
formal parameters.
|
|
The parameters are bound to arguments when the command
|
|
is sent to the spacecraft.
|
|
Different uses of the same command can have different
|
|
argument values.
|
|
|
|
The formal parameters of a command are the same
|
|
as for a <<Defining-Ports_Formal-Parameters,port definition>>, except
|
|
for the following:
|
|
|
|
. None of the parameters may be a
|
|
<<Defining-Ports_Reference-Parameters,reference parameter>>.
|
|
|
|
. Each parameter must have a <<Dictionary-Definitions,displayable type>>, i.e.,
|
|
a
|
|
type that the F Prime ground data system knows how to display.
|
|
For example, the type may not be an
|
|
<<Defining-Types_Abstract-Type-Definitions,abstract type>>.
|
|
Nor may it be an array or struct type that has an abstract type
|
|
as a member type.
|
|
|
|
As an example, here is a `Switch` component that has
|
|
two states, `ON` and `OFF`.
|
|
The component has a `SET_STATE` command for
|
|
setting the state.
|
|
The command has a single argument `state`
|
|
that specifies the new state.
|
|
|
|
[source,fpp]
|
|
----
|
|
@ The state enumeration
|
|
enum State {
|
|
OFF @< The off state
|
|
ON @< The on state
|
|
}
|
|
|
|
@ A switch with on and off state
|
|
active component Switch {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command input
|
|
command recv port cmdIn
|
|
|
|
@ Command registration
|
|
command reg port cmdRegOut
|
|
|
|
@ Command response
|
|
command resp port cmdResponseOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Commands
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Set the state
|
|
async command SET_STATE(
|
|
$state: State @< The new state
|
|
)
|
|
|
|
}
|
|
----
|
|
|
|
In this example, the enum type `State` is a displayable type because
|
|
its definition is known to FPP.
|
|
Try replacing the enum definition with the
|
|
abstract type definition `type S` and see what happens when
|
|
you run the model through `fpp-check`.
|
|
Remember to <<Defining-Components_Special-Port-Instances_Command-Ports,provide
|
|
stubs for the special command ports>> that are required by
|
|
`fpp-check`.
|
|
|
|
==== Opcodes
|
|
|
|
Every command in an F Prime FSW application has an *opcode*.
|
|
The opcode is a number that uniquely identifies the command.
|
|
The F Prime framework uses the opcode when dispatching commands
|
|
because it is a more compact identifier than the name.
|
|
The name is mainly for human interaction on the ground.
|
|
|
|
The opcodes associated with each component _C_
|
|
are relative to the component.
|
|
Typically the opcodes start at zero: that is, the
|
|
opcodes are 0, 1, 2, etc.
|
|
When constructing an instance _I_ of component _C_,
|
|
the framework adds a base opcode for _I_ to each relative opcode
|
|
associated with _C_ to form
|
|
the global opcodes associated with _I_.
|
|
That way different instances of _C_ can have different opcodes
|
|
for the same commands defined in _C_.
|
|
We will have more to say about base and relative opcodes
|
|
when we describe component instances and topologies.
|
|
|
|
If you specify a command _c_ with no explicit opcode, as in the examples
|
|
shown in the previous sections, then FPP assigns a default opcode
|
|
to _c_.
|
|
The default opcode for the first command in a component is zero.
|
|
Otherwise the default opcode for any command is one more than
|
|
the opcode of the previous command.
|
|
|
|
It is usually convenient to rely on the default opcodes.
|
|
However, you may wish to specify one or more opcodes explicitly.
|
|
To do this, you write the keyword `opcode` followed
|
|
by a numeric expression after the command name and after the
|
|
formal parameters, if any.
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ Component for illustrating command opcodes
|
|
active component CommandOpcodes {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command input
|
|
command recv port cmdIn
|
|
|
|
@ Command registration
|
|
command reg port cmdRegOut
|
|
|
|
@ Command response
|
|
command resp port cmdResponseOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Commands
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ This command has default opcode 0x0
|
|
async command COMMAND_1
|
|
|
|
@ This command has explicit opcode 0x10
|
|
async command COMMAND_2(a: F32, b: U32) opcode 0x10
|
|
|
|
@ This command has default opcode 0x11
|
|
sync command COMMAND_3
|
|
|
|
}
|
|
----
|
|
|
|
Within a component, the command opcodes must be unique.
|
|
For example, this component is incorrect because
|
|
the opcode zero appears twice:
|
|
|
|
[source,fpp]
|
|
--------
|
|
@ Component for illustrating a duplicate opcode
|
|
active component DuplicateOpcode {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command input
|
|
command recv port cmdIn
|
|
|
|
@ Command registration
|
|
command reg port cmdRegOut
|
|
|
|
@ Command response
|
|
command resp port cmdResponseOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Commands
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ This command has opcode 0x0
|
|
async command COMMAND_1
|
|
|
|
@ Oops! This command also has opcode 0x0
|
|
async command COMMAND_2 opcode 0x0
|
|
|
|
}
|
|
--------
|
|
|
|
==== Priority and Queue Full Behavior
|
|
|
|
When specifying an async command, you may specify
|
|
<<Defining-Components_Port-Instances_Priority,priority>> and
|
|
<<Defining-Components_Port-Instances_Queue-Full-Behavior,queue full behavior>>
|
|
as for an async input port.
|
|
You put the priority and queue full information after the command name
|
|
and after the formal parameters and opcode, if any.
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A component for illustrating priority and queue full behavior for async
|
|
@ commands
|
|
active component PriorityQueueFull {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command input
|
|
command recv port cmdIn
|
|
|
|
@ Command registration
|
|
command reg port cmdRegOut
|
|
|
|
@ Command response
|
|
command resp port cmdResponseOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Commands
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command with priority
|
|
async command COMMAND_1 priority 10
|
|
|
|
@ Command with formal parameters and priority
|
|
async command COMMAND_2(a: U32, b: F32) priority 20
|
|
|
|
@ Command with formal parameters, opcode, priority, and queue full behavior
|
|
async command COMMAND_3(a: string) opcode 0x10 priority 30 drop
|
|
|
|
}
|
|
----
|
|
|
|
Priority and queue full behavior are allowed only for
|
|
`async` commands.
|
|
Try changing one of the commands in the previous example
|
|
to `sync` and see what `fpp-check` has to say about it.
|
|
|
|
=== Events
|
|
|
|
When defining an F Prime component, you may specify one or more events.
|
|
The F Prime framework converts each event into a {cpp}
|
|
function that you can call from the component implementation.
|
|
Calling the function emits a serialized event report that
|
|
you can store in an on-board file system or send to the ground.
|
|
|
|
For complete information about F Prime event
|
|
handling, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/[F Prime User Manual].
|
|
Here we concentrate on how to specify events in FPP.
|
|
|
|
==== Basic Events
|
|
|
|
The simplest event consists of the keyword `event`, a name, a severity,
|
|
and a format string.
|
|
The name is the name of the event.
|
|
A severity is the keyword `severity` and one of the following:
|
|
|
|
* `activity` `high`: Spacecraft activity of greater importance.
|
|
|
|
* `activity` `low`: Spacecraft activity of lesser importance.
|
|
|
|
* `command`: An event related to commanding.
|
|
Primarily used by the command dispatcher.
|
|
|
|
* `diagnostic`: An event relating to system diagnosis
|
|
and debugging.
|
|
|
|
* `fatal`: An event that causes the system to abort.
|
|
|
|
* `warning` `high`: A warning of greater importance.
|
|
|
|
* `warning` `low`: A warning of lesser importance.
|
|
|
|
A format is the keyword `format` and a literal string for
|
|
use in a formatted real-time display or event log.
|
|
|
|
As an example, here is an active component called `BasicEvents`
|
|
with a few basic events.
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A component for illustrating basic events
|
|
passive component BasicEvents {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Event port
|
|
event port eventOut
|
|
|
|
@ Text event port
|
|
text event port textEventOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Events
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Activity low event
|
|
event Event1 severity activity low format "Event 1 occurred"
|
|
|
|
@ Warning low event
|
|
event Event2 severity warning low format "Event 2 occurred"
|
|
|
|
@ Warning high event
|
|
event Event3 severity warning high format "Event 3 occurred"
|
|
|
|
}
|
|
----
|
|
|
|
Notice that we defined the two
|
|
<<Defining-Components_Special-Port-Instances_Event-Ports,
|
|
event ports>>
|
|
and a
|
|
<<Defining-Components_Special-Port-Instances_Time-Get-Ports,
|
|
time get port>>
|
|
for this component.
|
|
All three ports are required for any component that has events.
|
|
As an example, try deleting one or more of these ports from the
|
|
code above and running the result through `fpp-check`.
|
|
|
|
==== Formal Parameters
|
|
|
|
When specifying an event, you may specify one or more
|
|
formal parameters.
|
|
The parameters are bound to arguments when the component
|
|
instance emits the event.
|
|
The argument values appear in the formatted text
|
|
that describes the event.
|
|
|
|
You specify the formal parameters of an event in the same
|
|
way as for a <<Defining-Components_Commands_Formal-Parameters,command
|
|
specifier>>.
|
|
For each formal parameter, there must be a corresponding
|
|
replacement field in the format string.
|
|
The replacement fields for event format strings are the same as for
|
|
format strings in
|
|
<<Defining-Types_Array-Type-Definitions_Format-Strings,
|
|
type definitions>>.
|
|
The replacement fields in the format string match the event
|
|
parameters, one for one and in the same order.
|
|
|
|
As an example, here is a component with two events,
|
|
each of which has formal parameters.
|
|
Notice how the replacement fields in the event format
|
|
strings correspond to the formal parameters.
|
|
|
|
[source,fpp]
|
|
----
|
|
@ An enumeration of cases
|
|
enum Case { A, B, C }
|
|
|
|
@ An array of 3 F64 values
|
|
array F64x3 = [3] F64
|
|
|
|
@ A component for illustrating event formal parameters
|
|
passive component EventParameters {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Event port
|
|
event port eventOut
|
|
|
|
@ Text event port
|
|
text event port textEventOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Events
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Event 1
|
|
@ Sample output: "Event 1 occurred with argument 42"
|
|
event Event1(
|
|
arg1: U32 @< Argument 1
|
|
) \
|
|
severity activity high \
|
|
format "Event 1 occurred with argument {}"
|
|
|
|
@ Event 2
|
|
@ Sample output: "Saw value [ 0.001, 0.002, 0.003 ] for case A"
|
|
event Event2(
|
|
case: Case @< The case
|
|
value: F64x3 @< The value
|
|
) \
|
|
severity warning low \
|
|
format "Saw value {} for case {}"
|
|
|
|
}
|
|
----
|
|
|
|
==== Identifiers
|
|
|
|
Every event in an F Prime FSW application has a unique
|
|
numeric identifier.
|
|
As for
|
|
<<Defining-Components_Commands_Opcodes,command opcodes>>,
|
|
the event identifiers for a component are specified
|
|
relative to the component, usually starting from
|
|
zero and counting up by one.
|
|
If you omit the identifier, then
|
|
FPP assigns a default identifier: zero for the first
|
|
event in the component; otherwise one more than the
|
|
identifier of the previous event.
|
|
|
|
If you wish, you may explicitly specify one or more event
|
|
identifiers.
|
|
To do this, you write the keyword `id` followed
|
|
by a numeric expression immediately before the keyword `format`.
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ Component for illustrating event identifiers
|
|
passive component EventIdentifiers {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Event port
|
|
event port eventOut
|
|
|
|
@ Text event port
|
|
text event port textEventOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Events
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Event 1
|
|
@ Its identifier is 0x00
|
|
event Event1 severity activity low \
|
|
id 0x10 \
|
|
format "Event 1 occurred"
|
|
|
|
@ Event 2
|
|
@ Its identifier is 0x10
|
|
event Event2(
|
|
count: U32 @< The count
|
|
) \
|
|
severity activity high \
|
|
id 0x11 \
|
|
format "The count is {}"
|
|
|
|
@ Event 3
|
|
@ Its identifier is 0x11
|
|
event Event3 severity activity high \
|
|
format "Event 3 occurred"
|
|
|
|
}
|
|
----
|
|
|
|
Within a component, the event identifiers must be unique.
|
|
|
|
==== Throttling
|
|
|
|
Sometimes it is necessary to throttle events, to ensure that
|
|
they do not flood the system.
|
|
For example, suppose that the FSW requests some resource _R_
|
|
at a rate _r_ of several times per second.
|
|
Suppose further that if _R_ is unavailable, then the FSW
|
|
emits a warning event.
|
|
In this case, we typically do not want the FSW to emit an unbounded number
|
|
of warnings at rate _r_; instead, we want it to emit a single warning
|
|
or a few warnings.
|
|
|
|
To achieve this behavior, you can write the keyword `throttle` and a
|
|
numeric expression after the format string.
|
|
The expression must evaluate to a constant value _n_, where _n_ is a number
|
|
greater than zero.
|
|
_n_ is called the *maximum throttle count*.
|
|
After an instance of the component has emitted the event _n_ times, it will
|
|
stop emitting the event.
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ Component for illustrating event throttling
|
|
passive component EventThrottling {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Event port
|
|
event port eventOut
|
|
|
|
@ Text event port
|
|
text event port textEventOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Events
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Event 1
|
|
event Event1 severity warning high \
|
|
format "Event 1 occurred" \
|
|
throttle 10
|
|
|
|
}
|
|
----
|
|
|
|
In this example, event `Event1` will be throttled after the component
|
|
instance has emitted it ten times.
|
|
|
|
Once an event is throttled, the component
|
|
instance will no longer emit the event until the throttling is reset.
|
|
There are two ways to reset the throttling of an event: manually and
|
|
automatically.
|
|
|
|
*Manual reset:*
|
|
To manually reset throttling for events,
|
|
you can use a <<Defining-Components_Commands,command>>.
|
|
For example, you can define a command `CLEAR_EVENT_THROTTLE`
|
|
that resets throttling for all throttled events.
|
|
You can implement this command by calling functions
|
|
that reset the throttle counts to zero.
|
|
The auto-generated {cpp} code provides one such function for
|
|
each event with throttling.
|
|
For details, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/[F Prime User Manual].
|
|
|
|
*Automatic reset:*
|
|
You can also specify in FPP that the throttling for an event should be reset
|
|
automatically after a specified amount of time.
|
|
To do this, you write `every` and a time interval after the maximum throttle
|
|
count.
|
|
The time interval is a <<Defining-Constants_Expressions_Struct-Values,struct
|
|
value expression>> with two members: `seconds` and `useconds`.
|
|
`useconds` is short for "`microseconds.`"
|
|
Each member must evaluate to an integer value.
|
|
Either or both of the members can be omitted; an omitted member evaluates to
|
|
zero.
|
|
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ Component for illustrating event throttling with timeouts
|
|
passive component EventThrottlingWithTimeout {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Event port
|
|
event port eventOut
|
|
|
|
@ Text event port
|
|
text event port textEventOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Events
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Event 1
|
|
event Event1 severity warning high \
|
|
format "Event 1 occurred" \
|
|
throttle 10 every { seconds = 2 }
|
|
|
|
@ Event 2
|
|
event Event2 severity warning high \
|
|
format "Event 2 occurred" \
|
|
throttle 10 every { seconds = 2, useconds = 500000 }
|
|
|
|
@ Event 3
|
|
event Event3 severity warning high \
|
|
format "Event 3 occurred" \
|
|
throttle 10 every { useconds = 500000 }
|
|
|
|
}
|
|
----
|
|
|
|
In this example, an instance of `EventThrottlingWithTimeout` will emit events
|
|
at the following rates:
|
|
|
|
* `Event1`: Up to 10 events every 2 seconds.
|
|
|
|
* `Event2`: Up to 10 events every 2.5 seconds.
|
|
|
|
* `Event3`: Up to 10 events every 0.5 seconds.
|
|
|
|
=== Telemetry
|
|
|
|
When defining an F Prime component, you may specify one or more
|
|
*telemetry channels*.
|
|
A telemetry channel consists of a data type and an identifier.
|
|
The F Prime framework converts each telemetry into a {cpp}
|
|
function that you can call from the component implementation.
|
|
Calling the function emits a value on the channel.
|
|
Each emitted value is called a
|
|
*telemetry point*.
|
|
You can store the telemetry points in an on-board file system
|
|
or send them the ground.
|
|
|
|
For complete information about F Prime telemetry
|
|
handling, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/[F Prime User Manual].
|
|
Here we concentrate on how to specify telemetry channels in FPP.
|
|
|
|
==== Basic Telemetry
|
|
|
|
The simplest telemetry channel consists of the keyword `telemetry`,
|
|
a name, and a data type.
|
|
The name is the name of the channel.
|
|
The data type is the type of data carried on the channel.
|
|
The data type must be a
|
|
<<Defining-Components_Commands_Formal-Parameters,displayable type>>.
|
|
|
|
As an example, here is an active component called `BasicTelemetry`
|
|
with a few basic events.
|
|
|
|
[source,fpp]
|
|
----
|
|
@ An array of 3 F64 values
|
|
array F64x3 = [3] F64
|
|
|
|
@ A component for illustrating basic telemetry channels
|
|
passive component BasicTelemetry {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Telemetry port
|
|
telemetry port tlmOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Telemetry
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Telemetry channel 1
|
|
telemetry Channel1: U32
|
|
|
|
@ Telemetry channel 2
|
|
telemetry Channel2: F64
|
|
|
|
@ Telemetry channel 3
|
|
telemetry Channel3: F64x3
|
|
|
|
}
|
|
----
|
|
|
|
Notice that we defined a
|
|
<<Defining-Components_Special-Port-Instances_Telemetry-Ports,
|
|
telemetry port>>
|
|
and a
|
|
<<Defining-Components_Special-Port-Instances_Time-Get-Ports,
|
|
time get port>>
|
|
for this component.
|
|
Both ports are required for any component that has telemetry.
|
|
|
|
==== Identifiers
|
|
|
|
Every telemetry channel in an F Prime FSW application has a unique
|
|
numeric identifier.
|
|
As for
|
|
<<Defining-Components_Commands_Opcodes,command opcodes>>
|
|
and
|
|
<<Defining-Components_Events_Identifiers,event identifiers>>,
|
|
the telemetry channel identifiers for a component are specified
|
|
relative to the component, usually starting from
|
|
zero and counting up by one.
|
|
If you omit the identifier, then
|
|
FPP assigns a default identifier: zero for the first
|
|
event in the component; otherwise one more than the
|
|
identifier of the previous channel.
|
|
|
|
If you wish, you may explicitly specify one or more
|
|
telemetry channel identifiers.
|
|
To do this, you write the keyword `id` followed
|
|
by a numeric expression immediately after the data type.
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ An array of 3 F64 values
|
|
array F64x3 = [3] F64
|
|
|
|
@ Component for illustrating telemetry channel identifiers
|
|
passive component TlmIdentifiers {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Telemetry port
|
|
telemetry port tlmOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Telemetry
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Telemetry channel 1
|
|
@ Its implied identifier is 0x00
|
|
telemetry Channel1: U32
|
|
|
|
@ Telemetry channel 2
|
|
@ Its identifier is 0x10
|
|
telemetry Channel2: F64 id 0x10
|
|
|
|
@ Telemetry channel 3
|
|
@ Its implied identifier is 0x11
|
|
telemetry Channel3: F64x3
|
|
|
|
}
|
|
----
|
|
|
|
Within a component, the telemetry channel identifiers must be unique.
|
|
|
|
==== Update Frequency
|
|
|
|
You can specify how often the telemetry is emitted on a channel _C_.
|
|
There are two possibilities:
|
|
|
|
* `always`: Emit a telemetry point on _C_
|
|
whenever the component implementation calls the
|
|
auto-generated function _F_ that emits telemetry on _C_.
|
|
|
|
* `on` `change`: Emit a telemetry point whenever
|
|
(1) the implementation calls _F_ and (2) either (a)
|
|
_F_ has not been called before or
|
|
(b) the last time that _F_ was called, the argument
|
|
to _F_ had a different value.
|
|
|
|
Emitting telemetry on change can reduce unnecessary
|
|
activity in the system.
|
|
For example, suppose a telemetry channel _C_ counts
|
|
the number of times that some event _E_ occurs
|
|
in a periodic task,
|
|
and suppose that _E_ does not occur on every cycle.
|
|
If you declare channel _C_ `on` `change`, then your implementation
|
|
can call the telemetry emit function for _C_ on every
|
|
cycle, and telemetry will be emitted only when
|
|
_E_ occurs.
|
|
|
|
To specify an update frequency, you write the keyword `update`
|
|
and one of the frequency selectors shown above.
|
|
The update specifier goes after
|
|
the type name and after the channel identifier, if any.
|
|
If you don't specify an update frequency, then the default
|
|
value is `always`.
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ An array of 3 F64 values
|
|
array F64x3 = [3] F64
|
|
|
|
@ Component for illustrating telemetry channel update specifiers
|
|
passive component TlmUpdate {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Telemetry port
|
|
telemetry port tlmOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Telemetry
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Telemetry channel 1
|
|
@ Always emitted
|
|
telemetry Channel1: U32
|
|
|
|
@ Telemetry channel 2
|
|
@ Emitted on change
|
|
telemetry Channel2: F64 id 0x10 update on change
|
|
|
|
@ Telemetry channel 3
|
|
@ Always emitted
|
|
telemetry Channel3: F64x3 update always
|
|
|
|
}
|
|
----
|
|
|
|
==== Format Strings
|
|
|
|
You may specify how a telemetry channel is formatted in the
|
|
ground display.
|
|
To do this, you write the keyword `format` and a format string
|
|
with one
|
|
<<Defining-Types_Array-Type-Definitions_Format-Strings,
|
|
replacement field>>.
|
|
The replacement field must match the type of the telemetry
|
|
channel.
|
|
The format specifier comes after the type name, after the
|
|
channel identifier, and after the update specifier.
|
|
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ Component for illustrating telemetry channel format specifiers
|
|
passive component TlmFormat {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Telemetry port
|
|
telemetry port tlmOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Telemetry
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Telemetry channel 1
|
|
telemetry Channel1: U32 format "{x}"
|
|
|
|
@ Telemetry channel 2
|
|
telemetry Channel2: F64 id 0x10 \
|
|
update on change \
|
|
format "{.3f}"
|
|
|
|
@ Telemetry channel 3
|
|
telemetry Channel3: F64\
|
|
update always \
|
|
format "{e}"
|
|
|
|
}
|
|
----
|
|
|
|
==== Limits
|
|
|
|
You may specify *limits*, or bounds, on the expected values
|
|
carried on a telemetry channel.
|
|
There are two kinds of limits: `low` (meaning that the
|
|
values on the channel should stay above the limit) and `high`
|
|
(meaning that the values should stay below the limit).
|
|
Within each kind, there are three levels of severity:
|
|
|
|
* `yellow`: Crossing the limit is of low concern.
|
|
|
|
* `orange`: Crossing the limit is of medium concern.
|
|
|
|
* `red`: Crossing the limit is of high concern.
|
|
|
|
The F Prime ground data system displays an appropriate warning
|
|
when a telemetry point crosses a limit.
|
|
|
|
The limit specifiers come after the type name, identifier,
|
|
update specifier, and format string.
|
|
You specify the low limits (if any) first, and then the high limits.
|
|
For the low limits, you write the keyword `low` followed by a
|
|
list of limits in curly braces `{ ... }`.
|
|
For the high limits, you do the same thing but use the keyword
|
|
`high`.
|
|
Each limit is a severity keyword followed by a numeric expression.
|
|
Here are some examples:
|
|
|
|
|
|
[source,fpp]
|
|
----
|
|
@ Component for illustrating telemetry channel limits
|
|
passive component TlmLimits {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Telemetry port
|
|
telemetry port tlmOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Telemetry
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Telemetry channel 1
|
|
telemetry Channel1: U32 \
|
|
low { red 0, orange 1, yellow 2 }
|
|
|
|
@ Telemetry channel 2
|
|
telemetry Channel2: F64 id 0x10 \
|
|
update on change \
|
|
format "{.3f}" \
|
|
low { red -3, orange -2, yellow -1 } \
|
|
high { red 3, orange 2, yellow 1 }
|
|
|
|
@ Telemetry channel 3
|
|
telemetry Channel3: F64 \
|
|
update always \
|
|
format "{e}" \
|
|
high { red 3, orange 2, yellow 1 }
|
|
|
|
}
|
|
----
|
|
|
|
Each limit must be a numeric value.
|
|
The type of the telemetry channel must be (1) a numeric
|
|
type; or (2) an array or struct type each of whose members
|
|
has a numeric type; or (3) an array or struct type
|
|
each of whose members satisfies condition (1) or
|
|
condition (2).
|
|
|
|
=== Parameters
|
|
|
|
When defining an F Prime component, you may specify one or more
|
|
*parameters*.
|
|
A parameter is a typed constant value that you can update
|
|
by command.
|
|
For example, it could be a configuration constant
|
|
for a hardware device or a software algorithm.
|
|
|
|
F Prime has special support for parameters, including a parameter
|
|
database component for storing parameters in a non-volatile
|
|
manner (e.g., on a file system).
|
|
For complete information about F Prime parameters, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/[F Prime User Manual].
|
|
Here we concentrate on how to specify parameters in FPP.
|
|
|
|
==== Basic Parameters
|
|
|
|
The simplest parameter consists of the keyword `param`,
|
|
a name, and a data type.
|
|
The name is the name of the parameter.
|
|
The data type is the type of data stored in the parameter.
|
|
The data type must be a
|
|
<<Defining-Components_Commands_Formal-Parameters,displayable type>>.
|
|
|
|
As an example, here is an active component called `BasicParams`
|
|
with a few basic parameters.
|
|
|
|
[source,fpp]
|
|
----
|
|
@ An array of 3 F64 values
|
|
array F64x3 = [3] F64
|
|
|
|
@ A component for illustrating basic parameters
|
|
passive component BasicParams {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command receive port
|
|
command recv port cmdIn
|
|
|
|
@ Command registration port
|
|
command reg port cmdRegOut
|
|
|
|
@ Command response port
|
|
command resp port cmdResponseOut
|
|
|
|
@ Parameter get port
|
|
param get port prmGetOut
|
|
|
|
@ Parameter set port
|
|
param set port prmSetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Parameters
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Parameter 1
|
|
param Param1: U32
|
|
|
|
@ Parameter 2
|
|
param Param2: F64
|
|
|
|
@ Parameter 3
|
|
param Param3: F64x3
|
|
|
|
}
|
|
----
|
|
|
|
Notice that we defined the two
|
|
<<Defining-Components_Special-Port-Instances_Parameter-Ports,
|
|
parameter ports>>
|
|
for this component.
|
|
Both ports are required for any component that has parameters.
|
|
|
|
Notice also that we defined the
|
|
<<Defining-Components_Special-Port-Instances_Command-Ports,
|
|
command ports>>
|
|
for this component.
|
|
When you add one or more parameters to a component,
|
|
F Prime automatically generates commands for (1)
|
|
setting the local parameter in the component and (2) saving
|
|
the local parameter to a system-wide parameter database.
|
|
Therefore, any component that has parameters must have
|
|
the command ports.
|
|
Try deleting one or more of the command ports from the example
|
|
above and see what `fpp-check` does.
|
|
|
|
==== Default Values
|
|
|
|
You can specify a default value for any parameter.
|
|
This is the value that F Prime will use if no value is
|
|
available in the parameter database.
|
|
If you don't specify a default value, and no value is
|
|
available in the database, then attempting to get
|
|
the parameter produces an invalid value.
|
|
What happens then is up to the FSW implementation.
|
|
By providing default values for your parameters,
|
|
you can avoid handling this case.
|
|
|
|
Here is the example from the previous section, updated
|
|
to include default values for the parameters:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ An array of 3 F64 values
|
|
array F64x3 = [3] F64
|
|
|
|
@ A component for illustrating default parameter values
|
|
passive component ParamDefaults {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command receive port
|
|
command recv port cmdIn
|
|
|
|
@ Command registration port
|
|
command reg port cmdRegOut
|
|
|
|
@ Command response port
|
|
command resp port cmdResponseOut
|
|
|
|
@ Parameter get port
|
|
param get port prmGetOut
|
|
|
|
@ Parameter set port
|
|
param set port prmSetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Parameters
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Parameter 1
|
|
param Param1: U32 default 1
|
|
|
|
@ Parameter 2
|
|
param Param2: F64 default 2.0
|
|
|
|
@ Parameter 3
|
|
param Param3: F64x3 default [ 1.0, 2.0, 3.0 ]
|
|
|
|
}
|
|
----
|
|
|
|
==== Identifiers
|
|
|
|
Every parameter in an F Prime FSW application has a unique
|
|
numeric identifier.
|
|
As for
|
|
<<Defining-Components_Commands_Opcodes,command opcodes>>,
|
|
<<Defining-Components_Events_Identifiers,event identifiers>>,
|
|
and
|
|
<<Defining-Components_Telemetry_Identifiers,
|
|
telemetry channel identifiers>>,
|
|
the parameter identifiers for a component are specified
|
|
relative to the component, usually starting from
|
|
zero and counting up by one.
|
|
If you omit the identifier, then
|
|
FPP assigns a default identifier: zero for the first
|
|
parameter in the component; otherwise one more than the
|
|
identifier of the previous parameter.
|
|
|
|
If you wish, you may explicitly specify one or more
|
|
parameter identifiers.
|
|
To do this, you write the keyword `id` followed
|
|
by a numeric expression after the data type
|
|
and after the default value, if any.
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ An array of 3 F64 values
|
|
array F64x3 = [3] F64
|
|
|
|
@ A component for illustrating default parameter identifiers
|
|
passive component ParamIdentifiers {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command receive port
|
|
command recv port cmdIn
|
|
|
|
@ Command registration port
|
|
command reg port cmdRegOut
|
|
|
|
@ Command response port
|
|
command resp port cmdResponseOut
|
|
|
|
@ Parameter get port
|
|
param get port prmGetOut
|
|
|
|
@ Parameter set port
|
|
param set port prmSetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Parameters
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Parameter 1
|
|
@ Its implied identifier is 0x00
|
|
param Param1: U32 default 1
|
|
|
|
@ Parameter 2
|
|
@ Its identifier is 0x10
|
|
param Param2: F64 default 2.0 id 0x10
|
|
|
|
@ Parameter 3
|
|
@ Its implied identifier is 0x11
|
|
param Param3: F64x3 default [ 1.0, 2.0, 3.0 ]
|
|
|
|
}
|
|
----
|
|
|
|
Within a component, the parameter identifiers must be unique.
|
|
|
|
==== Set and Save Opcodes
|
|
|
|
Each parameter that you specify has two implied commands: one
|
|
for setting the value bound to the parameter locally in the
|
|
component, and one for saving the current local value
|
|
to the system-wide parameter database.
|
|
The opcodes for these implied commands are called the *set and
|
|
save opcodes* for the parameter.
|
|
|
|
By default, FPP generates set and save opcodes for a
|
|
parameter _P_ according to the following rules:
|
|
|
|
* If no command or parameter appears before _P_ in the
|
|
component, then the set opcode is 0, and the save opcode is 1.
|
|
|
|
* Otherwise, let _o_ be the previous opcode defined
|
|
in the component
|
|
(either a command opcode or a parameter save opcode).
|
|
Then the set opcode is _o_ + 1, and the save opcode is
|
|
_o_ + 2.
|
|
|
|
If you wish, you may specify either or both of the set and
|
|
save opcodes explicitly.
|
|
To specify the set opcode, you write the keywords `set` `opcode`
|
|
and a numeric expression.
|
|
To specify the save opcode, you write the keywords `save` `opcode`
|
|
and a numeric expression.
|
|
The set and save opcodes come after the type name, default
|
|
parameter value, and parameter identifier.
|
|
If both are present, the set opcode comes first.
|
|
|
|
When you specify an explicit set or save opcode _o_, the
|
|
default value for the next opcode is _o_ + 1.
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ An array of 3 F64 values
|
|
array F64x3 = [3] F64
|
|
|
|
@ A component for illustrating parameter set and save opcodes
|
|
passive component ParamOpcodes {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command receive port
|
|
command recv port cmdIn
|
|
|
|
@ Command registration port
|
|
command reg port cmdRegOut
|
|
|
|
@ Command response port
|
|
command resp port cmdResponseOut
|
|
|
|
@ Parameter get port
|
|
param get port prmGetOut
|
|
|
|
@ Parameter set port
|
|
param set port prmSetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Parameters
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Parameter 1
|
|
@ Its implied set opcode is 0x00
|
|
@ Its implied save opcode is 0x01
|
|
param Param1: U32 default 1
|
|
|
|
@ Parameter 2
|
|
@ Its set opcode is 0x10
|
|
@ Its save opcode is 0x11
|
|
param Param2: F64 \
|
|
default 2.0 \
|
|
id 0x10 \
|
|
set opcode 0x10 \
|
|
save opcode 0x11
|
|
|
|
@ Parameter 3
|
|
@ Its set opcode is 0x12
|
|
@ Its save opcode is 0x20
|
|
param Param3: F64x3 \
|
|
default [ 1.0, 2.0, 3.0 ] \
|
|
save opcode 0x20
|
|
|
|
}
|
|
----
|
|
|
|
==== External Parameters
|
|
|
|
In the default case, when you specify a parameter _P_ in a component
|
|
_C_, the FPP code generator produces code for storing
|
|
and managing _P_ as part of the auto-generated
|
|
code for _C_.
|
|
Sometimes it is more convenient to store and manage parameters separately.
|
|
For example, you may wish to integrate an F Prime component with a library
|
|
that stores and manages its own parameters.
|
|
In this case you can specify an **external** parameter.
|
|
An external parameter is like an ordinary parameter (also called
|
|
an internal parameter), except that its specifier begins
|
|
with the keyword `external`.
|
|
|
|
Here is an example component definition that specifies two parameters,
|
|
an internal parameter `Param1` and an external parameter `Param2`.
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A component for illustrating internal and external parameters
|
|
passive component ParamExternal {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command receive port
|
|
command recv port cmdIn
|
|
|
|
@ Command registration port
|
|
command reg port cmdRegOut
|
|
|
|
@ Command response port
|
|
command resp port cmdResponseOut
|
|
|
|
@ Parameter get port
|
|
param get port prmGetOut
|
|
|
|
@ Parameter set port
|
|
param set port prmSetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Parameters
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Param1 is an internal parameter
|
|
param Param1: U32
|
|
|
|
@ Param2 is an external parameter
|
|
external param Param2: F64 \
|
|
default 2.0 \
|
|
id 0x10 \
|
|
set opcode 0x10 \
|
|
save opcode 0x11
|
|
|
|
}
|
|
----
|
|
|
|
When you specify an external parameter, you are responsible for
|
|
providing the {cpp} code that stores and maintains the parameter.
|
|
The
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/framework/autocoded-functions/#parameters[F
|
|
Prime User Manual] explains how to do this.
|
|
|
|
=== Data Products
|
|
|
|
When defining an F Prime component, you may specify the *data products*
|
|
produced by that component.
|
|
A data product is a collection of related data that is stored onboard
|
|
and transmitted to the ground.
|
|
F Prime has special support for data products, including components
|
|
for (1) managing buffers that can store data products in memory;
|
|
(2) writing data products to the file system; and (3)
|
|
cataloging stored data products for downlink in priority order.
|
|
For more information about these F Prime features, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/framework/data-products/[F
|
|
Prime User Manual].
|
|
|
|
==== Basic Data Products
|
|
|
|
In F Prime, a data product is represented as a *container*.
|
|
One container holds one data product, and each data product
|
|
is typically stored in its own file.
|
|
A container consists of a header, which provides information about
|
|
the container (e.g., the size of the data payload), and binary data
|
|
representing a list of serialized *records*.
|
|
A record is a unit of data.
|
|
For a complete specification of the container format, see the
|
|
https://fprime.jpl.nasa.gov/devel/Fw/Dp/docs/sdd/[F Prime design
|
|
documentation].
|
|
|
|
In an F Prime component, you can specify one or more containers
|
|
and one or more records.
|
|
The simplest container specification consists of the keywords `product` `container`
|
|
and a name.
|
|
The name is the name of the container.
|
|
The simplest record specification consists of the keywords `product` `record`,
|
|
a name, and a data type.
|
|
The name is the name of the record.
|
|
The data type is the type of the data that the record holds.
|
|
The data type must be a
|
|
<<Defining-Components_Commands_Formal-Parameters,displayable type>>.
|
|
|
|
As an example, here is a component called `BasicDataProducts` that specifies
|
|
two records and two containers.
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A struct type defining some data
|
|
struct Data { a: U32, b: F32 }
|
|
|
|
@ A component for illustrating basic data products
|
|
passive component BasicDataProducts {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Product get port
|
|
product get port productGetOut
|
|
|
|
@ Product send port
|
|
product send port productSendOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Records
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Record 1
|
|
product record Record1: I32
|
|
|
|
@ Record 2
|
|
product record Record2: Data
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Containers
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Container 1
|
|
product container Container1
|
|
|
|
@ Container 2
|
|
product container Container2
|
|
|
|
}
|
|
----
|
|
|
|
The FPP back end uses this specification to generate code for requesting
|
|
buffers to hold containers and for serializing records into containers.
|
|
See the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/framework/data-products/[F
|
|
Prime design documentation] for the details.
|
|
|
|
Note the following:
|
|
|
|
* Records are not specific to containers.
|
|
For example, with the specification shown above, you can serialize instances of
|
|
`Record1` and `Record2` into either or both of `Container1` and `Container2`.
|
|
|
|
* Like telemetry channels, F Prime containers are component-centric.
|
|
A component can request containers that it defines, and it can fill those
|
|
containers with records that it defines.
|
|
It cannot use records or containers defined by another component.
|
|
|
|
* If a component has container specifier, then it must have at least one record
|
|
specifier, and vice versa.
|
|
|
|
==== Identifiers
|
|
|
|
Every record in an F Prime FSW application has a unique
|
|
numeric identifier.
|
|
As for
|
|
<<Defining-Components_Commands_Opcodes,command opcodes>>,
|
|
<<Defining-Components_Events_Identifiers,event identifiers>>,
|
|
<<Defining-Components_Telemetry_Identifiers,
|
|
telemetry channel identifiers>>, and
|
|
<<Defining-Components_Parameters,parameters>>,
|
|
the record identifiers for a component are specified
|
|
relative to the component, usually starting from
|
|
zero and counting up by one.
|
|
If you omit the identifier, then
|
|
FPP assigns a default identifier: zero for the first
|
|
event in the component; otherwise one more than the
|
|
identifier of the previous parameter.
|
|
The same observations apply to containers and container
|
|
identifiers.
|
|
|
|
If you wish, you may explicitly specify one or more
|
|
container or record identifiers.
|
|
To do this, you write the keyword `id` followed
|
|
by a numeric expression at the end of the container
|
|
or record specifier.
|
|
Here is an example:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A struct type defining some data
|
|
struct Data { a: U32, b: F32 }
|
|
|
|
@ A component for illustrating data product identifiers
|
|
passive component DataProductIdentifiers {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Product get port
|
|
product get port productGetOut
|
|
|
|
@ Product send port
|
|
product send port productSendOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Records
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Record 1
|
|
@ Its implied identifier is 0x00
|
|
product record Record1: I32
|
|
|
|
@ Record 2
|
|
@ Its identifier is 0x10
|
|
product record Record2: Data id 0x10
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Containers
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Container 1
|
|
@ Its identifier is 0x10
|
|
product container Container1 id 0x10
|
|
|
|
@ Container 2
|
|
@ Its implied identifier is 0x11
|
|
product container Container2
|
|
|
|
}
|
|
----
|
|
|
|
Within a component, the record identifiers must be unique,
|
|
and the container identifiers must be unique.
|
|
|
|
==== Array Records
|
|
|
|
In the basic form of a record described above, each record that
|
|
does not have
|
|
<<Defining-Types_Array-Type-Definitions_Type-Names,string type>>
|
|
has a fixed, statically-specified size.
|
|
The record may contain an array (e.g., an
|
|
<<Defining-Types_Array-Type-Definitions,array type>>
|
|
or a struct type with a
|
|
<<Defining-Types_Struct-Type-Definitions_Member-Arrays,member array>>),
|
|
but the size of the array must be specified in the model.
|
|
To specify a record that is a dynamically-sized array, you put
|
|
the keyword `array` after the type specifier for the record.
|
|
For example:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A struct type defining some data
|
|
struct Data { a: U32, b: F32 }
|
|
|
|
@ A component for illustrating array records
|
|
passive component ArrayRecords {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Product get port
|
|
product get port productGetOut
|
|
|
|
@ Product send port
|
|
product send port productSendOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Records
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ A data record
|
|
@ It holds one element of type Data
|
|
product record DataRecord: Data
|
|
|
|
@ A data array record
|
|
@ It holds an array of elements of type Data
|
|
product record DataArrayRecord: Data array
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Containers
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ A container
|
|
product container Container
|
|
|
|
}
|
|
----
|
|
|
|
In this example, a record with name `DataArrayRecord` holds
|
|
an array of elements of type `Data`.
|
|
The number of elements is unspecified in the model;
|
|
it is provided when the record is serialized into a container.
|
|
|
|
=== State Machine Instances
|
|
|
|
A *state machine instance* is a component member that instantiates
|
|
an FPP <<Defining-State-Machines,state machine>>.
|
|
The state machine instance becomes part of the component implementation.
|
|
|
|
For example, here is a simple async component that has
|
|
one state machine instance and one async input port
|
|
for driving the state machine:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ An external state machine
|
|
state machine M
|
|
|
|
@ A component with a state machine
|
|
active component StateMachine {
|
|
|
|
@ A port for driving the state machine
|
|
async input port schedIn: Svc.Sched
|
|
|
|
@ An instance of state machine M
|
|
state machine instance m: M
|
|
|
|
}
|
|
----
|
|
|
|
When a state machine instance _m_ is part of a component _C_,
|
|
each instance _c_ of _C_ sends _m_ *signals* to process as it runs.
|
|
Signals occur in response to commands or port
|
|
invocations received by _c_, and they tell _m_ when to change state.
|
|
_c_ puts the signals on its queue, and _m_ dispatches them.
|
|
Therefore, if a component _C_ has a state machine instance member _m_,
|
|
then its instances _c_ must have queues, i.e., _C_ must be active or queued.
|
|
|
|
As with <<Defining-Components_Internal-Ports,internal ports>>,
|
|
you may specify priority and queue full behavior associated
|
|
with the signals dispatched by a state machine instance.
|
|
For example, we can revise the example above as follows:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ An external state machine
|
|
state machine M
|
|
|
|
@ A component with a state machine
|
|
active component StateMachine {
|
|
|
|
@ A port for driving the state machine
|
|
async input port schedIn: Svc.Sched
|
|
|
|
@ An instance of state machine M
|
|
state machine instance m: M priority 10 drop
|
|
|
|
}
|
|
----
|
|
|
|
As discussed
|
|
<<Defining-State-Machines_Writing-a-State-Machine-Definition,above>>, state
|
|
machine definitions may be internal (specified in FPP) or external (specified
|
|
by an external tool). For more details about the {cpp} code generation for
|
|
instances of internal state machines, see the
|
|
https://fprime.jpl.nasa.gov/devel/docs/user-manual/framework/state-machines/[F
|
|
Prime design documentation].
|
|
|
|
=== Constants, Types, Enums, and State Machines
|
|
|
|
You can write a <<Defining-Constants,constant definition>>,
|
|
<<Defining-Types,type definition>>,
|
|
<<Defining-Enums,enum definition>>,
|
|
or
|
|
<<Defining-State-Machines,state machine definition>>
|
|
as a component member.
|
|
When you do this, the component qualifies
|
|
the name of the constant or type, similarly to the way that a
|
|
<<Defining-Modules,module>> qualifies the names of the
|
|
definitions it contains.
|
|
For example, if you define a type `T` inside a component
|
|
`C`, then
|
|
|
|
* Inside the definition of `C`, you can refer to the
|
|
type as `T`.
|
|
|
|
* Outside the definition of `C`, you must refer to the
|
|
type as `C.T`.
|
|
|
|
As an example, here is the `SerialSplitter` component
|
|
from the section on
|
|
<<Defining-Components_Port-Instances_Serial-Port-Instances,
|
|
serial port instances>>, where we have moved the
|
|
definition of the constant `splitFactor`
|
|
into the definition of the component.
|
|
|
|
[source,fpp]
|
|
----
|
|
@ Component for splitting a serial data stream
|
|
passive component SerialSplitter {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Constants
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Split factor
|
|
constant splitFactor = 10
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Input
|
|
sync input port serialIn: serial
|
|
|
|
@ Output
|
|
output port serialOut: [splitFactor] serial
|
|
|
|
}
|
|
----
|
|
|
|
As another example, here is the `Switch` component from the section on
|
|
<<Defining-Components_Commands_Formal-Parameters,
|
|
command formal parameters>>, where we have moved the definition of
|
|
the enum `State` into the component:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ A switch with on and off state
|
|
active component Switch {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Types
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ The state enumeration
|
|
enum State {
|
|
OFF @< The off state
|
|
ON @< The on state
|
|
}
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command input
|
|
command recv port cmdIn
|
|
|
|
@ Command registration
|
|
command reg port cmdRegOut
|
|
|
|
@ Command response
|
|
command resp port cmdResponseOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Commands
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Set the state
|
|
async command SET_STATE(
|
|
$state: State @< The new state
|
|
)
|
|
|
|
}
|
|
----
|
|
|
|
In general, it is a good idea to state a definition inside a component
|
|
when the definition logically belongs to the component.
|
|
The name scoping mechanism emphasizes the hierarchical relationship
|
|
and prevents name clashes.
|
|
|
|
In most cases, a qualified name such as `Switch.State`
|
|
in FPP becomes a qualified name such as `Switch::State` when translating
|
|
to {cpp}.
|
|
However, the current {cpp} code generation strategy does not
|
|
support the definition
|
|
of constants and types as members of {cpp} components.
|
|
Therefore, when translating the previous example to {cpp},
|
|
the following occurs:
|
|
|
|
. The component `Switch` becomes an auto-generated {cpp} class
|
|
`SwitchComponentBase`.
|
|
|
|
. The type `State` becomes a {cpp} class `Switch_State`.
|
|
|
|
Similarly, the FPP constant `SerialSplitter.splitFactor`
|
|
becomes a {cpp} constant `SerialSplitter_SplitFactor`.
|
|
We will have more to say about this issue in the section on
|
|
<<Analyzing-and-Translating-Models_Generating-C-Plus-Plus,
|
|
generating {cpp}>>.
|
|
|
|
=== Include Specifiers
|
|
|
|
Component definitions can become long, especially when there
|
|
are many commands, events, telemetry channels, and parameters.
|
|
In this case it is useful to break up the component
|
|
definition into several files.
|
|
|
|
For example, suppose you are defining a component with
|
|
many commands, and you wish to place the commands in a
|
|
separate file `Commands.fppi`.
|
|
The suffix `.fppi` is conventional for included FPP files.
|
|
Inside the component definition, you can write the
|
|
following component member:
|
|
|
|
[source,fpp]
|
|
--------
|
|
include "Commands.fppi"
|
|
--------
|
|
|
|
This construct is called an *include specifier*.
|
|
During analysis and translation, the include specifier
|
|
is replaced with the commands specified
|
|
in `Commands.fppi`, just as if you had written them
|
|
at the point where you wrote the include specifier.
|
|
This replacement is called expanding or resolving the
|
|
include specifier.
|
|
You can use the same technique for events, telemetry,
|
|
parameters, or any other component members.
|
|
|
|
The text enclosed in quotation marks after the keyword
|
|
`include` is a path name relative to the directory of the
|
|
file in which the include specifier appears.
|
|
The file must exist and must contain component members
|
|
that can validly appear at the point where the include
|
|
specifier appears.
|
|
For example, if `Commands.fppi` contains invalid syntax
|
|
or syntax that may not appear inside a component,
|
|
or if the file `Commands.fppi` does not exist, then
|
|
the specifier `include "Commands.fppi"` is not valid.
|
|
|
|
Include specifiers are perhaps most useful when defining
|
|
components, but they can also appear at the top level of a
|
|
model, inside a module definition, or inside a
|
|
topology definition.
|
|
We discuss include specifiers further in the section on
|
|
<<Specifying-Models-as-Files_Include-Specifiers,
|
|
specifying models as files>>.
|
|
|
|
=== Matched Ports
|
|
|
|
Some F Prime components employ the following pattern:
|
|
|
|
. The component has a pair of port arrays, say `p1` and `p2`.
|
|
The two arrays have the same number of ports.
|
|
|
|
. For every connection between `p1` and another component
|
|
instance, there must be a matching connection between that
|
|
component instance and `p2`.
|
|
|
|
. The matched pairs in item 2 must be connected to the
|
|
same port numbers at `p1` and `p2`.
|
|
|
|
In this case we call `p1` and `p2` a pair of
|
|
*matched ports*.
|
|
For example:
|
|
|
|
* The standard Command Dispatcher component has matched ports
|
|
`compCmdReg` for receiving command registration and
|
|
`compCmdSend` for sending commands.
|
|
|
|
* The standard Health component has matched ports
|
|
`PingSend` for sending health ping messages and
|
|
`PingReturn` for receiving responses to the ping messages.
|
|
|
|
FPP provides special support for matched ports.
|
|
Inside a component definition, you can write
|
|
`match p1 with p2`, where `p1` and `p2` are the names of
|
|
<<Defining-Components_Port-Instances,port instances>>
|
|
defined in the component.
|
|
When you do this, the following occurs:
|
|
|
|
. The FPP translator checks that `p1` and `p2` have
|
|
the same number of ports.
|
|
If not, an error occurs.
|
|
|
|
. When
|
|
<<Defining-Topologies_Port-Numbering_Matched-Numbering,
|
|
automatically numbering a topology>>, the
|
|
translator ensures that the port numbers match in the manner
|
|
described above.
|
|
|
|
For example, here is a simplified version of the Health
|
|
component:
|
|
|
|
[source,fpp]
|
|
----
|
|
@ Number of health ping ports
|
|
constant numPingPorts = 10
|
|
|
|
queued component Health {
|
|
|
|
@ Ping output port
|
|
output port pingOut: [numPingPorts] Svc.Ping
|
|
|
|
@ Ping input port
|
|
async input port pingIn: [numPingPorts] Svc.Ping
|
|
|
|
@ Corresponding port numbers of pingOut and pingIn must match
|
|
match pingOut with pingIn
|
|
|
|
}
|
|
----
|
|
|
|
This component defines a pair of matched ports
|
|
`pingOut` and `pingIn`.
|