Skip to content

Commit 475f323

Browse files
authored
Add ground derived channels how-to guide (#4074)
* Initial derived channel work * Write ground-derived channels * Update plugin handler * Updating publishChannel case Updated method name to follow naming conventions. * Fix method name to use snake_case in documentation
1 parent 94f67e4 commit 475f323

File tree

2 files changed

+167
-1
lines changed

2 files changed

+167
-1
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Create Ground-Derived Channels in F Prime GDS
2+
3+
Ground-derived channels allow you to compute new values from incoming telemetry on the ground, using logic that runs entirely within the F Prime Ground Data System (GDS). These values can be published into the ground system as if they came from flight, enabling flexible monitoring, UI presentation, or conversions on the ground.
4+
5+
This guide walks through the process of creating a basic plugin that listens to incoming telemetry and publishes it back to F Prime.
6+
7+
> [!WARNING]
8+
> Ground-derived channels are only supported when running with the `--no-zmq` option of the GDS. The ZeroMQ backend is fast but cannot allow arbitrary publishers.
9+
10+
> [!NOTE]
11+
> **Prerequisite**
12+
> Before following this guide, you should first complete the [Develop GDS Plugins](../how-to/develop-gds-plugins.md) guide.
13+
> It explains the plugin system, registration process, and runtime behavior essential to this example.
14+
15+
---
16+
17+
## When to Use This
18+
19+
Use a ground-derived channel when you need additional channels on the ground, but do not wish to compute nor downlink these values in-flight. For example:
20+
21+
- You want to transform or scale telemetry (e.g., converting a raw sensor ADC measurement to engineering units)
22+
- You want to compute a value based on multiple telemetry channels (e.g., battery differential)
23+
- You want to normalize or rename telemetry values for downstream consumers
24+
25+
---
26+
27+
## Plugin Architecture
28+
29+
Ground-derived channels are implemented using a `DataHandlerPlugin`, one of the built-in plugin types in the F Prime GDS.
30+
31+
Our plugin will:
32+
33+
- **Receive decoded telemetry data** via the `data_callback` API
34+
- **Apply your transformation logic**
35+
- **Publish the result** using the standard pipeline
36+
37+
The plugin runs in the `CustomDataHandler` process, isolated from the core GDS.
38+
39+
---
40+
41+
## Basic Setup
42+
43+
To get started, create a Python file (e.g., `ground_derived_channels.py`) and define a plugin class that listens for telemetry. The initial plugin structure will look like this:
44+
45+
```python
46+
from fprime_gds.common.handlers import DataHandlerPlugin
47+
from fprime_gds.plugin.definitions import gds_plugin
48+
49+
50+
@gds_plugin(DataHandlerPlugin)
51+
class ExampleGroundDerivedChannel(DataHandlerPlugin):
52+
""" Example Ground derived channel
53+
"""
54+
55+
@classmethod
56+
def get_name(cls):
57+
""" Return the name of the plugin """
58+
return "example-ground-derived-channel"
59+
60+
@classmethod
61+
def get_arguments(cls):
62+
""" No special arguments"""
63+
return {}
64+
65+
def get_handled_descriptors(self):
66+
""" List descriptors of F Prime data types that this plugin can handle """
67+
# Inform the GDS we want to process telemetry channels
68+
return ["FW_PACKET_TELEM"]
69+
70+
def data_callback(self, data, source):
71+
""" Handle channel objects
72+
"""
73+
pass
74+
```
75+
76+
The two critical functions are: `get_handled_descriptors`, and `data_callback`. `get_handled_descriptors` returns a list of descriptors to listen to, and `data_callback` will give a place to perform our calculations. In our case, we only want to subscribe to telemetry channels (i.e. `FW_PACKET_TELEM`). When using the telemetry packetizer, users may alternatively subscribe to `FW_PACKET_PACKETIZED_TLM`.
77+
78+
## Defining New Channels
79+
80+
To publish new channels, you must define them. Create a new dictionary file (e.g. "MyGroundChannelsDictionary.json") and add the channels you need. This structure will look like the following:
81+
82+
```
83+
{
84+
"metadata" : {
85+
"deploymentName" : "GroundChannels",
86+
"projectVersion" : "<match your project>",
87+
"frameworkVersion" : "<match your project>",
88+
"libraryVersions" : [],
89+
"dictionarySpecVersion" : "1.0.0"
90+
},
91+
"telemetryChannels" : [
92+
{
93+
"name" : "Examples(Ground).CommandCountMinus7",
94+
"type" : {
95+
"name" : "I64",
96+
"kind" : "integer",
97+
"size" : 64
98+
},
99+
"id" : 1000020224,
100+
"telemetryUpdate" : "always",
101+
"annotation" : "Output (derived) of command count less seven"
102+
}
103+
],
104+
105+
106+
107+
"typeDefinitions" : [],
108+
"constants" : [],
109+
"commands" : [],
110+
"parameters" : [],
111+
"events" : [],
112+
"records" : [],
113+
"containers" : [],
114+
"telemetryPacketSets" : []
115+
}
116+
```
117+
118+
Make sure that the "name" and "id" fields are unique to your derived channel.
119+
120+
## Deriving Values and Time
121+
122+
Now that you have a plugin structure and dictionary, it is time to derive the channel. The sample code below produces a count from CommandsDispatched less seven.
123+
124+
```python
125+
def data_callback(self, data, source):
126+
""" Handle channel objects
127+
"""
128+
# Filter out all channels that we do not need to create our derivation
129+
if not data.template.get_full_name().endswith("CommandsDispatched"):
130+
return
131+
# Operate on the channel's `val` field
132+
new_value = data.val - 7
133+
# Publish the new channel.
134+
self.publish_channel("Examples(Ground).CommandCountMinus7", new_value, data.time)
135+
```
136+
137+
First this code filters out unwanted channels. Then it performs a translation on the data's value. Then it publishes the new channel supplying name, value, and time. In this case, we have reused the original time.
138+
139+
## Running It
140+
141+
Install the plugin as directed in the plugin development How-To. Next we need to merge our dictionaries and run it. This is accomplished by running the merge dictionary command, and then supplying the output to the `--dictionary` flag of the GDS.
142+
143+
```
144+
fprime-merge-dictionary --permissive --output MergedDictionary.json \
145+
/path/to/flight/dictionary \
146+
./MyGroundChannelsDictionary.json
147+
fprime-gds --dictionary ./MergedDictionary.json --no-zmq
148+
```
149+
---
150+
151+
## See Also
152+
153+
- [DataHandlerPlugin Reference](../reference/gds-plugins/data-handler.md)
154+
- [Plugin System Overview](../how-to/develop-gds-plugins.md)
155+
- [Cosine Example](https://github.com/nasa/fprime-examples/tree/devel/GdsExamples/gds-plugins/src/ground_channels)

docs/reference/gds-plugins/data-handler.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ To create a Data Handler plugin, subclass the [`DataHandlerPlugin`](https://gith
3737
| "FW_PACKET_TELEM" | F Prime channels |
3838
| "FW_PACKET_LOG" | F Prime events |
3939
| "FW_PACKET_FILE" | F Prime file packets |
40+
| "FW_PACKET_PACKETIZED_TLM" | F Prime packets |
4041

4142

4243
- `data_callback(data, source)`:
@@ -61,4 +62,14 @@ class EventLogger(DataHandlerPlugin):
6162
This plugin will be called for every decoded event received by the system.
6263

6364
> [!NOTE]
64-
> The decoded data passed to data_callback() is an instance of the decoded object — for example, a ChannelTelemetry, Event, or CustomType depending on the descriptor.
65+
> The decoded data passed to data_callback() is an instance of the decoded object — for example, a ChannelTelemetry, Event, or CustomType depending on the descriptor.
66+
67+
## Helpers
68+
69+
The data handler plugin also supports some helper functionality.
70+
71+
```
72+
self.publisher.publish_channel(name: str, value: Any, time: TimeType)
73+
```
74+
75+
This call allows users to publish telemetry values by name. It is used in ground-processed channels.

0 commit comments

Comments
 (0)