mirror of
https://github.com/nasa/fprime.git
synced 2025-12-12 07:43:50 -06:00
2292 lines
71 KiB
Markdown
2292 lines
71 KiB
Markdown
# F' Math Component Tutorial
|
|
|
|
# Table of Contents
|
|
|
|
* <a href="#Introduction">1. Introduction</a>
|
|
* <a href="#The-MathOp-Type">2. The MathOp Type</a>
|
|
* <a href="#The-MathOp-Type_Construct-the-FPP-Model">2.1. Construct the FPP Model</a>
|
|
* <a href="#The-MathOp-Type_Add-the-Model-to-the-Project">2.2. Add the Model to the Project</a>
|
|
* <a href="#The-MathOp-Type_Build-the-Model">2.3. Build the Model</a>
|
|
* <a href="#The-MathOp-Type_Reference-Implementation">2.4. Reference Implementation</a>
|
|
* <a href="#The-MathOp-and-MathResult-Ports">3. The MathOp and MathResult Ports</a>
|
|
* <a href="#The-MathOp-and-MathResult-Ports_Construct-the-FPP-Model">3.1. Construct the FPP Model</a>
|
|
* <a href="#The-MathOp-and-MathResult-Ports_Add-the-Model-to-the-Project">3.2. Add the Model to the Project</a>
|
|
* <a href="#The-MathOp-and-MathResult-Ports_Build-the-Model">3.3. Build the Model</a>
|
|
* <a href="#The-MathOp-and-MathResult-Ports_Reference-Implementation">3.4. Reference Implementation</a>
|
|
* <a href="#The-MathSender-Component">4. The MathSender Component</a>
|
|
* <a href="#The-MathSender-Component_Construct-the-FPP-Model">4.1. Construct the FPP Model</a>
|
|
* <a href="#The-MathSender-Component_Add-the-Model-to-the-Project">4.2. Add the Model to the Project</a>
|
|
* <a href="#The-MathSender-Component_Build-the-Stub-Implementation">4.3. Build the Stub Implementation</a>
|
|
* <a href="#The-MathSender-Component_Complete-the-Implementation">4.4. Complete the Implementation</a>
|
|
* <a href="#The-MathSender-Component_Write-and-Run-Unit-Tests">4.5. Write and Run Unit Tests</a>
|
|
* <a href="#The-MathSender-Component_Write-and-Run-Unit-Tests_Set-Up-the-Unit-Test-Environment">4.5.1. Set Up the Unit Test Environment</a>
|
|
* <a href="#The-MathSender-Component_Write-and-Run-Unit-Tests_Write-and-Run-One-Test">4.5.2. Write and Run One Test</a>
|
|
* <a href="#The-MathSender-Component_Write-and-Run-Unit-Tests_Write-and-Run-More-Tests">4.5.3. Write and Run More Tests</a>
|
|
* <a href="#The-MathSender-Component_Write-and-Run-Unit-Tests_Exercise-Random-Testing">4.5.4. Exercise: Random Testing</a>
|
|
* <a href="#The-MathSender-Component_Reference-Implementation">4.6. Reference Implementation</a>
|
|
* <a href="#The-MathReceiver-Component">5. The MathReceiver Component</a>
|
|
* <a href="#The-MathReceiver-Component_Construct-the-FPP-Model">5.1. Construct the FPP Model</a>
|
|
* <a href="#The-MathReceiver-Component_Add-the-Model-to-the-Project">5.2. Add the Model to the Project</a>
|
|
* <a href="#The-MathReceiver-Component_Build-the-Stub-Implementation">5.3. Build the Stub Implementation</a>
|
|
* <a href="#The-MathReceiver-Component_Complete-the-Implementation">5.4. Complete the Implementation</a>
|
|
* <a href="#The-MathReceiver-Component_Write-and-Run-Unit-Tests">5.5. Write and Run Unit Tests</a>
|
|
* <a href="#The-MathReceiver-Component_Write-and-Run-Unit-Tests_Set-up-the-Unit-Test-Environment">5.5.1. Set up the Unit Test Environment</a>
|
|
* <a href="#The-MathReceiver-Component_Write-and-Run-Unit-Tests_Add-Helper-Code">5.5.2. Add Helper Code</a>
|
|
* <a href="#The-MathReceiver-Component_Write-and-Run-Unit-Tests_Write-and-Run-Tests">5.5.3. Write and Run Tests</a>
|
|
* <a href="#The-MathReceiver-Component_Reference-Implementation">5.6. Reference Implementation</a>
|
|
* <a href="#The-MathReceiver-Component_Exercises">5.7. Exercises</a>
|
|
* <a href="#The-MathReceiver-Component_Exercises_Adding-Telemetry">5.7.1. Adding Telemetry</a>
|
|
* <a href="#The-MathReceiver-Component_Exercises_Error-Handling">5.7.2. Error Handling</a>
|
|
* <a href="#Updating-the-Ref-Deployment">6. Updating the Ref Deployment</a>
|
|
* <a href="#Updating-the-Ref-Deployment_Defining-the-Component-Instances">6.1. Defining the Component Instances</a>
|
|
* <a href="#Updating-the-Ref-Deployment_Updating-the-Topology">6.2. Updating the Topology</a>
|
|
* <a href="#Updating-the-Ref-Deployment_Building-the-Ref-Deployment">6.3. Building the Ref Deployment</a>
|
|
* <a href="#Updating-the-Ref-Deployment_Visualizing-the-Ref-Topology">6.4. Visualizing the Ref Topology</a>
|
|
* <a href="#Updating-the-Ref-Deployment_Reference-Implementation">6.5. Reference Implementation</a>
|
|
* <a href="#Running-the-Ref-Deployment">7. Running the Ref Deployment</a>
|
|
* <a href="#Running-the-Ref-Deployment_Sending-a-Command">7.1. Sending a Command</a>
|
|
* <a href="#Running-the-Ref-Deployment_Checking-Events">7.2. Checking Events</a>
|
|
* <a href="#Running-the-Ref-Deployment_Checking-Telemetry">7.3. Checking Telemetry</a>
|
|
* <a href="#Running-the-Ref-Deployment_Setting-Parameters">7.4. Setting Parameters</a>
|
|
* <a href="#Running-the-Ref-Deployment_Saving-Parameters">7.5. Saving Parameters</a>
|
|
* <a href="#Running-the-Ref-Deployment_GDS-Logs">7.6. GDS Logs</a>
|
|
|
|
<a name="Introduction"></a>
|
|
## 1. Introduction
|
|
|
|
This tutorial shows how to develop, test, and deploy a simple topology
|
|
consisting of two components:
|
|
|
|
1. `MathSender`: A component that receives commands and forwards work to
|
|
`MathReceiver`.
|
|
|
|
1. `MathReceiver`: A component that carries out arithmetic operations and
|
|
returns the results to `MathSender`.
|
|
|
|
See the diagram below.
|
|
|
|
<a name="math-top"></a>
|
|

|
|
|
|
**What is covered:** The tutorial covers the following concepts:
|
|
|
|
1. Using the [FPP modeling language](https://fprime-community.github.io/fpp) to
|
|
specify the types and ports used by the components.
|
|
|
|
1. Using the F Prime build system to build the types and ports.
|
|
|
|
1. Developing the `MathSender` component: Specifying the component, building
|
|
the component, completing the C++ component implementation, and writing
|
|
component unit tests.
|
|
|
|
1. Developing the `MathReceiver` component.
|
|
|
|
1. Adding the new components and connections to the F Prime `Ref` application.
|
|
|
|
1. Using the F Prime Ground Data System (GDS) to run the updated `Ref`
|
|
application.
|
|
|
|
**Prerequisites:** This tutorial assumes the following:
|
|
|
|
1. Basic knowledge of Unix: How to navigate in a shell and execute programs.
|
|
|
|
1. Basic knowledge of git: How to create a branch.
|
|
|
|
1. Basic knowledge of C++, including class declarations, inheritance,
|
|
and virtual functions.
|
|
|
|
If you have not yet installed F Prime on your system, do so now.
|
|
Follow the installation guide at `INSTALL.md`
|
|
in the [F Prime git repository](https://github.com/nasa/fprime).
|
|
You may also wish to work through the Getting Started tutorial at
|
|
`docs/GettingStarted/Tutorial.md`.
|
|
|
|
**Git branch:** This tutorial is designed to work on the branch `release/v3.0.0`.
|
|
|
|
Working on this tutorial will modify some files under version control in the
|
|
F Prime git repository.
|
|
Therefore it is a good idea to do this work on a new branch.
|
|
For example:
|
|
|
|
```bash
|
|
git checkout release/v3.0.0
|
|
git checkout -b math-tutorial
|
|
```
|
|
|
|
If you wish, you can save your work by committing to this branch.
|
|
|
|
<a name="The-MathOp-Type"></a>
|
|
## 2. The MathOp Type
|
|
|
|
In F Prime, a **type definition** defines a kind of data that you can pass
|
|
between components or use in commands and telemetry.
|
|
|
|
For this tutorial, we need one type definition.
|
|
It defines an enumeration called `MathOp`, which
|
|
represents a mathematical operation.
|
|
|
|
We will add the specification for the `MathOp` type to the
|
|
`Ref` topology.
|
|
We will do this in three stages:
|
|
|
|
1. Construct the FPP model.
|
|
|
|
1. Add the model to the project.
|
|
|
|
1. Build the model.
|
|
|
|
<a name="The-MathOp-Type_Construct-the-FPP-Model"></a>
|
|
### 2.1. Construct the FPP Model
|
|
|
|
**Create the MathTypes directory:**
|
|
Go to the directory `Ref` at the top-level of the
|
|
F Prime repository and run `mkdir MathTypes`.
|
|
This step creates a new directory `Ref/MathTypes`.
|
|
This directory will contain our new type.
|
|
|
|
**Create the FPP model file:**
|
|
Now go into the directory `Ref/MathTypes`.
|
|
In that directory, create a file `MathTypes.fpp` with the following contents:
|
|
|
|
```fpp
|
|
module Ref {
|
|
|
|
@ A math operation
|
|
enum MathOp {
|
|
ADD @< Addition
|
|
SUB @< Subtraction
|
|
MUL @< Multiplication
|
|
DIV @< Division
|
|
}
|
|
|
|
}
|
|
```
|
|
|
|
You can do this by typing, or by copy-paste.
|
|
|
|
This file defines an enumeration or **enum** with enumerated constants `ADD`,
|
|
`SUB`, `MUL`, and `DIV`.
|
|
These four constants represent the operations of addition, subtraction,
|
|
multiplication, and division.
|
|
The enum also defines a type `MathOp`; the enumerated constants are the values
|
|
of this type.
|
|
For more information on enums, see [_The FPP User's
|
|
Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Enums).
|
|
|
|
The enum `MathTypes` resides in an FPP module `Ref`.
|
|
|
|
An FPP module is like a C++ namespace: it encloses several definitions, each of
|
|
which is qualified with the name of the module.
|
|
For more information on FPP modules, see [_The FPP User's
|
|
Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Modules).
|
|
|
|
The text following a symbol `@` or `@<` is called an **annotation**.
|
|
These annotations are carried through the parsing and become comments in the
|
|
generated code.
|
|
For more information, see [_The FPP User's
|
|
Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Writing-Comments-and-Annotations).
|
|
|
|
<a name="types_add"></a>
|
|
<a name="The-MathOp-Type_Add-the-Model-to-the-Project"></a>
|
|
### 2.2. Add the Model to the Project
|
|
|
|
**Create Ref/MathTypes/CMakeLists.txt:**
|
|
Create a file `Ref/MathTypes/CMakeLists.txt` with the following contents:
|
|
|
|
```cmake
|
|
set(SOURCE_FILES
|
|
"${CMAKE_CURRENT_LIST_DIR}/MathTypes.fpp"
|
|
)
|
|
|
|
register_fprime_module()
|
|
```
|
|
|
|
This code will tell the build system how to build the FPP model.
|
|
|
|
**Update Ref/CMakeLists.txt:**
|
|
Now we need to add the new directory to the `Ref` project.
|
|
To do that, open the file `Ref/CMakeLists.txt`.
|
|
This file should already exist; it was put there by the developers
|
|
of the `Ref` topology.
|
|
In this file, you should see several lines starting with `add_fprime_subdirectory`.
|
|
Immediately after the last of those lines, add the following new line:
|
|
|
|
```cmake
|
|
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/MathTypes/")
|
|
```
|
|
|
|
<a name="types_build"></a>
|
|
<a name="The-MathOp-Type_Build-the-Model"></a>
|
|
### 2.3. Build the Model
|
|
|
|
**Run the build:**
|
|
Do the following:
|
|
|
|
1. Go to the directory `Ref/MathTypes`.
|
|
|
|
1. If you have not already run `fprime-util generate`, then do so now.
|
|
|
|
1. Run the command `fprime-util build`.
|
|
|
|
The output should indicate that the model built without any errors.
|
|
If not, try to identify and correct what is wrong,
|
|
either by deciphering the error output, or by going over the steps again.
|
|
If you get stuck, you can look at the
|
|
<a href="#types_ref">reference implementation</a>.
|
|
|
|
**Inspect the generated code:**
|
|
Now go to the directory `Ref/build-fprime-automatic-native/Ref/MathTypes`
|
|
(you may want to use `pushd`, or do this in a separate shell,
|
|
so you don't lose your current working directory).
|
|
The directory `build-fprime-automatic-native` is where all the
|
|
generated code lives for the "automatic native" build of the `Ref`
|
|
project.
|
|
Within that directory is a directory tree that mirrors the project
|
|
structure.
|
|
In particular, `Ref/build-fprime-automatic-native/Ref/MathTypes`
|
|
contains the generated code for `Ref/MathTypes`.
|
|
|
|
Run `ls`.
|
|
You should see something like this:
|
|
|
|
```bash
|
|
CMakeFiles MathOpEnumAc.cpp MathOpEnumAi.xml.prev cmake_install.cmake
|
|
Makefile MathOpEnumAc.hpp autocoder
|
|
```
|
|
|
|
The files `MathOpEnumAc.hpp` and
|
|
`MathOpEnumAc.cpp` are the auto-generated C++ files
|
|
corresponding to the `MathOp` enum.
|
|
You may wish to study the file `MathOpEnumAc.hpp`.
|
|
This file gives the interface to the C++ class `Ref::MathOp`.
|
|
All enum types have a similar auto-generated class
|
|
interface.
|
|
|
|
<a name="types_ref"></a>
|
|
<a name="The-MathOp-Type_Reference-Implementation"></a>
|
|
### 2.4. Reference Implementation
|
|
|
|
A reference implementation for this section is available at
|
|
`docs/Tutorials/MathComponent/MathTypes`.
|
|
To build this implementation from a clean repository,
|
|
do the following:
|
|
|
|
1. Go to the `Ref` directory.
|
|
|
|
1. Run `cp -R ../docs/Tutorials/MathComponent/MathTypes .`
|
|
|
|
1. Update `Ref/CMakeLists.txt` as stated <a href="#types_add">above</a>.
|
|
|
|
1. Follow the steps for <a href="#types_build">building the model</a>.
|
|
|
|
If you have modified the repo, revise the steps accordingly.
|
|
For example, switch git branches, use `git stash` to stash
|
|
your changes, or move `MathTypes` to another directory such
|
|
as `MathTypes-saved`.
|
|
|
|
<a name="ports"></a>
|
|
<a name="The-MathOp-and-MathResult-Ports"></a>
|
|
## 3. The MathOp and MathResult Ports
|
|
|
|
A **port** is the endpoint of a connection between
|
|
two components.
|
|
A **port definition** is like a function signature;
|
|
it defines the type of the data carried on a port.
|
|
|
|
For this tutorial, we need two port definitions:
|
|
|
|
* `MathOp` for sending an arithmetic operation request from
|
|
`MathSender` to `MathReceiver`.
|
|
|
|
* `MathResult` for sending the result of an arithmetic
|
|
operation from `MathReceiver` to `MathSender`.
|
|
|
|
We follow the same three steps as in the previous section.
|
|
|
|
<a name="The-MathOp-and-MathResult-Ports_Construct-the-FPP-Model"></a>
|
|
### 3.1. Construct the FPP Model
|
|
|
|
**Create the MathPorts directory:**
|
|
Go to the directory `Ref` at the top-level of the
|
|
F Prime repository and run `mkdir MathPorts`.
|
|
This directory will contain our new ports.
|
|
|
|
**Create the FPP model file:**
|
|
Now go into the directory `Ref/MathPorts`.
|
|
Create a file `MathPorts.fpp` with the following contents:
|
|
|
|
```fpp
|
|
module Ref {
|
|
|
|
@ Port for requesting an operation on two numbers
|
|
port MathOp(
|
|
val1: F32 @< The first operand
|
|
op: MathOp @< The operation
|
|
val2: F32 @< The second operand
|
|
)
|
|
|
|
@ Port for returning the result of a math operation
|
|
port MathResult(
|
|
result: F32 @< the result of the operation
|
|
)
|
|
|
|
}
|
|
```
|
|
|
|
This file defines the ports `MathOp` and `MathResult`.
|
|
`MathOp` has three formal parameters: a first operand, an
|
|
operation, and a second operand.
|
|
The operands have type `F32`, which represents a 32-bit
|
|
floating-point number.
|
|
The operation has type `MathOp`, which is the enum type
|
|
we defined in the previous section.
|
|
`MathResult` has a single formal parameter, the value of type `F32`
|
|
returned as the result of the operation.
|
|
|
|
For more information about port definitions, see
|
|
[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Ports).
|
|
|
|
<a name="The-MathOp-and-MathResult-Ports_Add-the-Model-to-the-Project"></a>
|
|
### 3.2. Add the Model to the Project
|
|
|
|
Add add the model
|
|
`Ref/MathPorts/MathPorts.fpp` to the `Ref` project.
|
|
Carry out the steps in the
|
|
<a href="#types_add">previous section</a>, after
|
|
substituting `MathPorts` for `MathTypes`.
|
|
|
|
<a name="The-MathOp-and-MathResult-Ports_Build-the-Model"></a>
|
|
### 3.3. Build the Model
|
|
|
|
Carry out the steps in the
|
|
<a href="#types_build">previous section</a>,
|
|
in directory `MathPorts` instead of `MathTypes`.
|
|
The generated code will go in
|
|
`Ref/build-fprime-automatic-native/Ref/MathPorts`.
|
|
For port definitions, the names of the auto-generated C++
|
|
files end in `PortAc.hpp` and `PortAc.cpp`.
|
|
You can look at this code if you wish.
|
|
However, the auto-generated C++ port files are used
|
|
by the autocoded component implementations (described below);
|
|
you won't ever program directly against their interfaces.
|
|
|
|
<a name="The-MathOp-and-MathResult-Ports_Reference-Implementation"></a>
|
|
### 3.4. Reference Implementation
|
|
|
|
A reference implementation for this section is available at
|
|
`docs/Tutorials/MathComponent/MathPorts`.
|
|
To build this implementation, follow the steps
|
|
described for <a href="#types_ref">`MathTypes`</a>.
|
|
|
|
<a name="math-sender"></name>
|
|
<a name="The-MathSender-Component"></a>
|
|
## 4. The MathSender Component
|
|
|
|
Now we can build and test the `MathSender` component.
|
|
There are five steps:
|
|
|
|
1. Construct the FPP model.
|
|
1. Add the model to the project.
|
|
1. Build the stub implementation.
|
|
1. Complete the implementation.
|
|
1. Write and run unit tests.
|
|
|
|
<a name="The-MathSender-Component_Construct-the-FPP-Model"></a>
|
|
### 4.1. Construct the FPP Model
|
|
|
|
**Create the MathSender directory:**
|
|
Go to the directory `Ref` at the top-level of the
|
|
F Prime repository.
|
|
Run `mkdir MathSender` to create a directory for the new component.
|
|
|
|
**Create the FPP model file:**
|
|
Now go into the directory `Ref/MathSender`.
|
|
Create a file `MathSender.fpp` with the following contents:
|
|
|
|
```fpp
|
|
module Ref {
|
|
|
|
@ Component for sending a math operation
|
|
active component MathSender {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# General ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Port for sending the operation request
|
|
output port mathOpOut: MathOp
|
|
|
|
@ Port for receiving the result
|
|
async input port mathResultIn: MathResult
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Special ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command receive port
|
|
command recv port cmdIn
|
|
|
|
@ Command registration port
|
|
command reg port cmdRegOut
|
|
|
|
@ Command response port
|
|
command resp port cmdResponseOut
|
|
|
|
@ Event port
|
|
event port eventOut
|
|
|
|
@ Telemetry port
|
|
telemetry port tlmOut
|
|
|
|
@ Text event port
|
|
text event port textEventOut
|
|
|
|
@ Time get port
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Commands
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Do a math operation
|
|
async command DO_MATH(
|
|
val1: F32 @< The first operand
|
|
op: MathOp @< The operation
|
|
val2: F32 @< The second operand
|
|
)
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Events
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Math command received
|
|
event COMMAND_RECV(
|
|
val1: F32 @< The first operand
|
|
op: MathOp @< The operation
|
|
val2: F32 @< The second operand
|
|
) \
|
|
severity activity low \
|
|
format "Math command received: {f} {} {f}"
|
|
|
|
@ Received math result
|
|
event RESULT(
|
|
result: F32 @< The math result
|
|
) \
|
|
severity activity high \
|
|
format "Math result is {f}"
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Telemetry
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ The first value
|
|
telemetry VAL1: F32
|
|
|
|
@ The operation
|
|
telemetry OP: MathOp
|
|
|
|
@ The second value
|
|
telemetry VAL2: F32
|
|
|
|
@ The result
|
|
telemetry RESULT: F32
|
|
|
|
}
|
|
|
|
}
|
|
```
|
|
|
|
This code defines a component `Ref.MathSender`.
|
|
The component is **active**, which means it has its
|
|
own thread.
|
|
|
|
Inside the definition of the `MathSender` component are
|
|
several specifiers.
|
|
We have divided the specifiers into five groups:
|
|
|
|
1. **General ports:** These are user-defined ports for
|
|
application-specific functions.
|
|
There are two general ports: an output port `mathOpOut`
|
|
of type `MathOp` and an input port `mathResultIn` of
|
|
type `MathResult`.
|
|
Notice that these port specifiers use the ports that
|
|
we defined <a href="#ports">above</a>.
|
|
The input port is **asynchronous**.
|
|
This means that invoking the port (i.e., sending
|
|
data on the port) puts a message on a queue.
|
|
The handler runs later, on the thread of this component.
|
|
|
|
1. **Special ports:** These are ports that have a special
|
|
meaning in F Prime.
|
|
There are ports for registering commands with the dispatcher,
|
|
receiving commands, sending command responses, emitting
|
|
event reports, emitting telemetry, and getting the time.
|
|
|
|
1. **Commands:** These are commands sent from the ground
|
|
or from a sequencer and dispatched to this component.
|
|
There is one command `DO_MATH` for doing a math operation.
|
|
The command is asynchronous.
|
|
This means that when the command arrives, it goes on a queue
|
|
and its handler is later run on the thread of this component.
|
|
|
|
1. **Events:** These are event reports that this component
|
|
can emit.
|
|
There are two event reports, one for receiving a command
|
|
and one for receiving a result.
|
|
|
|
1. **Telemetry:** These are **channels** that define telemetry
|
|
points that the this component can emit.
|
|
There are four telemetry channels: three for the arguments
|
|
to the last command received and one for the last
|
|
result received.
|
|
|
|
For more information on defining components, see
|
|
[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Components).
|
|
|
|
<a name="math-sender_add-model"></a>
|
|
<a name="The-MathSender-Component_Add-the-Model-to-the-Project"></a>
|
|
### 4.2. Add the Model to the Project
|
|
|
|
**Create Ref/MathSender/CMakeLists.txt:**
|
|
Create a file `Ref/MathSender/CMakeLists.txt` with the following contents:
|
|
|
|
```cmake
|
|
# Register the standard build
|
|
set(SOURCE_FILES
|
|
"${CMAKE_CURRENT_LIST_DIR}/MathSender.cpp"
|
|
"${CMAKE_CURRENT_LIST_DIR}/MathSender.fpp"
|
|
)
|
|
register_fprime_module()
|
|
```
|
|
|
|
This code will tell the build system how to build the FPP model
|
|
and component implementation.
|
|
|
|
**Update Ref/CMakeLists.txt:**
|
|
Add `Ref/MathSender` to `Ref/CMakeLists.txt`, as we did
|
|
for <a href="#types_add">`Ref/MathTypes`</a>.
|
|
|
|
<a name="math-sender_build-stub"></a>
|
|
<a name="The-MathSender-Component_Build-the-Stub-Implementation"></a>
|
|
### 4.3. Build the Stub Implementation
|
|
|
|
**Run the build:**
|
|
Go into the directory `Ref/MathTypes`.
|
|
Run the following commands:
|
|
|
|
```bash
|
|
touch MathSender.cpp
|
|
fprime-util impl
|
|
```
|
|
|
|
The first command creates an empty file `MathSender.cpp`.
|
|
The build rules we wrote in the previous section expect
|
|
this file to be there.
|
|
After the second command, the build system should
|
|
run for a bit.
|
|
At the end there should be two new files
|
|
in the directory:
|
|
`MathSenderComponentImpl.cpp-template` and
|
|
`MathSenderComponentImpl.hpp-template`.
|
|
|
|
Run the following commands:
|
|
|
|
```bash
|
|
mv MathSenderComponentImpl.cpp-template MathSender.cpp
|
|
mv MathSenderComponentImpl.hpp-template MathSender.hpp
|
|
```
|
|
|
|
These commands produce a template, or stub implementation,
|
|
of the `MathSender` implementation class.
|
|
You will fill in this implementation class below.
|
|
|
|
Now run the command `fprime-util build --jobs 4`.
|
|
The model and the stub implementation should build.
|
|
The option `--jobs 4` says to use four cores for the build.
|
|
This should make the build go faster.
|
|
You can use any number after `--jobs`, up to the number
|
|
of cores available on your system.
|
|
|
|
**Inspect the generated code:**
|
|
The generated code resides in the directory
|
|
`Ref/fprime-build-automatic-native-ut/Ref/MathSender`.
|
|
You may wish to look over the file `MathSenderComponentAc.hpp`
|
|
to get an idea of the interface to the auto-generated
|
|
base class `MathSenderComponentBase`.
|
|
The `MathSender` implementation class is a derived class
|
|
of this base class.
|
|
|
|
<a name="The-MathSender-Component_Complete-the-Implementation"></a>
|
|
### 4.4. Complete the Implementation
|
|
|
|
Now we can complete the stub implementation.
|
|
In an editor, open the file `MathSender.cpp`.
|
|
|
|
**Fill in the DO_MATH command handler:**
|
|
You should see a stub handler for the `DO_MATH`
|
|
command that looks like this:
|
|
|
|
```c++
|
|
void MathSender ::
|
|
DO_MATH_cmdHandler(
|
|
const FwOpcodeType opCode,
|
|
const U32 cmdSeq,
|
|
F32 val1,
|
|
MathOp op,
|
|
F32 val2
|
|
)
|
|
{
|
|
// TODO
|
|
this->cmdResponse_out(opCode,cmdSeq,Fw::CmdResponse::OK);
|
|
}
|
|
```
|
|
|
|
The handler `DO_MATH_handler` is called when the `MathSender`
|
|
component receives a `DO_MATH` command.
|
|
This handler overrides the corresponding pure virtual
|
|
function in the auto-generated base class.
|
|
Fill in the handler so that it looks like this:
|
|
|
|
```c++
|
|
void MathSender ::
|
|
DO_MATH_cmdHandler(
|
|
const FwOpcodeType opCode,
|
|
const U32 cmdSeq,
|
|
F32 val1,
|
|
MathOp op,
|
|
F32 val2
|
|
)
|
|
{
|
|
this->tlmWrite_VAL1(val1);
|
|
this->tlmWrite_OP(op);
|
|
this->tlmWrite_VAL2(val2);
|
|
this->log_ACTIVITY_LO_COMMAND_RECV(val1, op, val2);
|
|
this->mathOpOut_out(0, val1, op, val2);
|
|
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
|
|
}
|
|
```
|
|
|
|
The first two arguments to the handler function provide
|
|
the command opcode and the command sequence number
|
|
(a unique identifier generated by the command dispatcher).
|
|
The remaining arguments are supplied when the command is sent,
|
|
for example, from the F Prime ground data system (GDS).
|
|
The implementation code does the following:
|
|
|
|
1. Emit telemetry and events.
|
|
|
|
1. Invoke the `mathOpOut` port to request that `MathReceiver`
|
|
perform the operation.
|
|
|
|
1. Send a command response indicating success.
|
|
The command response goes out on the special port
|
|
`cmdResponseOut`.
|
|
|
|
In F Prime, every execution of a command handler must end by
|
|
sending a command response.
|
|
The proper behavior of other framework components (e.g., command
|
|
dispatcher, command sequencer) depends upon adherence to this rule.
|
|
|
|
**Check the build:**
|
|
Run `fprime-util build` again to make sure that everything still builds.
|
|
|
|
**Fill in the mathResultIn handler:**
|
|
You should see a stub handler for the `mathResultIn`
|
|
port that looks like this:
|
|
|
|
```c++
|
|
void MathSender ::
|
|
mathResultIn_handler(
|
|
const NATIVE_INT_TYPE portNum,
|
|
F32 result
|
|
)
|
|
{
|
|
// TODO
|
|
}
|
|
```
|
|
|
|
The handler `mathResultIn_handler` is called when the `MathReceiver`
|
|
component code returns a result by invoking the `mathResultIn` port.
|
|
Again the handler overrides the corresponding pure virtual
|
|
function in the auto-generated base class.
|
|
Fill in the handler so that it looks like this:
|
|
|
|
```c++
|
|
void MathSender ::
|
|
mathResultIn_handler(
|
|
const NATIVE_INT_TYPE portNum,
|
|
F32 result
|
|
)
|
|
{
|
|
this->tlmWrite_RESULT(result);
|
|
this->log_ACTIVITY_HI_RESULT(result);
|
|
}
|
|
```
|
|
|
|
The implementation code emits the result on the `RESULT`
|
|
telemetry channel and as a `RESULT` event report.
|
|
|
|
**Check the build:**
|
|
Run `fprime-util build`.
|
|
|
|
<a name="math-sender_unit"></a>
|
|
<a name="The-MathSender-Component_Write-and-Run-Unit-Tests"></a>
|
|
### 4.5. Write and Run Unit Tests
|
|
|
|
**Unit tests** are an important part of FSW development.
|
|
At the component level, unit tests typically invoke input ports, send commands,
|
|
and check for expected values on output ports (including telemetry and event
|
|
ports).
|
|
|
|
We will carry out the unit testing for the `MathSender` component
|
|
in three steps:
|
|
|
|
1. Set up the unit test environment
|
|
|
|
1. Write and run one unit test
|
|
|
|
1. Write and run additional unit tests
|
|
|
|
<a name="math-sender_unit_setup"></a>
|
|
<a name="The-MathSender-Component_Write-and-Run-Unit-Tests_Set-Up-the-Unit-Test-Environment"></a>
|
|
#### 4.5.1. Set Up the Unit Test Environment
|
|
|
|
**Create the stub Tester class:**
|
|
Do the following in directory `Ref/MathSender`:
|
|
|
|
1. Run `mkdir -p test/ut` to create the directory where
|
|
the unit tests will reside.
|
|
|
|
1. Run the command `fprime-util impl --ut`.
|
|
It should generate files `Tester.cpp` and `Tester.hpp`.
|
|
|
|
1. Move these files to the `test/ut` directory:
|
|
|
|
```bash
|
|
mv Tester.* test/ut
|
|
```
|
|
|
|
**Create a stub main.cpp file:**
|
|
Now go to the directory `Ref/MathSender/test/ut`.
|
|
In that directory, create a file `main.cpp` with the
|
|
following contents:
|
|
|
|
```c++
|
|
#include "Tester.hpp"
|
|
|
|
int main(int argc, char **argv) {
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|
|
```
|
|
|
|
This file is a stub for running tests using the
|
|
[Google Test framework](https://github.com/google/googletest).
|
|
Right now there aren't any tests to run; we will add one
|
|
in the next section.
|
|
|
|
**Update Ref/MathSender/CMakeLists.txt:**
|
|
Go back to the directory `Ref/MathSender`.
|
|
Add the following lines to `CMakeLists.txt`:
|
|
|
|
```cmake
|
|
# Register the unit test build
|
|
set(UT_SOURCE_FILES
|
|
"${CMAKE_CURRENT_LIST_DIR}/MathSender.fpp"
|
|
"${CMAKE_CURRENT_LIST_DIR}/test/ut/Tester.cpp"
|
|
"${CMAKE_CURRENT_LIST_DIR}/test/ut/main.cpp"
|
|
)
|
|
register_fprime_ut()
|
|
```
|
|
|
|
This code tells the build system how to build
|
|
and run the unit tests.
|
|
|
|
**Run the build:**
|
|
Now we can check that the unit test build is working.
|
|
|
|
1. If you have not yet run `fprime-util generate --ut`,
|
|
then do so now.
|
|
This step generates the CMake build cache for the unit
|
|
tests.
|
|
|
|
1. Run `fprime-util build --ut`.
|
|
Everything should build without errors.
|
|
|
|
**Inspect the generated code:**
|
|
The generated code is located at
|
|
`Ref/build-fprime-automatic-native-ut/Ref/MathSender`.
|
|
This directory contains two auto-generated classes:
|
|
|
|
1. `MathSenderGTestBase`: This is the direct base
|
|
class of `Tester`.
|
|
It provides a test interface implemented with Google Test
|
|
macros.
|
|
|
|
1. `MathSenderTesterBase`: This is the direct base
|
|
class of `MathSenderGTestBase`.
|
|
It provides basic features such as histories of port
|
|
invocations.
|
|
It is not specific to Google Test, so you can
|
|
use this class without Google Test if desired.
|
|
|
|
You can look at the header files for these generated classes
|
|
to see what operations they provide.
|
|
In the next sections we will provide some example uses
|
|
of these operations.
|
|
|
|
<a name="The-MathSender-Component_Write-and-Run-Unit-Tests_Write-and-Run-One-Test"></a>
|
|
#### 4.5.2. Write and Run One Test
|
|
|
|
Now we will write a unit test that exercises the
|
|
`DO_MATH` command.
|
|
We will do this in three phases:
|
|
|
|
1. In the `Tester` class, add a helper function for sending the command and
|
|
checking the responses.
|
|
That way multiple tests can reuse the same code.
|
|
|
|
1. In the `Tester` class, write a test function that
|
|
calls the helper to run a test.
|
|
|
|
1. In the `main` function, write a Google Test macro
|
|
that invokes the test function.
|
|
|
|
1. Run the test.
|
|
|
|
**Add a helper function:**
|
|
Go into the directory `Ref/MathSender/test/ut`.
|
|
In the file `Tester.hpp`, add the following lines
|
|
to the section entitled "Helper methods":
|
|
|
|
```c++
|
|
//! Test a DO_MATH command
|
|
void testDoMath(MathOp op);
|
|
```
|
|
|
|
In the file `Tester.cpp`, add the corresponding
|
|
function body:
|
|
|
|
```c++
|
|
void Tester ::
|
|
testDoMath(MathOp op)
|
|
{
|
|
|
|
// Pick values
|
|
|
|
const F32 val1 = 2.0;
|
|
const F32 val2 = 3.0;
|
|
|
|
// Send the command
|
|
|
|
// pick a command sequence number
|
|
const U32 cmdSeq = 10;
|
|
// send DO_MATH command
|
|
this->sendCmd_DO_MATH(0, cmdSeq, val1, op, val2);
|
|
// retrieve the message from the message queue and dispatch the command to the handler
|
|
this->component.doDispatch();
|
|
|
|
// Verify command receipt and response
|
|
|
|
// verify command response was sent
|
|
ASSERT_CMD_RESPONSE_SIZE(1);
|
|
// verify the command response was correct as expected
|
|
ASSERT_CMD_RESPONSE(0, MathSenderComponentBase::OPCODE_DO_MATH, cmdSeq, Fw::CmdResponse::OK);
|
|
|
|
// Verify operation request on mathOpOut
|
|
|
|
// verify that that one output port was invoked overall
|
|
ASSERT_FROM_PORT_HISTORY_SIZE(1);
|
|
// verify that the math operation port was invoked once
|
|
ASSERT_from_mathOpOut_SIZE(1);
|
|
// verify the arguments of the operation port
|
|
ASSERT_from_mathOpOut(0, val1, op, val2);
|
|
|
|
// Verify telemetry
|
|
|
|
// verify that 3 channels were written
|
|
ASSERT_TLM_SIZE(3);
|
|
// verify that the desired telemetry values were sent once
|
|
ASSERT_TLM_VAL1_SIZE(1);
|
|
ASSERT_TLM_VAL2_SIZE(1);
|
|
ASSERT_TLM_OP_SIZE(1);
|
|
// verify that the correct telemetry values were sent
|
|
ASSERT_TLM_VAL1(0, val1);
|
|
ASSERT_TLM_VAL2(0, val2);
|
|
ASSERT_TLM_OP(0, op);
|
|
|
|
// Verify event reports
|
|
|
|
// verify that one event was sent
|
|
ASSERT_EVENTS_SIZE(1);
|
|
// verify the expected event was sent once
|
|
ASSERT_EVENTS_COMMAND_RECV_SIZE(1);
|
|
// verify the correct event arguments were sent
|
|
ASSERT_EVENTS_COMMAND_RECV(0, val1, op, val2);
|
|
|
|
}
|
|
```
|
|
|
|
This function is parameterized over different
|
|
operations.
|
|
It is divided into five sections: sending the command,
|
|
checking the command response, checking the output on
|
|
`mathOpOut`, checking telemetry, and checking events.
|
|
The comments explain what is happening in each section.
|
|
For further information about the F Prime unit test
|
|
interface, see the F Prime User's Guide.
|
|
|
|
Notice that after sending the command to the component, we call
|
|
the function `doDispatch` on the component.
|
|
We do this in order to simulate the behavior of the active
|
|
component in a unit test environment.
|
|
In a flight configuration, the component has its own thread,
|
|
and the thread blocks on the `doDispatch` call until another
|
|
thread puts a message on the queue.
|
|
In a unit test context, there is only one thread, so the pattern
|
|
is to place work on the queue and then call `doDispatch` on
|
|
the same thread.
|
|
|
|
There are a couple of pitfalls to watch out for with this pattern:
|
|
|
|
1. If you put work on the queue and forget to call `doDispatch`,
|
|
the work won't get dispatched.
|
|
Likely this will cause a unit test failure.
|
|
|
|
1. If you call `doDispatch` without putting work on the queue,
|
|
the unit test will block until you kill the process (e.g.,
|
|
with control-C).
|
|
|
|
**Write a test function:**
|
|
Next we will write a test function that calls
|
|
`testDoMath` to test an `ADD` operation.
|
|
In `Tester.hpp`, add the following line in the
|
|
section entitled "Tests":
|
|
|
|
```c++
|
|
//! Test an ADD command
|
|
void testAddCommand();
|
|
```
|
|
|
|
In `Tester.cpp`, add the corresponding function
|
|
body:
|
|
|
|
```c++
|
|
void Tester ::
|
|
testAddCommand()
|
|
{
|
|
this->testDoMath(MathOp::ADD);
|
|
}
|
|
```
|
|
|
|
This function calls `testDoMath` to test an `ADD` command.
|
|
|
|
**Write a test macro:**
|
|
Add the following code to the file `main.cpp`,
|
|
before the definition of the `main` function:
|
|
|
|
```c++
|
|
TEST(Nominal, AddCommand) {
|
|
Ref::Tester tester;
|
|
tester.testAddCommand();
|
|
}
|
|
```
|
|
|
|
The `TEST` macro is an instruction to Google Test to run a test.
|
|
`Nominal` is the name of a test suite.
|
|
We put this test in the `Nominal` suite because it addresses
|
|
nominal (expected) behavior.
|
|
`AddCommand` is the name of the test.
|
|
Inside the body of the macro, the first line declares a new
|
|
object `tester` of type `Tester`.
|
|
We typically declare a new object for each unit test, so that
|
|
each test starts in a fresh state.
|
|
The second line invokes the function `testAddCommand`
|
|
that we wrote in the previous section.
|
|
|
|
**Run the test:**
|
|
Go back to directory `Ref/MathSender`.
|
|
Run the command `fprime-util check`.
|
|
The build system should compile and run the unit
|
|
tests.
|
|
You should see output indicating that the test ran
|
|
and passed.
|
|
|
|
As an exercise, try the following:
|
|
|
|
1. Change the behavior of the component
|
|
so that it does something incorrect.
|
|
For example, try adding one to a telemetry
|
|
value before emitting it.
|
|
|
|
1. Rerun the test and observe what happens.
|
|
|
|
<a name="The-MathSender-Component_Write-and-Run-Unit-Tests_Write-and-Run-More-Tests"></a>
|
|
#### 4.5.3. Write and Run More Tests
|
|
|
|
**Add more command tests:**
|
|
Try to follow the pattern given in the previous
|
|
section to add three more tests, one each
|
|
for operations `SUB`, `MUL`, and `DIV`.
|
|
Most of the work should be done in the helper
|
|
that we already wrote.
|
|
Each new test requires just a short test function
|
|
and a short test macro.
|
|
|
|
Run the tests to make sure everything compiles and
|
|
the tests pass.
|
|
|
|
**Add a result test:**
|
|
Add a test for exercising the scenario in which the `MathReceiver`
|
|
component sends a result back to `MathSender`.
|
|
|
|
1. Add the following function signature in the "Tests"
|
|
section of to `Tester.hpp`:
|
|
|
|
```c++
|
|
//! Test receipt of a result
|
|
void testResult();
|
|
```
|
|
|
|
1. Add the corresponding function body in `Tester.cpp`:
|
|
|
|
```c++
|
|
void Tester ::
|
|
testResult()
|
|
{
|
|
// Generate an expected result
|
|
const F32 result = 10.0;
|
|
// reset all telemetry and port history
|
|
this->clearHistory();
|
|
// call result port with result
|
|
this->invoke_to_mathResultIn(0, result);
|
|
// retrieve the message from the message queue and dispatch the command to the handler
|
|
this->component.doDispatch();
|
|
// verify one telemetry value was written
|
|
ASSERT_TLM_SIZE(1);
|
|
// verify the desired telemetry channel was sent once
|
|
ASSERT_TLM_RESULT_SIZE(1);
|
|
// verify the values of the telemetry channel
|
|
ASSERT_TLM_RESULT(0, result);
|
|
// verify one event was sent
|
|
ASSERT_EVENTS_SIZE(1);
|
|
// verify the expected event was sent once
|
|
ASSERT_EVENTS_RESULT_SIZE(1);
|
|
// verify the expect value of the event
|
|
ASSERT_EVENTS_RESULT(0, result);
|
|
}
|
|
```
|
|
|
|
This code is similar to the helper function in the previous section.
|
|
The main difference is that it invokes a port directly
|
|
(the `mathResultIn` port) instead of sending a command.
|
|
|
|
1. Add the following test macro to `main.cpp`:
|
|
|
|
```c++
|
|
TEST(Nominal, Result) {
|
|
Ref::Tester tester;
|
|
tester.testResult();
|
|
}
|
|
```
|
|
|
|
1. Run the tests.
|
|
Again you can try altering something in the component code
|
|
to see what effect it has on the test output.
|
|
|
|
<a name="math-sender_exercise"></a>
|
|
<a name="The-MathSender-Component_Write-and-Run-Unit-Tests_Exercise-Random-Testing"></a>
|
|
#### 4.5.4. Exercise: Random Testing
|
|
|
|
F Prime provides a module called `STest`
|
|
that provides helper classes and functions for writing
|
|
unit tests.
|
|
As an exercise, use the interface provided by
|
|
`STest/STest/Pick.hpp` to pick random values to use in the
|
|
tests instead of using hard-coded values such as 2.0, 3.0,
|
|
and 10.
|
|
|
|
**Modifying the code:** You will need to do the following:
|
|
|
|
1. Add `#include "STest/Pick/Pick.hpp"` to `Tester.cpp`.
|
|
|
|
1. Add the following
|
|
line to `Ref/MathSender/CMakeLists.txt`, before `register_fprime_ut`:
|
|
|
|
```cmake
|
|
set(UT_MOD_DEPS STest)
|
|
```
|
|
|
|
This line tells the build system to make the unit test build
|
|
depend on the `STest` build module.
|
|
|
|
1. Add `#include STest/Random/Random.hpp` to `main.cpp`.
|
|
|
|
1. Add the following line to the `main` function of `main.cpp`,
|
|
just before the return statement:
|
|
|
|
```c++
|
|
STest::Random::seed();
|
|
```
|
|
|
|
This line seeds the random number generator used by STest.
|
|
|
|
**Running the tests:**
|
|
Recompile and rerun the tests.
|
|
Now go to
|
|
`Ref/build-fprime-automatic-native-ut/Ref/MathSender` and inspect the
|
|
file `seed-history`.
|
|
This file is a log of random seed values.
|
|
Each line represents the seed used in the corresponding run.
|
|
|
|
**Fixing the random seed:**
|
|
Sometimes you may want to run a test with a particular seed value,
|
|
e.g., for replay debugging.
|
|
To do this, put the seed value into a file `seed` in the same
|
|
directory as `seed-history`.
|
|
If the file `seed` exists, then STest will use the seed it contains instead
|
|
of generating a new seed.
|
|
|
|
Try the following:
|
|
|
|
1. Copy the last value _S_ of `seed-history` into `seed`.
|
|
|
|
1. In `Ref/MathSender`, re-run the unit tests a few times.
|
|
|
|
1. Inspect `Ref/build-fprime-automatic-native-ut/Ref/MathSender/seed-history`.
|
|
You should see that the value _S_ was used in the runs you just did
|
|
(corresponding to the last few entries in `seed-history`).
|
|
|
|
<a name="The-MathSender-Component_Reference-Implementation"></a>
|
|
### 4.6. Reference Implementation
|
|
|
|
A reference implementation for this section is available at
|
|
`docs/Tutorials/MathComponent/MathSender`.
|
|
|
|
<a name="The-MathReceiver-Component"></a>
|
|
## 5. The MathReceiver Component
|
|
|
|
Now we will build and test the `MathReceiver` component.
|
|
We will use the same five steps as for the
|
|
<a href="#math-sender">`MathSender` component</a>.
|
|
|
|
<a name="The-MathReceiver-Component_Construct-the-FPP-Model"></a>
|
|
### 5.1. Construct the FPP Model
|
|
|
|
**Create the MathReceiver directory:**
|
|
Create the directory `Ref/MathReceiver`.
|
|
|
|
**Create the FPP model file:**
|
|
In directory `Ref/MathReceiver`, create a file
|
|
`MathReceiver.fpp` with the following contents:
|
|
|
|
```fpp
|
|
module Ref {
|
|
|
|
@ Component for receiving and performing a math operation
|
|
queued component MathReceiver {
|
|
|
|
# ----------------------------------------------------------------------
|
|
# General ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Port for receiving the math operation
|
|
async input port mathOpIn: MathOp
|
|
|
|
@ Port for returning the math result
|
|
output port mathResultOut: MathResult
|
|
|
|
@ The rate group scheduler input
|
|
sync input port schedIn: Svc.Sched
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Special ports
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Command receive
|
|
command recv port cmdIn
|
|
|
|
@ Command registration
|
|
command reg port cmdRegOut
|
|
|
|
@ Command response
|
|
command resp port cmdResponseOut
|
|
|
|
@ Event
|
|
event port eventOut
|
|
|
|
@ Parameter get
|
|
param get port prmGetOut
|
|
|
|
@ Parameter set
|
|
param set port prmSetOut
|
|
|
|
@ Telemetry
|
|
telemetry port tlmOut
|
|
|
|
@ Text event
|
|
text event port textEventOut
|
|
|
|
@ Time get
|
|
time get port timeGetOut
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Parameters
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ The multiplier in the math operation
|
|
param FACTOR: F32 default 1.0 id 0 \
|
|
set opcode 10 \
|
|
save opcode 11
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Events
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Factor updated
|
|
event FACTOR_UPDATED(
|
|
val: F32 @< The factor value
|
|
) \
|
|
severity activity high \
|
|
id 0 \
|
|
format "Factor updated to {f}" \
|
|
throttle 3
|
|
|
|
@ Math operation performed
|
|
event OPERATION_PERFORMED(
|
|
val: MathOp @< The operation
|
|
) \
|
|
severity activity high \
|
|
id 1 \
|
|
format "{} operation performed"
|
|
|
|
@ Event throttle cleared
|
|
event THROTTLE_CLEARED \
|
|
severity activity high \
|
|
id 2 \
|
|
format "Event throttle cleared"
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Commands
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ Clear the event throttle
|
|
async command CLEAR_EVENT_THROTTLE \
|
|
opcode 0
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Telemetry
|
|
# ----------------------------------------------------------------------
|
|
|
|
@ The operation
|
|
telemetry OPERATION: MathOp id 0
|
|
|
|
@ Multiplication factor
|
|
telemetry FACTOR: F32 id 1
|
|
|
|
}
|
|
|
|
}
|
|
```
|
|
|
|
This code defines a component `Ref.MathReceiver`.
|
|
The component is **queued**, which means it has a queue
|
|
but no thread.
|
|
Work occurs when the thread of another component invokes
|
|
the `schedIn` port of this component.
|
|
|
|
We have divided the specifiers of this component into six groups:
|
|
|
|
1. **General ports:** There are three ports:
|
|
an input port `mathOpIn` for receiving a math operation,
|
|
an output port `mathResultOut` for sending a math result, and
|
|
an input port `schedIn` for receiving invocations from the scheduler.
|
|
`mathOpIn` is asynchronous.
|
|
That means invocations of `mathOpIn` put messages on a queue.
|
|
`schedIn` is synchronous.
|
|
That means invocations of `schedIn` immediately call the
|
|
handler function to do work.
|
|
|
|
1. **Special ports:**
|
|
As before, there are special ports for commands, events, telemetry,
|
|
and time.
|
|
There are also special ports for getting and setting parameters.
|
|
We will explain the function of these ports below.
|
|
|
|
1. **Parameters:** There is one **parameter**.
|
|
A parameter is a constant that is configurable by command.
|
|
In this case there is one parameter `FACTOR`.
|
|
It has the default value 1.0 until its value is changed by command.
|
|
When doing math, the `MathReceiver` component performs the requested
|
|
operation and then multiplies by this factor.
|
|
For example, if the arguments of the `mathOpIn` port
|
|
are _v1_, `ADD`, and _v2_, and the factor is _f_,
|
|
then the result sent on `mathResultOut` is
|
|
_(v1 + v2) f_.
|
|
|
|
1. **Events:** There are three event reports:
|
|
|
|
1. `FACTOR_UPDATED`: Emitted when the `FACTOR` parameter
|
|
is updated by command.
|
|
This event is **throttled** to a limit of three.
|
|
That means that after the event is emitted three times
|
|
it will not be emitted any more, until the throttling
|
|
is cleared by command (see below).
|
|
|
|
1. `OPERATION_PERFORMED`: Emitted when this component
|
|
performs a math operation.
|
|
|
|
1. `THROTTLE_CLEARED`: Emitted when the event throttling
|
|
is cleared.
|
|
|
|
1. **Commands:** There is one command for clearing
|
|
the event throttle.
|
|
|
|
1. **Telemetry:**
|
|
There two telemetry channels: one for reporting
|
|
the last operation received and one for reporting
|
|
the factor parameter.
|
|
|
|
For the parameters, events, commands, and telemetry, we chose
|
|
to put in all the opcodes and identifiers explicitly.
|
|
These can also be left implicit, as in the `MathSender`
|
|
component example.
|
|
For more information, see
|
|
[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Components).
|
|
|
|
<a name="The-MathReceiver-Component_Add-the-Model-to-the-Project"></a>
|
|
### 5.2. Add the Model to the Project
|
|
|
|
Follow the steps given for the
|
|
<a href="#math-sender_add-model">`MathSender` component</a>.
|
|
|
|
<a name="The-MathReceiver-Component_Build-the-Stub-Implementation"></a>
|
|
### 5.3. Build the Stub Implementation
|
|
|
|
Follow the same steps as for the
|
|
<a href="#math-sender_build-stub">`MathSender` component</a>.
|
|
|
|
<a name="The-MathReceiver-Component_Complete-the-Implementation"></a>
|
|
### 5.4. Complete the Implementation
|
|
|
|
**Fill in the mathOpIn handler:**
|
|
In `MathReceiver.cpp`, complete the implementation of
|
|
`mathOpIn_handler` so that it looks like this:
|
|
|
|
```cpp
|
|
void MathReceiver ::
|
|
mathOpIn_handler(
|
|
const NATIVE_INT_TYPE portNum,
|
|
F32 val1,
|
|
const MathOp& op,
|
|
F32 val2
|
|
)
|
|
{
|
|
|
|
// Get the initial result
|
|
F32 res = 0.0;
|
|
switch (op.e) {
|
|
case MathOp::ADD:
|
|
res = val1 + val2;
|
|
break;
|
|
case MathOp::SUB:
|
|
res = val1 - val2;
|
|
break;
|
|
case MathOp::MUL:
|
|
res = val1 * val2;
|
|
break;
|
|
case MathOp::DIV:
|
|
res = val1 / val2;
|
|
break;
|
|
default:
|
|
FW_ASSERT(0, op.e);
|
|
break;
|
|
}
|
|
|
|
// Get the factor value
|
|
Fw::ParamValid valid;
|
|
F32 factor = paramGet_FACTOR(valid);
|
|
FW_ASSERT(
|
|
valid.e == Fw::ParamValid::VALID || valid.e == Fw::ParamValid::DEFAULT,
|
|
valid.e
|
|
);
|
|
|
|
// Multiply result by factor
|
|
res *= factor;
|
|
|
|
// Emit telemetry and events
|
|
this->log_ACTIVITY_HI_OPERATION_PERFORMED(op);
|
|
this->tlmWrite_OPERATION(op);
|
|
|
|
// Emit result
|
|
this->mathResultOut_out(0, res);
|
|
|
|
}
|
|
```
|
|
|
|
This code does the following:
|
|
|
|
1. Compute an initial result based on the input values and
|
|
the requested operation.
|
|
|
|
1. Get the value of the factor parameter.
|
|
Check that the value is a valid value from the parameter
|
|
database or a default parameter value.
|
|
|
|
1. Multiply the initial result by the factor to generate
|
|
the final result.
|
|
|
|
1. Emit telemetry and events.
|
|
|
|
1. Emit the result.
|
|
|
|
Note that in step 1, `op` is an enum (a C++ class type), and `op.e`
|
|
is the corresponding numeric value (an integer type).
|
|
Note also that in the `default` case we deliberately fail
|
|
an assertion.
|
|
This is a standard pattern for exhaustive case checking.
|
|
We should never hit the assertion.
|
|
If we do, then a bug has occurred: we missed a case.
|
|
|
|
**Fill in the schedIn handler:**
|
|
In `MathReceiver.cpp`, complete the implementation of
|
|
`schedIn_handler` so that it looks like this:
|
|
|
|
```c++
|
|
void MathReceiver ::
|
|
schedIn_handler(
|
|
const NATIVE_INT_TYPE portNum,
|
|
NATIVE_UINT_TYPE context
|
|
)
|
|
{
|
|
U32 numMsgs = this->m_queue.getNumMsgs();
|
|
for (U32 i = 0; i < numMsgs; ++i) {
|
|
(void) this->doDispatch();
|
|
}
|
|
}
|
|
```
|
|
|
|
This code dispatches all the messages on the queue.
|
|
Note that for a queued component, we have to do this
|
|
dispatch explicitly in the `schedIn` handler.
|
|
For an active component, the framework auto-generates
|
|
the dispatch code.
|
|
|
|
**Fill in the CLEAR_EVENT_THROTTLE command handler:**
|
|
In `MathReceiver.cpp`, complete the implementation of
|
|
`CLEAR_EVENT_THROTTLE_cmdHandler` so that it looks like this:
|
|
|
|
```c++
|
|
void MathReceiver ::
|
|
CLEAR_EVENT_THROTTLE_cmdHandler(
|
|
const FwOpcodeType opCode,
|
|
const U32 cmdSeq
|
|
)
|
|
{
|
|
// clear throttle
|
|
this->log_ACTIVITY_HI_FACTOR_UPDATED_ThrottleClear();
|
|
// send event that throttle is cleared
|
|
this->log_ACTIVITY_HI_THROTTLE_CLEARED();
|
|
// reply with completion status
|
|
this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
|
|
}
|
|
```
|
|
|
|
The call to `log_ACTIVITY_HI_FACTOR_UPDATED_ThrottleClear` clears
|
|
the throttling of the `FACTOR_UPDATED` event.
|
|
The next two lines send a notification event and send
|
|
a command response.
|
|
|
|
**Add a parameterUpdated function:**
|
|
Add the following function to `MathReceiver.cpp`.
|
|
You will need to add the corresponding function header
|
|
to `MathReceiver.hpp`.
|
|
|
|
```c++
|
|
void MathReceiver ::
|
|
parameterUpdated(FwPrmIdType id)
|
|
{
|
|
switch (id) {
|
|
case PARAMID_FACTOR: {
|
|
Fw::ParamValid valid;
|
|
F32 val = this->paramGet_FACTOR(valid);
|
|
FW_ASSERT(
|
|
valid.e == Fw::ParamValid::VALID || valid.e == Fw::ParamValid::DEFAULT,
|
|
valid.e
|
|
);
|
|
this->log_ACTIVITY_HI_FACTOR_UPDATED(val);
|
|
break;
|
|
}
|
|
default:
|
|
FW_ASSERT(0, id);
|
|
break;
|
|
}
|
|
}
|
|
```
|
|
|
|
This code implements an optional function that, if present,
|
|
is called when a parameter is updated by command.
|
|
The parameter identifier is passed in as the `id` argument
|
|
of the function.
|
|
Here we do the following:
|
|
|
|
1. If the parameter identifier is `PARAMID_FACTOR` (the parameter
|
|
identifier corresponding to the `FACTOR` parameter,
|
|
then get the parameter value and emit an event report.
|
|
|
|
1. Otherwise fail an assertion.
|
|
This code should never run, because there are no other
|
|
parameters.
|
|
|
|
<a name="The-MathReceiver-Component_Write-and-Run-Unit-Tests"></a>
|
|
### 5.5. Write and Run Unit Tests
|
|
|
|
<a name="The-MathReceiver-Component_Write-and-Run-Unit-Tests_Set-up-the-Unit-Test-Environment"></a>
|
|
#### 5.5.1. Set up the Unit Test Environment
|
|
|
|
1. Follow the steps given for the
|
|
<a href="#math-sender_unit_setup">`MathSender` component</a>.
|
|
|
|
1. Follow the steps given under **Modifying the code**
|
|
for the
|
|
<a href="#math-sender_exercise">random testing exercise</a>,
|
|
so that you can use STest to pick random values.
|
|
|
|
<a name="The-MathReceiver-Component_Write-and-Run-Unit-Tests_Add-Helper-Code"></a>
|
|
#### 5.5.2. Add Helper Code
|
|
|
|
**Add a ThrottleState enum class:**
|
|
Add the following code to the beginning of the
|
|
`Tester` class in `Tester.hpp`:
|
|
|
|
```c++
|
|
private:
|
|
|
|
// ----------------------------------------------------------------------
|
|
// Types
|
|
// ----------------------------------------------------------------------
|
|
|
|
enum class ThrottleState {
|
|
THROTTLED,
|
|
NOT_THROTTLED
|
|
};
|
|
```
|
|
|
|
This code defines a C++ enum class for recording whether an
|
|
event is throttled.
|
|
|
|
**Add helper functions:**
|
|
Add each of the functions described below to the
|
|
"Helper methods" section of `Tester.cpp`.
|
|
For each function, you must add
|
|
the corresponding function prototype to `Tester.hpp`.
|
|
After adding each function, compile the unit tests
|
|
to make sure that everything still compiles.
|
|
Fix any errors that occur.
|
|
|
|
Add a `pickF32Value` function.
|
|
|
|
```c++
|
|
F32 Tester ::
|
|
pickF32Value()
|
|
{
|
|
const F32 m = 10e6;
|
|
return m * (1.0 - 2 * STest::Pick::inUnitInterval());
|
|
}
|
|
```
|
|
|
|
This function picks a random `F32` value in the range
|
|
_[ -10^6, 10^6 ]_.
|
|
|
|
Add a `setFactor` function.
|
|
|
|
```c++
|
|
void Tester ::
|
|
setFactor(
|
|
F32 factor,
|
|
ThrottleState throttleState
|
|
)
|
|
{
|
|
// clear history
|
|
this->clearHistory();
|
|
// set the parameter
|
|
this->paramSet_FACTOR(factor, Fw::ParamValid::VALID);
|
|
const U32 instance = STest::Pick::any();
|
|
const U32 cmdSeq = STest::Pick::any();
|
|
this->paramSend_FACTOR(instance, cmdSeq);
|
|
if (throttleState == ThrottleState::NOT_THROTTLED) {
|
|
// verify the parameter update notification event was sent
|
|
ASSERT_EVENTS_SIZE(1);
|
|
ASSERT_EVENTS_FACTOR_UPDATED_SIZE(1);
|
|
ASSERT_EVENTS_FACTOR_UPDATED(0, factor);
|
|
}
|
|
else {
|
|
ASSERT_EVENTS_SIZE(0);
|
|
}
|
|
}
|
|
```
|
|
|
|
This function does the following:
|
|
|
|
1. Clear the test history.
|
|
|
|
1. Send a command to the component to set the `FACTOR` parameter
|
|
to the value `factor`.
|
|
|
|
1. If `throttleState` is `NOT_THROTTLED`, then check
|
|
that the event was emitted.
|
|
Otherwise check that the event was throttled (not emitted).
|
|
|
|
Add a function `computeResult` to `Tester.cpp`.
|
|
|
|
```c++
|
|
F32 Tester ::
|
|
computeResult(
|
|
F32 val1,
|
|
MathOp op,
|
|
F32 val2,
|
|
F32 factor
|
|
)
|
|
{
|
|
F32 result = 0;
|
|
switch (op.e) {
|
|
case MathOp::ADD:
|
|
result = val1 + val2;
|
|
break;
|
|
case MathOp::SUB:
|
|
result = val1 - val2;
|
|
break;
|
|
case MathOp::MUL:
|
|
result = val1 * val2;
|
|
break;
|
|
case MathOp::DIV:
|
|
result = val1 / val2;
|
|
break;
|
|
default:
|
|
FW_ASSERT(0, op.e);
|
|
break;
|
|
}
|
|
result *= factor;
|
|
return result;
|
|
}
|
|
```
|
|
|
|
This function carries out the math computation of the
|
|
math component.
|
|
By running this function and comparing, we can
|
|
check the output of the component.
|
|
|
|
Add a `doMathOp` function to `Tester.cpp`.
|
|
|
|
```c++
|
|
void Tester ::
|
|
doMathOp(
|
|
MathOp op,
|
|
F32 factor
|
|
)
|
|
{
|
|
|
|
// pick values
|
|
const F32 val1 = pickF32Value();
|
|
const F32 val2 = pickF32Value();
|
|
|
|
// clear history
|
|
this->clearHistory();
|
|
|
|
// invoke operation port with add operation
|
|
this->invoke_to_mathOpIn(0, val1, op, val2);
|
|
// invoke scheduler port to dispatch message
|
|
const U32 context = STest::Pick::any();
|
|
this->invoke_to_schedIn(0, context);
|
|
|
|
// verify the result of the operation was returned
|
|
|
|
// check that there was one port invocation
|
|
ASSERT_FROM_PORT_HISTORY_SIZE(1);
|
|
// check that the port we expected was invoked
|
|
ASSERT_from_mathResultOut_SIZE(1);
|
|
// check that the component performed the operation correctly
|
|
const F32 result = computeResult(val1, op, val2, factor);
|
|
ASSERT_from_mathResultOut(0, result);
|
|
|
|
// verify events
|
|
|
|
// check that there was one event
|
|
ASSERT_EVENTS_SIZE(1);
|
|
// check that it was the op event
|
|
ASSERT_EVENTS_OPERATION_PERFORMED_SIZE(1);
|
|
// check that the event has the correct argument
|
|
ASSERT_EVENTS_OPERATION_PERFORMED(0, op);
|
|
|
|
// verify telemetry
|
|
|
|
// check that one channel was written
|
|
ASSERT_TLM_SIZE(1);
|
|
// check that it was the op channel
|
|
ASSERT_TLM_OPERATION_SIZE(1);
|
|
// check for the correct value of the channel
|
|
ASSERT_TLM_OPERATION(0, op);
|
|
|
|
}
|
|
```
|
|
|
|
This function is similar to the `doMath` helper function that
|
|
we wrote for the `MathSender` component.
|
|
Notice that the method for invoking a port is different.
|
|
Since the component is queued, we don't call `doDispatch`
|
|
directly.
|
|
Instead we invoke `schedIn`.
|
|
|
|
<a name="The-MathReceiver-Component_Write-and-Run-Unit-Tests_Write-and-Run-Tests"></a>
|
|
#### 5.5.3. Write and Run Tests
|
|
|
|
For each of the tests described below, you must add the
|
|
corresponding function prototype to `Tester.hpp`
|
|
and the corresponding test macro to `main.cpp`.
|
|
If you can't remember how to do it, look back at the
|
|
`MathSender` examples.
|
|
After writing each test, run all the tests and make sure
|
|
that they pass.
|
|
|
|
**Write an ADD test:**
|
|
Add the following function to the "Tests" section of `Tester.cpp`:
|
|
|
|
```c++
|
|
void Tester ::
|
|
testAdd()
|
|
{
|
|
// Set the factor parameter by command
|
|
const F32 factor = pickF32Value();
|
|
this->setFactor(factor, ThrottleState::NOT_THROTTLED);
|
|
// Do the add operation
|
|
this->doMathOp(MathOp::ADD, factor);
|
|
}
|
|
```
|
|
|
|
This function calls the `setFactor` helper function
|
|
to set the factor parameter.
|
|
Then it calls the `doMathOp` function to
|
|
do a math operation.
|
|
|
|
**Write a SUB test:**
|
|
Add the following function to the "Tests" section of `Tester.cpp`:
|
|
|
|
```c++
|
|
void Tester ::
|
|
testSub()
|
|
{
|
|
// Set the factor parameter by loading parameters
|
|
const F32 factor = pickF32Value();
|
|
this->paramSet_FACTOR(factor, Fw::ParamValid::VALID);
|
|
this->component.loadParameters();
|
|
// Do the operation
|
|
this->doMathOp(MathOp::SUB, factor);
|
|
}
|
|
```
|
|
|
|
This test is similar to `testAdd`, but it shows
|
|
another way to set a parameter.
|
|
`testAdd` showed how to set a parameter by command.
|
|
You can also set a parameter by initialization, as follows:
|
|
|
|
1. Call the `paramSet` function as shown.
|
|
This function sets the parameter value in
|
|
the part of the test harness that mimics the behavior of the
|
|
parameter database component.
|
|
|
|
1. Call the `loadParameters` function as shown.
|
|
In flight, the function `loadParameters` is typically called at the
|
|
start of FSW to load the parameters from the database;
|
|
here it loads the parameters from the test harness.
|
|
There is no command to update a parameter, so `parameterUpdated`
|
|
is not called, and no event is emitted.
|
|
|
|
As before, after setting the parameter we call `doMathOp`
|
|
to do the operation.
|
|
|
|
**Write a MUL test:**
|
|
This test is the same as the ADD test, except that it
|
|
uses MUL instead of add.
|
|
|
|
**Write a DIV test:**
|
|
This test is the same as the SUB test, except that it
|
|
uses DIV instead of SUB.
|
|
|
|
**Write a throttle test:**
|
|
Add the following function to the "Tests" section of `Tester.cpp`:
|
|
|
|
```c++
|
|
void Tester ::
|
|
testThrottle()
|
|
{
|
|
|
|
// send the number of commands required to throttle the event
|
|
// Use the autocoded value so the unit test passes if the
|
|
// throttle value is changed
|
|
const F32 factor = pickF32Value();
|
|
for (
|
|
U16 cycle = 0;
|
|
cycle < MathReceiverComponentBase::EVENTID_FACTOR_UPDATED_THROTTLE;
|
|
cycle++
|
|
) {
|
|
this->setFactor(factor, ThrottleState::NOT_THROTTLED);
|
|
}
|
|
|
|
// Event should now be throttled
|
|
this->setFactor(factor, ThrottleState::THROTTLED);
|
|
|
|
// send the command to clear the throttle
|
|
this->sendCmd_CLEAR_EVENT_THROTTLE(INSTANCE, CMD_SEQ);
|
|
// invoke scheduler port to dispatch message
|
|
const U32 context = STest::Pick::any();
|
|
this->invoke_to_schedIn(0, context);
|
|
// verify clear event was sent
|
|
ASSERT_EVENTS_SIZE(1);
|
|
ASSERT_EVENTS_THROTTLE_CLEARED_SIZE(1);
|
|
|
|
// Throttling should be cleared
|
|
this->setFactor(factor, ThrottleState::NOT_THROTTLED);
|
|
|
|
}
|
|
```
|
|
|
|
This test first loops over the throttle count, which is stored
|
|
for us in the constant `EVENTID_FACTOR_UPDATED_THROTTLE`
|
|
of the `MathReceiver` component base class.
|
|
On each iteration, it calls `setFactor`.
|
|
At the end of this loop, the `FACTOR_UPDATED` event should be
|
|
throttled.
|
|
|
|
Next the test calls `setFactor` with a second argument of
|
|
`ThrottleState::THROTTLED`.
|
|
This code checks that the event is throttled.
|
|
|
|
Next the test sends the command `CLEAR_EVENT_THROTTLE`,
|
|
checks for the corresponding notification event,
|
|
and checks that the throttling is cleared.
|
|
|
|
<a name="The-MathReceiver-Component_Reference-Implementation"></a>
|
|
### 5.6. Reference Implementation
|
|
|
|
A reference implementation for this section is available at
|
|
`docs/Tutorials/MathComponent/MathReceiver`.
|
|
|
|
<a name="The-MathReceiver-Component_Exercises"></a>
|
|
### 5.7. Exercises
|
|
|
|
<a name="The-MathReceiver-Component_Exercises_Adding-Telemetry"></a>
|
|
#### 5.7.1. Adding Telemetry
|
|
|
|
Add a telemetry channel that records the number of math
|
|
operations performed.
|
|
|
|
1. Add the channel to the FPP model.
|
|
|
|
1. In the component implementation class, add a member
|
|
variable `numMathOps` of type `U32`.
|
|
Initialize the variable to zero in the class constructor.
|
|
|
|
1. Revise the `mathOpIn` handler so that it increments
|
|
`numMathOps` and emits the updated value as telemetry.
|
|
|
|
1. Revise the unit tests to cover the new behavior.
|
|
|
|
<a name="The-MathReceiver-Component_Exercises_Error-Handling"></a>
|
|
#### 5.7.2. Error Handling
|
|
|
|
Think about what will happen if the floating-point
|
|
math operation performed by `MathReceiver` causes an error.
|
|
For example, suppose that `mathOpIn` is invoked with `op = DIV`
|
|
and `val2 = 0.0`.
|
|
What will happen?
|
|
As currently designed and implemented, the `MathReceiver`
|
|
component will perform the requested operation.
|
|
On some systems the result will be `INF` (floating-point infinity).
|
|
In this case, the result will be sent back to `MathSender`
|
|
and reported in the usual way.
|
|
On other systems, the hardware could issue a floating-point exception.
|
|
|
|
Suppose you wanted to handle the case of division by zero
|
|
explicitly.
|
|
How would you change the design?
|
|
Here are some questions to think about:
|
|
|
|
1. How would you check for division by zero?
|
|
Note that `val2 = 0.0` is not the only case in which a division
|
|
by zero error can occur.
|
|
It can also occur for very small values of `val2`.
|
|
|
|
1. Should the error be caught in `MathSender` or `MathReceiver`?
|
|
|
|
1. Suppose the design says that `MathSender` catches the error,
|
|
and so never sends requests to `MathReceiver` to divide by zero.
|
|
What if anything should `MathReceiver` do if it receives
|
|
a divide by zero request?
|
|
Carry out the operation normally?
|
|
Emit a warning?
|
|
Fail a FSW assertion?
|
|
|
|
1. If the error is caught by `MathReceiver`, does the
|
|
interface between the components have to change?
|
|
If so, how?
|
|
What should `MathSender` do if `MathReceiver`
|
|
reports an error instead of a valid result?
|
|
|
|
Revise the MathSender and MathReceiver components to implement your
|
|
ideas.
|
|
Add unit tests covering the new behavior.
|
|
|
|
<a name="Updating-the-Ref-Deployment"></a>
|
|
## 6. Updating the Ref Deployment
|
|
|
|
The next step in the tutorial is to define instances of the
|
|
`MathSender` and `MathReceiver` components and add them
|
|
to the `Ref` topology.
|
|
|
|
<a name="Updating-the-Ref-Deployment_Defining-the-Component-Instances"></a>
|
|
### 6.1. Defining the Component Instances
|
|
|
|
Go to the directory `Ref/Top` and open the file `instances.fpp`.
|
|
This file defines the instances used in the topology for the
|
|
`Ref` application.
|
|
Update this file as described below.
|
|
|
|
**Define the mathSender instance:**
|
|
At the end of the section entitled "Active component instances,"
|
|
add the following lines:
|
|
|
|
```fpp
|
|
instance mathSender: Ref.MathSender base id 0xE00 \
|
|
queue size Default.queueSize \
|
|
stack size Default.stackSize \
|
|
priority 100
|
|
```
|
|
|
|
This code defines an instance `mathSender` of component
|
|
`MathSender`.
|
|
It has **base identifier** 0xE00.
|
|
FPP adds the base identifier to each the relative identifier
|
|
defined in the component to compute the corresponding
|
|
identifier for the instance.
|
|
For example, component `MathSender` has a telemetry channel
|
|
`MathOp` with identifier 1, so instance `mathSender`
|
|
has a command `MathOp` with identifier 0xE01.
|
|
|
|
The following lines define the queue size, stack size,
|
|
and thread priority for the active component.
|
|
Here we give `mathSender` the default queue size
|
|
and stack size and a priority of 100.
|
|
|
|
**Define the mathReceiver instance:**
|
|
At the end of the section "Queued component instances,"
|
|
add the following lines:
|
|
|
|
```fpp
|
|
instance mathReceiver: Ref.MathReceiver base id 0x2700 \
|
|
queue size Default.queueSize
|
|
```
|
|
|
|
This code defines an instance `mathReceiver` of
|
|
component `MathReceiver`.
|
|
It has base identifier 0x2700 and the default queue size.
|
|
|
|
**More information:**
|
|
For more information on defining component instances,
|
|
see
|
|
[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Component-Instances).
|
|
|
|
<a name="Updating-the-Ref-Deployment_Updating-the-Topology"></a>
|
|
### 6.2. Updating the Topology
|
|
|
|
Go to the directory `Ref/Top` and open the file `topology.fpp`.
|
|
This file defines the topology for the `Ref` application.
|
|
Update this file as described below.
|
|
|
|
**Add the new instances:**
|
|
You should see a list of instances, each of which begins
|
|
with the keyword `instance`.
|
|
After the line `instance linuxTime`, add the following
|
|
lines:
|
|
|
|
```fpp
|
|
instance mathSender
|
|
instance mathReceiver
|
|
```
|
|
|
|
These lines add the `mathSender` and `mathReceiver`
|
|
instances to the topology.
|
|
|
|
**Check for unconnected ports:**
|
|
Run the following commands:
|
|
|
|
```bash
|
|
fprime-util fpp-check -u unconnected.txt
|
|
cat unconnected.txt
|
|
```
|
|
|
|
You should see a list of ports
|
|
that are unconnected in the `Ref` topology.
|
|
Those ports will include the ports for the new instances
|
|
`mathSender` and `mathReceiver`.
|
|
|
|
**Connect mathReceiver to rate group 1:**
|
|
Find the line that starts `connections RateGroups`.
|
|
This is the beginning of the definition of the `RateGroups`
|
|
connection graph.
|
|
Inside the block of that definition,
|
|
find the line
|
|
`rateGroup1Comp.RateGroupMemberOut[3] -> fileDownlink.Run`.
|
|
After that line, add the line
|
|
|
|
```fpp
|
|
rateGroup1Comp.RateGroupMemberOut[4] -> mathReceiver.schedIn
|
|
```
|
|
|
|
This line adds the connection that drives the `schedIn`
|
|
port of the `mathReceiver` component instance.
|
|
|
|
**Re-run the check for unconnected ports:**
|
|
When this capability exists, you will be able to see
|
|
that `mathReceiver.schedIn` is now connected
|
|
(it no longer appears in the list).
|
|
|
|
**Add the Math connections:**
|
|
Find the Uplink connections that begin with the line
|
|
`connections Uplink`.
|
|
After the block of that definition, add the following
|
|
lines:
|
|
|
|
```fpp
|
|
connections Math {
|
|
mathSender.mathOpOut -> mathReceiver.mathOpIn
|
|
mathReceiver.mathResultOut -> mathSender.mathResultIn
|
|
}
|
|
```
|
|
|
|
These lines add the connections between the `mathSender`
|
|
and `mathReceiver` instances.
|
|
|
|
**Re-run the check for unconnected ports:**
|
|
When this capability exists, you will be able to see
|
|
that the `mathSender` and `mathReceiver` ports are connected.
|
|
|
|
**More information:**
|
|
For more information on defining topologies,
|
|
see
|
|
[_The FPP User's Guide_](https://fprime-community.github.io/fpp/fpp-users-guide.html#Defining-Topologies).
|
|
|
|
<a name="Updating-the-Ref-Deployment_Building-the-Ref-Deployment"></a>
|
|
### 6.3. Building the Ref Deployment
|
|
|
|
Go to the `Ref` directory.
|
|
Run `fprime-util build --jobs 4`.
|
|
The updated deployment should build without errors.
|
|
The generated files are located at
|
|
`Ref/build-fprime-automatic-native/Ref/Top`.
|
|
|
|
<a name="Updating-the-Ref-Deployment_Visualizing-the-Ref-Topology"></a>
|
|
### 6.4. Visualizing the Ref Topology
|
|
|
|
Now we will see how to create a visualization (graphical rendering)
|
|
of the Ref topology.
|
|
|
|
**Generate the layout:**
|
|
For this step, we will use the F Prime Layout (FPL) tool.
|
|
If FPL is not installed on your system, then install it how:
|
|
clone [this repository](https://github.com/fprime-community/fprime-layout)
|
|
and follow the instructions.
|
|
|
|
In directory `Ref/Top`, run the following commands in an sh-compatible
|
|
shell such as bash.
|
|
If you are using a different shell, you can run `sh`
|
|
to enter the `sh` shell, run these commands, and enter
|
|
`exit` when done.
|
|
Or you can stay in your preferred shell and adjust these commands
|
|
appropriately.
|
|
|
|
```bash
|
|
cp ../build-fprime-automatic-native/Ref/Top/RefTopologyAppAi.xml .
|
|
mkdir visual
|
|
cd visual
|
|
fpl-extract-xml < ../RefTopologyAppAi.xml
|
|
mkdir Ref
|
|
for file in `ls *.xml`
|
|
do
|
|
echo "laying out $file"
|
|
base=`basename $file .xml`
|
|
fpl-convert-xml $file | fpl-layout > Ref/$base.json
|
|
done
|
|
```
|
|
|
|
This step extracts the connection graphs from the topology XML and
|
|
converts each one to a JSON layout file.
|
|
|
|
**Render the layout:**
|
|
For this step, we will use the F Prime Visualizer (FPV) tool.
|
|
If FPV is not installed on your system, then install it now:
|
|
clone [this repository](https://github.com/fprime-community/fprime-visual)
|
|
and follow the instructions.
|
|
|
|
In directory `Ref/Top`, run the following commands in an sh-compatible
|
|
shell.
|
|
Replace `[path to fpv root]` with the path to the
|
|
root of the FPV repo on your system.
|
|
|
|
```bash
|
|
echo DATA_FOLDER=Ref/ > .fpv-env
|
|
nodemon [path to fpv root]/server/index.js ./.fpv-env
|
|
```
|
|
|
|
You should see the FPV server application start up on the
|
|
console.
|
|
|
|
Now open a browser and navigate to `http://localhost:3000`.
|
|
You should see a Topology menu at the top of the window
|
|
and a rendering of the Command topology below.
|
|
Select Math from the topology menu.
|
|
You should see a rendering of the Math topology.
|
|
It should look similar to the
|
|
<a href="#math-top">topology diagram shown above</a>.
|
|
|
|
You can use the menu to view other topology graphs.
|
|
When you are done, close the browser window and
|
|
type control-C in the console to shut down the FPV server.
|
|
|
|
<a name="Updating-the-Ref-Deployment_Reference-Implementation"></a>
|
|
### 6.5. Reference Implementation
|
|
|
|
A reference implementation for this section is available at
|
|
`docs/Tutorials/MathComponent/Top`.
|
|
To build this implementation, copy the files
|
|
`instances.fpp` and `topology.fpp` from
|
|
that directory to `Ref/Top`.
|
|
|
|
<a name="Running-the-Ref-Deployment"></a>
|
|
## 7. Running the Ref Deployment
|
|
|
|
Now we will use the F Prime Ground Data System (GDS) to run the Ref deployment.
|
|
Go to the `Ref` directory and run `fprime-gds`.
|
|
You should see some activity on the console.
|
|
The system is starting the Ref deployment executable, starting the GDS,
|
|
and connecting them over the local network on your machine.
|
|
After several seconds, a browser window should appear.
|
|
|
|
<a name="Running-the-Ref-Deployment_Sending-a-Command"></a>
|
|
### 7.1. Sending a Command
|
|
|
|
At the top of the window are several buttons, each of which corresponds to
|
|
a GDS view.
|
|
Select the Commanding button (this is the view that is selected
|
|
when you first start the GDS).
|
|
In the Mnemonic menu, start typing `mathSender.DO_MATH` in the text box.
|
|
As you type, the GDS will filter the menu selections.
|
|
When only one choice remains, stop typing and press return.
|
|
You should see three boxes appear:
|
|
|
|
1. A text box for entering `val1`.
|
|
|
|
1. A menu for entering `op`.
|
|
|
|
1. A text box for entering `val2`.
|
|
|
|
Fill in the arguments corresponding to the operation `1 + 2`.
|
|
You can use the tab key to move between the boxes.
|
|
When you have done this, click the Send Command button.
|
|
You should see a table entry at the bottom of the window
|
|
indicating that the command was sent.
|
|
|
|
<a name="Running-the-Ref-Deployment_Checking-Events"></a>
|
|
### 7.2. Checking Events
|
|
|
|
Now click on the Events button at the top of the window.
|
|
The view changes to the Events tab.
|
|
You should see events indicating that the command you sent was
|
|
dispatched, received, and completed.
|
|
You should also see events indicating that `mathReceiver`
|
|
performed an `ADD` operation and `mathSender`
|
|
received a result of 3.0.
|
|
|
|
<a name="Running-the-Ref-Deployment_Checking-Telemetry"></a>
|
|
### 7.3. Checking Telemetry
|
|
|
|
Click on the Channels button at the top of the window.
|
|
You should see a table of telemetry channels.
|
|
Each row corresponds to the latest value of a telemetry
|
|
channel received by the GDS.
|
|
You should see the channels corresponding to the input
|
|
values, the operation, and the result.
|
|
|
|
<a name="Running-the-Ref-Deployment_Setting-Parameters"></a>
|
|
### 7.4. Setting Parameters
|
|
|
|
Go back to the Commanding tab.
|
|
Select the command `mathReceiver.FACTOR_PRM_SET`.
|
|
This is an auto-generated command for setting the
|
|
parameter `FACTOR`.
|
|
Type the value 2.0 in the `val` box and click Send Command.
|
|
Check the events to see that the command was dispatched
|
|
and executed.
|
|
You should also see the events sent by the code
|
|
that you implemented.
|
|
|
|
In the Commanding tab, issue the command `1 + 2` again.
|
|
Check the Events tab.
|
|
Because the factor is now 2.0, you should see a result
|
|
value of 6.0.
|
|
|
|
<a name="Running-the-Ref-Deployment_Saving-Parameters"></a>
|
|
### 7.5. Saving Parameters
|
|
|
|
When you set a parameter by command, the new parameter
|
|
value resides in the component that receives the command.
|
|
At this point, if you stop and restart FSW, the parameter
|
|
will return to its original value (the value before you
|
|
sent the command).
|
|
|
|
At some point you may wish to update parameters more permanently.
|
|
You can do this by saving them to non-volatile storage.
|
|
For the Ref application, "non-volatile storage" means the
|
|
file system on your machine.
|
|
|
|
To save the parameter `mathReceiver.FACTOR` to non-volatile storage,
|
|
do the following:
|
|
|
|
1. Send the command `mathReceiver.FACTOR_PRM_SAVE`.
|
|
This command saves the parameter value to the **parameter database**,
|
|
which is a standard F Prime component for storing system parameters.
|
|
|
|
1. Send the command `prmDb.PRM_SAVE_FILE`.
|
|
This command saves the parameter values in the parameter database
|
|
to non-volatile storage.
|
|
|
|
Note that saving parameters is a two-step process.
|
|
The first step copies a single parameter from a component
|
|
to the database.
|
|
The second step saves all parameters in the database
|
|
to the disk.
|
|
If you do only the first step, the parameter will not be
|
|
saved to the disk.
|
|
|
|
<a name="Running-the-Ref-Deployment_GDS-Logs"></a>
|
|
### 7.6. GDS Logs
|
|
|
|
As it runs, the GDS writes a log into a subdirectory of `Ref/logs`.
|
|
The subdirectory is stamped with the current date.
|
|
Go into the directory for the run you just performed.
|
|
(If the GDS is still running, you will have to do this in a
|
|
different shell.)
|
|
You should see the following logs, among others:
|
|
|
|
* `Ref.log`: FSW console output.
|
|
|
|
* `command.log`: Commands sent.
|
|
|
|
* `event.log`: Event reports received.
|
|
|
|
* `channel.log`: Telemetry points received.
|
|
|
|
You can also view these logs via the GDS browser interface.
|
|
Click the Logs tab to go the Logs view.
|
|
Select the log you wish to inspect from the drop-down menu.
|
|
By default, there is no log selected.
|