-
Notifications
You must be signed in to change notification settings - Fork 51
Add processor documentation and CLI support #2108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
ce1fb71
Add processor documentation and CLI support
jmthomas 5d49733
Add processors to yaml_docs_spec
jmthomas 68c1096
Cleanup and fix refs
jmthomas ca8f1c3
Restore template vars
jmthomas 083f646
Add limits response docs and cleanup
jmthomas 0ee933c
Add limits-response.md
jmthomas 578904e
Fix spelling
jmthomas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
--- | ||
sidebar_position: 10 | ||
title: Processors | ||
description: Processors execute code every time a packet is received to calculate values | ||
sidebar_custom_props: | ||
myEmoji: 🧮 | ||
--- | ||
|
||
<!-- Be sure to edit _processors.md because processors.md is a generated file --> | ||
|
||
# Overview | ||
|
||
Processors execute code every time a packet is received to calculate values that can be retrieved by a [ProcessorConversion](/docs/configuration/conversions#processor_conversion). Processors are applied using the [PROCESSOR](/docs/configuration/telemetry#processor) keyword and generate values unique to the processor. | ||
|
||
If you only want to perform calculations using a single packet to modify a telemetry value you probably want to use a [Conversion](/docs/configuration/conversions). Processors are used when you're deriving a number of values from a single telemetry item. | ||
|
||
## Custom Processors | ||
|
||
You can easily create your own custom processors by using the [Processor Code Generator](/docs/getting-started/generators#processor-generator). To generate a process you must be inside an existing COSMOS plugin. The generator takes both a target name and the processor name. For example if your plugin is called `openc3-cosmos-gse` and you have an existing target named `GSE`: | ||
|
||
```bash | ||
openc3-cosmos-gse % openc3.sh cli generate processor GSE slope --python | ||
Processor targets/GSE/lib/slope_processor.py successfully generated! | ||
To use the processor add the following to a telemetry packet: | ||
PROCESSOR SLOPE slope_processor.py <PARAMS...> | ||
``` | ||
|
||
Note: To create a Ruby processor simply replace `--python` with `--ruby`. | ||
|
||
The above command creates a processor called `slope_processor.py` at `targets/GSE/lib/slope_processor.py`. The code which is generated looks like the following: | ||
|
||
```python | ||
import math | ||
from openc3.processors.processor import Processor | ||
|
||
# Custom processor class | ||
# See https://docs.openc3.com/docs/configuration/processors | ||
class SlopeProcessor(Processor): | ||
def __init__(self, item_name, num_samples, value_type='CONVERTED'): | ||
super().__init__(value_type) | ||
self.item_name = item_name.upper() | ||
self.num_samples = int(num_samples) | ||
self.reset() | ||
|
||
def call(self, value, packet, buffer): | ||
value = packet.read(self.item_name, self.value_type, buffer) | ||
# Don't process NaN or Infinite values | ||
if math.isnan(value) or math.isinf(value): | ||
return | ||
|
||
self.samples.append(value) | ||
if len(self.samples) > self.num_samples: | ||
self.samples = self.samples[-self.num_samples :] | ||
|
||
if len(self.samples) > 1: | ||
self.results['RATE_OF_CHANGE'] = (self.samples[-1] - self.samples[0]) / (len(self.samples) - 1) | ||
else: | ||
self.results['RATE_OF_CHANGE'] = None | ||
|
||
def reset(self): | ||
self.samples = [] | ||
self.results['RATE_OF_CHANGE'] = None | ||
``` | ||
|
||
### **init** | ||
|
||
The **init** method is where the processor is initialized. The parameters specified are the parameters given in the configuration file when creating the processor. So for our example, the telemetry configuration file will look like: | ||
|
||
``` | ||
# Calculate the slope of TEMP1 over the last 60 samples (1 minute) | ||
PROCESSOR SLOPE slope_processor.py TEMP1 60 | ||
``` | ||
|
||
### call | ||
|
||
The call method is where the actual processor logic is implemented. In our case we want to calculate the rate of change from the first sample to the last sample. There are certainly more efficient ways to calculate a single rate of change value (you really only need 2 values) but this example shows how to keep a running list of values. Also note that if you're only performing a single calculation you might be better off using a [Conversion](/docs/configuration/conversions). | ||
|
||
### reset | ||
|
||
The reset method initializes the samples and clears any state by setting the results to `None`. | ||
|
||
### Instantiate Processor | ||
|
||
Now that we have implemented the processor logic we need to create the processor by adding it to a telemetry packet with the line `PROCESSOR SLOPE slope_processor.py` in the [telemetry](/docs/configuration/telemetry) definition file. We also need a [ProcessorConversion](/docs/configuration/conversions#processor_conversion) to pull the calculated values out of the processor and into a [derived](/docs/configuration/telemetry#derived-items) telemetry item. This could look something like this: | ||
|
||
```bash | ||
TELEMETRY GSE DATA BIG_ENDIAN "Data packet" | ||
... # Telemetry items | ||
ITEM TEMP1SLOPE 0 0 DERIVED "Rate of change for the last 60 samples of TEMP1" | ||
READ_CONVERSION openc3/conversions/processor_conversion.py SLOPE RATE_OF_CHANGE | ||
# Calculate the slope of TEMP1 over the last 60 samples (1 minute) | ||
PROCESSOR SLOPE slope_processor.py TEMP1 60 | ||
``` | ||
|
||
If you have multiple values you're calculating you simply add additional ITEMs with READ_COVERSIONs and read the various values the processor calculates in the results. | ||
|
||
# Built-in Processors | ||
|
||
COSMOS_META |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
--- | ||
sidebar_position: 11 | ||
title: Limits Response | ||
description: Custom code invoked when a item with limits changes state | ||
sidebar_custom_props: | ||
myEmoji: ⚠️ | ||
--- | ||
|
||
# Overview | ||
|
||
A limits response is custom code which can respond to a telemetry item changing limits states (red, yellow, green or blue). To apply a limits response to a telemetry item you use the [LIMITS_RESPONSE](/docs/configuration/telemetry#limits_response) keyword. | ||
|
||
## Creating a Limits Response | ||
|
||
You can easily create a limits response by using the [Limits Response Code Generator](/docs/getting-started/generators#limits-response-generator). To generate a limits response you must be inside an existing COSMOS plugin. The generator takes both a target name and the limits response name. For example if your plugin is called `openc3-cosmos-gse` and you have an existing target named `GSE`: | ||
|
||
```bash | ||
openc3-cosmos-gse % openc3.sh cli generate limits_response GSE abort --python | ||
Limits response targets/GSE/lib/abort_limits_response.py successfully generated! | ||
To use the limits response add the following to a telemetry item: | ||
LIMITS_RESPONSE abort_limits_response.py | ||
``` | ||
|
||
Note: To create a Ruby conversion simply replace `--python` with `--ruby` | ||
|
||
This creates a limits response called `abort_limits_response.py` at `targets/GSE/lib/abort_limits_response.py`. The code which is generated looks like the following: | ||
|
||
```python | ||
from openc3.packets.limits_response import LimitsResponse | ||
from openc3.api import * | ||
|
||
class AbortLimitsResponse(LimitsResponse): | ||
# @param packet [Packet] Packet the limits response is assigned to | ||
# @param item [PacketItem] PacketItem the limits response is assigned to | ||
# @param old_limits_state [Symbol] Previous value of the limit. One of nil, | ||
# "GREEN_HIGH", "GREEN_LOW", "YELLOW", "YELLOW_HIGH", "YELLOW_LOW", | ||
# "RED", "RED_HIGH", "RED_LOW". nil if the previous limit state has not yet | ||
# been established. | ||
def call(self, packet, item, old_limits_state): | ||
# Take action based on the current limits state | ||
# Delete any of the case lines that do not apply or you don't care about | ||
match item.limits.state: | ||
case "RED_HIGH": | ||
# Take action like sending a command: | ||
# cmd("TARGET SAFE") | ||
pass | ||
case "RED_LOW": | ||
pass | ||
case "YELLOW_LOW": | ||
pass | ||
case "YELLOW_HIGH": | ||
pass | ||
# GREEN limits are only available if a telemetry item has them defined | ||
# COSMOS refers to these as "operational limits" | ||
# See https://docs.openc3.com/docs/configuration/telemetry#limits | ||
case "GREEN_LOW": | ||
pass | ||
case "GREEN_HIGH": | ||
pass | ||
# :RED and :YELLOW limits are triggered for STATES with defined RED and YELLOW states | ||
# See https://docs.openc3.com/docs/configuration/telemetry#state | ||
case "RED": | ||
pass | ||
case "YELLOW": | ||
pass | ||
``` | ||
|
||
There are a lot of comments to help you know what to do. The only thing you need to modify is the `call` method. | ||
|
||
### call | ||
|
||
The call method is where the limits response logic is implemented. As an example, suppose we want to send the `INST ABORT` command every time we enter a RED_HIGH or RED_LOW state. The final result with comments removed looks like the following: | ||
|
||
```python | ||
from openc3.packets.limits_response import LimitsResponse | ||
from openc3.api import * | ||
class AbortLimitsResponse(LimitsResponse): | ||
def call(self, packet, item, old_limits_state): | ||
match item.limits.state: | ||
case "RED_HIGH" | "RED_LOW": | ||
cmd("INST ABORT") | ||
``` | ||
|
||
### Apply Conversion | ||
|
||
Now that we have implemented the limits response logic we need to apply it to a telemetry item by adding the line `LIMITS_RESPONSE abort_limits_response.py` in the [telemetry](/docs/configuration/telemetry) definition file. This could look something like this: | ||
|
||
```bash | ||
TELEMETRY GSE DATA BIG_ENDIAN "Data packet" | ||
... # Header items | ||
APPEND_ITEM VALUE 16 UINT "limits response item" | ||
LIMITS DEFAULT 1 ENABLED -90 -80 80 90 | ||
LIMITS_RESPONSE abort_limits_response.py | ||
``` | ||
|
||
The definition combined with the `AbortLimitsResponse` means that each time the `GSE DATA VALUE` item goes below -90 or above 90 the `INST ABORT` command will be sent. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be Ruby to match the rest of our docs?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the majority of our users are going to be Python going forward so I'm slowing switching the docs over to Python as the primary example.