openc3 7.4.1 → 7.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/pipinstall +18 -5
- data/bin/pipuninstall +1 -1
- data/bin/uvinstall +36 -8
- data/data/config/default_plugins.yaml +43 -0
- data/data/config/interface_modifiers.yaml +22 -7
- data/data/config/item_modifiers.yaml +14 -4
- data/data/config/plugins.yaml +1 -1
- data/data/config/widgets.yaml +1 -1
- data/lib/openc3/api/cmd_api.rb +10 -13
- data/lib/openc3/api/interface_api.rb +1 -2
- data/lib/openc3/api/limits_api.rb +1 -1
- data/lib/openc3/api/metrics_api.rb +26 -1
- data/lib/openc3/api/router_api.rb +1 -2
- data/lib/openc3/api/tlm_api.rb +6 -10
- data/lib/openc3/config/config_parser.rb +3 -1
- data/lib/openc3/core_ext/tempfile.rb +2 -0
- data/lib/openc3/interfaces/file_interface.rb +6 -1
- data/lib/openc3/interfaces/http_client_interface.rb +14 -6
- data/lib/openc3/interfaces/interface.rb +27 -3
- data/lib/openc3/interfaces/read_queue_interface.rb +331 -0
- data/lib/openc3/interfaces/stream_interface.rb +22 -3
- data/lib/openc3/interfaces/tcpip_server_interface.rb +13 -0
- data/lib/openc3/interfaces/udp_interface.rb +16 -2
- data/lib/openc3/interfaces.rb +1 -0
- data/lib/openc3/logs/log_writer.rb +13 -4
- data/lib/openc3/microservices/decom_microservice.rb +7 -4
- data/lib/openc3/microservices/interface_microservice.rb +146 -50
- data/lib/openc3/microservices/log_microservice.rb +1 -3
- data/lib/openc3/microservices/microservice.rb +25 -1
- data/lib/openc3/microservices/plugin_microservice.rb +32 -0
- data/lib/openc3/microservices/text_log_microservice.rb +1 -3
- data/lib/openc3/models/interface_model.rb +1 -1
- data/lib/openc3/models/interface_status_model.rb +6 -0
- data/lib/openc3/models/metric_model.rb +52 -4
- data/lib/openc3/models/microservice_model.rb +7 -3
- data/lib/openc3/models/plugin_model.rb +12 -12
- data/lib/openc3/models/python_package_model.rb +3 -13
- data/lib/openc3/models/target_model.rb +83 -9
- data/lib/openc3/operators/microservice_operator.rb +5 -0
- data/lib/openc3/operators/operator.rb +69 -0
- data/lib/openc3/packets/packet.rb +10 -4
- data/lib/openc3/packets/packet_config.rb +12 -1
- data/lib/openc3/packets/packet_item.rb +27 -14
- data/lib/openc3/packets/parsers/state_parser.rb +1 -1
- data/lib/openc3/packets/structure_item.rb +3 -2
- data/lib/openc3/script/api_shared.rb +58 -84
- data/lib/openc3/script/extract.rb +215 -19
- data/lib/openc3/script/plugins.rb +4 -11
- data/lib/openc3/script/storage.rb +81 -10
- data/lib/openc3/system/system.rb +9 -6
- data/lib/openc3/topics/command_decom_topic.rb +1 -1
- data/lib/openc3/topics/command_topic.rb +2 -1
- data/lib/openc3/topics/router_topic.rb +2 -1
- data/lib/openc3/topics/telemetry_decom_topic.rb +1 -1
- data/lib/openc3/topics/telemetry_topic.rb +3 -2
- data/lib/openc3/topics/topic.rb +154 -2
- data/lib/openc3/utilities/cli_generator.rb +67 -15
- data/lib/openc3/utilities/config_overlay.rb +35 -17
- data/lib/openc3/utilities/default_plugins.rb +33 -0
- data/lib/openc3/utilities/local_mode.rb +82 -46
- data/lib/openc3/utilities/metric.rb +16 -0
- data/lib/openc3/utilities/process_stats.rb +160 -0
- data/lib/openc3/utilities/pypi_url.rb +65 -0
- data/lib/openc3/utilities/questdb_client.rb +14 -0
- data/lib/openc3/utilities/running_script.rb +22 -18
- data/lib/openc3/utilities/store_autoload.rb +20 -3
- data/lib/openc3/utilities/store_queued.rb +37 -9
- data/lib/openc3/utilities/target_file.rb +57 -12
- data/lib/openc3/utilities/topic_lag_monitor.rb +193 -0
- data/lib/openc3/version.rb +6 -6
- data/templates/conversion/conversion.py +6 -2
- data/templates/conversion/conversion.rb +5 -0
- data/templates/plugin/.github/workflows/playwright.yml +62 -0
- data/templates/plugin/.github/workflows/release.yml +45 -0
- data/templates/plugin/.github/workflows/unit-tests.yml +35 -0
- data/templates/plugin/.gitignore +6 -0
- data/templates/plugin/README.md +114 -2
- data/templates/plugin/lib/PACKAGE/__init__.py +4 -0
- data/templates/plugin/plugin.gemspec +10 -1
- data/templates/plugin/pyproject.toml +119 -0
- data/templates/tool_angular/package.json +2 -2
- data/templates/tool_angular/pnpm-workspace.yaml +16 -0
- data/templates/tool_react/package.json +1 -1
- data/templates/tool_react/pnpm-workspace.yaml +16 -0
- data/templates/tool_svelte/package.json +2 -1
- data/templates/tool_svelte/pnpm-workspace.yaml +20 -0
- data/templates/tool_vue/package.json +3 -3
- data/templates/tool_vue/pnpm-workspace.yaml +16 -0
- data/templates/widget/package.json +2 -2
- data/templates/widget/pnpm-workspace.yaml +16 -0
- metadata +16 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 86e5a8920effe09dd66ebc0ca5f90e8df99948804786b07abce52711dbd03d37
|
|
4
|
+
data.tar.gz: 612c0bfe5198ecc591bb57722931174813ee343370db9ad1474b5f9e62a87a8f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 36d1781323b82533f1af150fef184e63ec9b59c93006f4faaef8949f841d1983adbec5204ff5551f629cdd8d843cf843890edc3d5193249215514359c7cb437e
|
|
7
|
+
data.tar.gz: 3eafda9e5def7b46492a96627ce1e4b9a2c22d0f2e0ea3b51279051ebb2e658833ee5b18dacc119933cd3cafcb24219d587fc1afb8f11efab33daf9025ceabad
|
data/bin/pipinstall
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
VENV="${PIPINSTALL_VENV:-$PYTHONUSERBASE}"
|
|
3
|
+
|
|
4
|
+
# --no-sources finishes what --no-config starts. PypiUrl.build_args adds
|
|
5
|
+
# --no-config when an operator has designated an index, which stops uv searching
|
|
6
|
+
# a named index declared in [tool.uv].index - but a package pinned with
|
|
7
|
+
# [tool.uv].sources is project metadata that survives --no-config and still
|
|
8
|
+
# resolves against the index the pin names. --no-sources drops those pins so the
|
|
9
|
+
# operator's index is the only one consulted. Every uv command below resolves,
|
|
10
|
+
# so it applies to all of them.
|
|
11
|
+
case " $* " in
|
|
12
|
+
*" --no-config "*) NO_SOURCES="--no-sources" ;;
|
|
13
|
+
*) NO_SOURCES="" ;;
|
|
14
|
+
esac
|
|
15
|
+
|
|
3
16
|
uv venv "$VENV" --allow-existing
|
|
4
|
-
echo "uv pip install
|
|
5
|
-
uv pip install --python "$VENV" "$@"
|
|
17
|
+
echo "uv pip install $*"
|
|
18
|
+
uv pip install --python "$VENV" ${NO_SOURCES} "$@"
|
|
6
19
|
if [ $? -eq 0 ]; then
|
|
7
20
|
echo "Command succeeded"
|
|
8
21
|
exit 0
|
|
@@ -26,9 +39,9 @@ LAST_ARG="$PREV"
|
|
|
26
39
|
if [ -d "$LAST_ARG" ] && [ -f "$LAST_ARG/pyproject.toml" ]; then
|
|
27
40
|
echo "Warning: Failed to build Python package, attempting to install declared dependencies from pyproject.toml"
|
|
28
41
|
TMPFILE=$(mktemp)
|
|
29
|
-
uv pip compile ${OPTS} "${LAST_ARG}/pyproject.toml" > "$TMPFILE"
|
|
42
|
+
uv pip compile ${NO_SOURCES} ${OPTS} "${LAST_ARG}/pyproject.toml" > "$TMPFILE"
|
|
30
43
|
if [ $? -eq 0 ] && [ -s "$TMPFILE" ]; then
|
|
31
|
-
uv pip install --python "$VENV" ${OPTS} -r "$TMPFILE"
|
|
44
|
+
uv pip install --python "$VENV" ${NO_SOURCES} ${OPTS} -r "$TMPFILE"
|
|
32
45
|
if [ $? -eq 0 ]; then
|
|
33
46
|
echo "Dependencies installed successfully"
|
|
34
47
|
rm -f "$TMPFILE"
|
|
@@ -39,7 +52,7 @@ if [ -d "$LAST_ARG" ] && [ -f "$LAST_ARG/pyproject.toml" ]; then
|
|
|
39
52
|
fi
|
|
40
53
|
|
|
41
54
|
echo "Warning: Install failed - retrying with --no-index"
|
|
42
|
-
uv pip install --python "$VENV" --no-index "$@"
|
|
55
|
+
uv pip install --python "$VENV" --no-index ${NO_SOURCES} "$@"
|
|
43
56
|
if [ $? -ne 0 ]; then
|
|
44
57
|
echo "ERROR: uv pip install failed"
|
|
45
58
|
exit 1
|
data/bin/pipuninstall
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# PythonPackageModel.destroy for per-plugin venvs), uninstall from that venv.
|
|
4
4
|
# Otherwise fall back to the shared PYTHONUSERBASE.
|
|
5
5
|
VENV="${PIPINSTALL_VENV:-$PYTHONUSERBASE}"
|
|
6
|
-
echo "uv pip uninstall
|
|
6
|
+
echo "uv pip uninstall $*"
|
|
7
7
|
uv pip uninstall --python "$VENV" "$@"
|
|
8
8
|
if [ $? -eq 0 ]; then
|
|
9
9
|
echo "Command succeeded"
|
data/bin/uvinstall
CHANGED
|
@@ -22,7 +22,28 @@
|
|
|
22
22
|
PLUGIN_VENV_NAME="$1"
|
|
23
23
|
GEM_PATH="$2"
|
|
24
24
|
shift 2
|
|
25
|
-
|
|
25
|
+
# Index arguments built by PypiUrl.build_args: --default-index (the PyPI URL
|
|
26
|
+
# from Admin Console -> Settings) and, when the deployment opts in,
|
|
27
|
+
# --allow-insecure-host. Only --allow-insecure-host is load bearing on the
|
|
28
|
+
# `uv sync --frozen` path below: a frozen sync never resolves, so uv installs
|
|
29
|
+
# from the registry and wheel URLs already recorded in uv.lock and the default
|
|
30
|
+
# index is unused. On the `uv pip` fallback paths both matter.
|
|
31
|
+
UV_ARGS="$*"
|
|
32
|
+
|
|
33
|
+
# --no-sources finishes what --no-config starts. PypiUrl.build_args adds
|
|
34
|
+
# --no-config when an operator has designated an index, which stops uv searching
|
|
35
|
+
# a named index the plugin declares in [tool.uv].index - but a package pinned
|
|
36
|
+
# with [tool.uv].sources is project metadata that survives --no-config and still
|
|
37
|
+
# resolves against the plugin author's index. --no-sources drops those pins so
|
|
38
|
+
# the operator's index is the only one consulted.
|
|
39
|
+
#
|
|
40
|
+
# It is added only for the commands that resolve: `uv sync --frozen` rejects
|
|
41
|
+
# --no-sources outright, and has no use for it, because a frozen sync installs
|
|
42
|
+
# the registry and wheel URLs already recorded in uv.lock without resolving.
|
|
43
|
+
RESOLVE_ARGS="$UV_ARGS"
|
|
44
|
+
case " ${UV_ARGS} " in
|
|
45
|
+
*" --no-config "*) RESOLVE_ARGS="${UV_ARGS} --no-sources" ;;
|
|
46
|
+
esac
|
|
26
47
|
|
|
27
48
|
if [ -z "$PLUGIN_VENV_NAME" ] || [ -z "$GEM_PATH" ]; then
|
|
28
49
|
echo "Usage: uvinstall <plugin_venv_name> <gem_path> [extra uv args...]"
|
|
@@ -57,7 +78,12 @@ uv_offline_then_online() {
|
|
|
57
78
|
uv "$@"
|
|
58
79
|
}
|
|
59
80
|
|
|
60
|
-
|
|
81
|
+
# The venv root is overridable so the spec suite can drive this script against
|
|
82
|
+
# a temporary directory. Production never sets it; PluginModel computes the same
|
|
83
|
+
# /gems/plugin_venvs path independently when it records the .uv_managed marker,
|
|
84
|
+
# so pointing this elsewhere outside a test would put the two out of step.
|
|
85
|
+
VENV_ROOT="${UVINSTALL_VENV_ROOT:-/gems/plugin_venvs}"
|
|
86
|
+
VENV_BASE="${VENV_ROOT}/${PLUGIN_VENV_NAME}"
|
|
61
87
|
VENV_DIR="${VENV_BASE}/.venv"
|
|
62
88
|
|
|
63
89
|
echo "Creating per-plugin venv for '${PLUGIN_VENV_NAME}' at ${VENV_DIR}"
|
|
@@ -72,7 +98,9 @@ if [ -f "${GEM_PATH}/uv.lock" ] && [ -f "${GEM_PATH}/pyproject.toml" ]; then
|
|
|
72
98
|
cp "${GEM_PATH}/uv.lock" "${VENV_BASE}/uv.lock"
|
|
73
99
|
|
|
74
100
|
# Create venv and sync dependencies
|
|
75
|
-
|
|
101
|
+
# Syncing in the wrong directory would resolve against whatever
|
|
102
|
+
# pyproject.toml happens to be there, so treat this as fatal.
|
|
103
|
+
cd "${VENV_BASE}" || exit 1
|
|
76
104
|
uv venv "${VENV_DIR}" --allow-existing
|
|
77
105
|
|
|
78
106
|
# Offline sync first, online only on a cache miss (bounded by UV_HTTP_*).
|
|
@@ -99,14 +127,14 @@ uv venv "${VENV_DIR}" --allow-existing
|
|
|
99
127
|
|
|
100
128
|
if [ -f "${GEM_PATH}/requirements.txt" ]; then
|
|
101
129
|
echo "Installing from requirements.txt via uv pip install"
|
|
102
|
-
uv_offline_then_online pip install --python "${VENV_DIR}" ${
|
|
130
|
+
uv_offline_then_online pip install --python "${VENV_DIR}" ${RESOLVE_ARGS} -r "${GEM_PATH}/requirements.txt"
|
|
103
131
|
RESULT=$?
|
|
104
132
|
elif [ -f "${GEM_PATH}/pyproject.toml" ]; then
|
|
105
133
|
echo "Installing from pyproject.toml via uv pip install"
|
|
106
134
|
# --python: The Python interpreter into which packages should be installed.
|
|
107
135
|
# --project: Discover a project (i.e. pyproject.toml) in the given directory.
|
|
108
136
|
# -r: Install the packages listed in the given files.
|
|
109
|
-
uv_offline_then_online pip install --python "${VENV_DIR}" --project "${GEM_PATH}" ${
|
|
137
|
+
uv_offline_then_online pip install --python "${VENV_DIR}" --project "${GEM_PATH}" ${RESOLVE_ARGS} -r "${GEM_PATH}/pyproject.toml"
|
|
110
138
|
RESULT=$?
|
|
111
139
|
|
|
112
140
|
# `uv pip install <path>` builds the plugin as a package before installing
|
|
@@ -121,11 +149,11 @@ elif [ -f "${GEM_PATH}/pyproject.toml" ]; then
|
|
|
121
149
|
if [ $RESULT -ne 0 ]; then
|
|
122
150
|
echo "Warning: Failed to build Python package, attempting to install declared dependencies from pyproject.toml"
|
|
123
151
|
TMPFILE=$(mktemp)
|
|
124
|
-
uv pip compile --offline ${
|
|
152
|
+
uv pip compile --offline ${RESOLVE_ARGS} "${GEM_PATH}/pyproject.toml" > "$TMPFILE"
|
|
125
153
|
COMPILED=$?
|
|
126
154
|
if [ $COMPILED -ne 0 ] || [ ! -s "$TMPFILE" ]; then
|
|
127
155
|
echo "Offline uv pip compile missed the cache; retrying online"
|
|
128
|
-
uv pip compile ${
|
|
156
|
+
uv pip compile ${RESOLVE_ARGS} "${GEM_PATH}/pyproject.toml" > "$TMPFILE"
|
|
129
157
|
COMPILED=$?
|
|
130
158
|
fi
|
|
131
159
|
# Both the exit status AND a non-empty file are required: uv writes its
|
|
@@ -133,7 +161,7 @@ elif [ -f "${GEM_PATH}/pyproject.toml" ]; then
|
|
|
133
161
|
# fails partway still leaves a non-empty file. Installing from that would
|
|
134
162
|
# produce a venv missing dependencies and report success.
|
|
135
163
|
if [ $COMPILED -eq 0 ] && [ -s "$TMPFILE" ]; then
|
|
136
|
-
uv_offline_then_online pip install --python "${VENV_DIR}" ${
|
|
164
|
+
uv_offline_then_online pip install --python "${VENV_DIR}" ${RESOLVE_ARGS} -r "$TMPFILE"
|
|
137
165
|
RESULT=$?
|
|
138
166
|
fi
|
|
139
167
|
rm -f "$TMPFILE"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Copyright 2026 OpenC3, Inc.
|
|
2
|
+
# All Rights Reserved.
|
|
3
|
+
#
|
|
4
|
+
# This program is distributed in the hope that it will be useful,
|
|
5
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
6
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
7
|
+
# See LICENSE.md for more details.
|
|
8
|
+
#
|
|
9
|
+
# This file may also be used under the terms of a commercial license
|
|
10
|
+
# if purchased from OpenC3, Inc.
|
|
11
|
+
|
|
12
|
+
# Plugins that ship with COSMOS. These are hidden by default in the Admin
|
|
13
|
+
# Plugins tab, skipped by plugin_list(default: false) and never written to
|
|
14
|
+
# the local mode plugins folder.
|
|
15
|
+
# This is the single source of truth: OpenC3::DefaultPlugins reads it and
|
|
16
|
+
# GET /openc3-api/plugins/all flags each plugin with 'default' for the
|
|
17
|
+
# frontend and script APIs.
|
|
18
|
+
- openc3-cosmos-tool-admin
|
|
19
|
+
- openc3-cosmos-tool-bucketexplorer
|
|
20
|
+
- openc3-cosmos-tool-cmdsender
|
|
21
|
+
- openc3-cosmos-tool-cmdqueue # Enterprise only
|
|
22
|
+
- openc3-cosmos-tool-cmdhistory # Enterprise only
|
|
23
|
+
- openc3-cosmos-tool-cmdtlmserver
|
|
24
|
+
- openc3-cosmos-tool-dataextractor
|
|
25
|
+
- openc3-cosmos-tool-dataviewer
|
|
26
|
+
- openc3-cosmos-tool-docs
|
|
27
|
+
- openc3-cosmos-tool-handbooks
|
|
28
|
+
- openc3-cosmos-tool-iframe
|
|
29
|
+
- openc3-cosmos-tool-limitsmonitor
|
|
30
|
+
- openc3-cosmos-tool-packetviewer
|
|
31
|
+
- openc3-cosmos-tool-scriptrunner
|
|
32
|
+
- openc3-cosmos-tool-tablemanager
|
|
33
|
+
- openc3-cosmos-tool-tlmgrapher
|
|
34
|
+
- openc3-cosmos-tool-tlmviewer
|
|
35
|
+
- openc3-cosmos-enterprise-tool-admin # Enterprise only
|
|
36
|
+
- openc3-cosmos-tool-autonomic # Enterprise only
|
|
37
|
+
- openc3-cosmos-tool-calendar # Enterprise only
|
|
38
|
+
- openc3-cosmos-tool-grafana # Enterprise only
|
|
39
|
+
- openc3-cosmos-tool-logexplorer # Enterprise only
|
|
40
|
+
- openc3-cosmos-tool-notebooks # Enterprise only
|
|
41
|
+
- openc3-cosmos-tool-systemhealth # Enterprise only
|
|
42
|
+
- openc3-enterprise-tool-base # Enterprise only
|
|
43
|
+
- openc3-tool-base
|
|
@@ -76,6 +76,10 @@ DISABLE_DISCONNECT:
|
|
|
76
76
|
Use this keyword to prevent the user from disconnecting from the interface.
|
|
77
77
|
This is typically used in a 'production' environment where you would not want
|
|
78
78
|
the user to inadvertently disconnect from a target.
|
|
79
|
+
DONT_LOG:
|
|
80
|
+
summary: Deprecated keyword that has no effect
|
|
81
|
+
description: Accepted for backwards compatibility with older plugins. A warning is logged and the keyword is otherwise ignored.
|
|
82
|
+
deprecated: DONT_LOG does nothing and can be removed
|
|
79
83
|
LOG_STREAM:
|
|
80
84
|
summary: Log all data on the interface exactly as it is sent and received
|
|
81
85
|
description:
|
|
@@ -154,15 +158,15 @@ OPTION:
|
|
|
154
158
|
connections on (default 0.0.0.0).
|
|
155
159
|
values: .*
|
|
156
160
|
- name: Parameters
|
|
157
|
-
required:
|
|
158
|
-
description:
|
|
161
|
+
required: true
|
|
162
|
+
description: One or more parameters to pass to the option
|
|
159
163
|
values: .*
|
|
160
164
|
example: |
|
|
161
165
|
INTERFACE SERIAL_INT serial_interface.rb COM1 COM1 115200 NONE 1 10.0 nil
|
|
162
166
|
OPTION FLOW_CONTROL RTSCTS
|
|
163
167
|
OPTION DATA_BITS 8
|
|
164
168
|
ROUTER SERIAL_ROUTER tcpip_server_interface.rb 2950 2950 10.0 nil BURST
|
|
165
|
-
|
|
169
|
+
MAP_TARGET DATA
|
|
166
170
|
OPTION LISTEN_ADDRESS 127.0.0.1
|
|
167
171
|
SECRET:
|
|
168
172
|
summary: Define a secret needed by this interface
|
|
@@ -261,7 +265,7 @@ CONTAINER:
|
|
|
261
265
|
since: 5.7.0
|
|
262
266
|
parameters:
|
|
263
267
|
- name: Args
|
|
264
|
-
required:
|
|
268
|
+
required: true
|
|
265
269
|
description: Name of the container
|
|
266
270
|
values: .+
|
|
267
271
|
ROUTE_PREFIX:
|
|
@@ -281,11 +285,22 @@ SHARD:
|
|
|
281
285
|
since: 6.0.0
|
|
282
286
|
parameters:
|
|
283
287
|
- name: Shard
|
|
284
|
-
required:
|
|
288
|
+
required: true
|
|
285
289
|
description: Shard number starting from 0
|
|
286
290
|
values: \d+
|
|
287
291
|
example: |
|
|
288
292
|
SHARD 0
|
|
293
|
+
DB_SHARD:
|
|
294
|
+
summary: Database shard for this interface's command and directive streams
|
|
295
|
+
description: DB Shard. Only used if running multiple database shards typically in Kubernetes
|
|
296
|
+
since: 7.1.0
|
|
297
|
+
parameters:
|
|
298
|
+
- name: DB Shard
|
|
299
|
+
required: true
|
|
300
|
+
description: DB Shard number starting from 0
|
|
301
|
+
values: \d+
|
|
302
|
+
example: |
|
|
303
|
+
DB_SHARD 0
|
|
289
304
|
BRIDGE:
|
|
290
305
|
summary: Name of the bridge to run this interface on
|
|
291
306
|
description: Run this interface on a bridge host rather than within COSMOS. Note only python is supported.
|
|
@@ -338,8 +353,8 @@ BRIDGE_OPTION:
|
|
|
338
353
|
connections on (default 0.0.0.0).
|
|
339
354
|
values: .*
|
|
340
355
|
- name: Parameters
|
|
341
|
-
required:
|
|
342
|
-
description:
|
|
356
|
+
required: true
|
|
357
|
+
description: One or more parameters to pass to the option
|
|
343
358
|
values: .*
|
|
344
359
|
example: |
|
|
345
360
|
INTERFACE SERIAL_INT serial_interface.rb COM1 COM1 115200 NONE 1 10.0 nil
|
|
@@ -123,6 +123,12 @@ LIMITS:
|
|
|
123
123
|
define additional named sets (e.g. TVAC), the DEFAULT set must be defined first.
|
|
124
124
|
Attempting to define a named set before DEFAULT will raise an error of the form
|
|
125
125
|
"DEFAULT limits set must be defined for TARGET PACKET ITEM before setting limits set NAME".
|
|
126
|
+
|
|
127
|
+
A value exactly equal to a limit is placed in the outer (more severe) state.
|
|
128
|
+
For example, with LIMITS DEFAULT 1 ENABLED 1 3 7 9 4 6 an integer item reports
|
|
129
|
+
RED_LOW at 1 and below, YELLOW_LOW at 2-3, GREEN_LOW at 4, BLUE at 5,
|
|
130
|
+
GREEN_HIGH at 6, YELLOW_HIGH at 7-8 and RED_HIGH at 9 and above. Without
|
|
131
|
+
the green limits, 4-6 would all be GREEN.
|
|
126
132
|
example: |
|
|
127
133
|
LIMITS DEFAULT 3 ENABLED -80.0 -70.0 60.0 80.0 -20.0 20.0
|
|
128
134
|
LIMITS TVAC 3 ENABLED -80.0 -30.0 30.0 80.0
|
|
@@ -169,16 +175,20 @@ LIMITS:
|
|
|
169
175
|
description: Setting the Green Low and Green High limits defines an
|
|
170
176
|
"operational limit" which is colored blue by OpenC3. This allows for a
|
|
171
177
|
distinct desired operational range which is narrower than the green safety limit.
|
|
172
|
-
If the telemetry value is greater than
|
|
173
|
-
|
|
178
|
+
If the telemetry value is greater than this value, but less than the
|
|
179
|
+
Green High Limit, a Blue operational condition will be detected. If the
|
|
180
|
+
telemetry value is less than or equal to this value, but greater than the
|
|
181
|
+
Yellow Low Limit, a Green Low condition will be detected.
|
|
174
182
|
values: .+
|
|
175
183
|
- name: Green High Limit
|
|
176
184
|
required: false
|
|
177
185
|
description: Setting the Green Low and Green High limits defines an
|
|
178
186
|
"operational limit" which is colored blue by OpenC3. This allows for a
|
|
179
187
|
distinct desired operational range which is narrower than the green safety limit.
|
|
180
|
-
If the telemetry value is less than
|
|
181
|
-
|
|
188
|
+
If the telemetry value is less than this value, but greater than the
|
|
189
|
+
Green Low Limit, a Blue operational condition will be detected. If the
|
|
190
|
+
telemetry value is greater than or equal to this value, but less than the
|
|
191
|
+
Yellow High Limit, a Green High condition will be detected.
|
|
182
192
|
values: .+
|
|
183
193
|
LIMITS_RESPONSE:
|
|
184
194
|
summary: Defines a response class that is called when the limits state of the current item changes
|
data/data/config/plugins.yaml
CHANGED
|
@@ -79,7 +79,7 @@ INTERFACE:
|
|
|
79
79
|
documentation for more details.
|
|
80
80
|
ROUTER:
|
|
81
81
|
modifiers:
|
|
82
|
-
<%= MetaConfigParser.load('interface_modifiers.yaml').to_meta_config_yaml(4) %>
|
|
82
|
+
<%= MetaConfigParser.load('interface_modifiers.yaml').reject { |keyword, _| keyword.start_with?('BRIDGE') }.to_meta_config_yaml(4) %>
|
|
83
83
|
summary: Create router to receive commands and output telemetry packets from one or more interfaces
|
|
84
84
|
description: Creates an router which receives command packets from
|
|
85
85
|
their remote clients and sends them to associated interfaces. They receive telemetry
|
data/data/config/widgets.yaml
CHANGED
|
@@ -1230,7 +1230,7 @@ Telemetry Widgets:
|
|
|
1230
1230
|
parameters:
|
|
1231
1231
|
- name: Icon name
|
|
1232
1232
|
required: true
|
|
1233
|
-
description: The astro UX icon to display. Valid choices are 'astro' icons taken from https://
|
|
1233
|
+
description: The astro UX icon to display. Valid choices are 'astro' icons taken from https://www.astrouxds.com/components/icon-library/.
|
|
1234
1234
|
values: .+
|
|
1235
1235
|
- name: Icon label
|
|
1236
1236
|
required: false
|
data/lib/openc3/api/cmd_api.rb
CHANGED
|
@@ -211,18 +211,14 @@ module OpenC3
|
|
|
211
211
|
# @param target_name [String] Name of the target
|
|
212
212
|
# @return [Array<String>] Array of all command packet names
|
|
213
213
|
def get_all_cmd_names(target_name, hidden: false, manual: false, scope: $openc3_scope, token: $openc3_token)
|
|
214
|
+
target_name = target_name.upcase
|
|
215
|
+
authorize(permission: 'cmd_info', target_name: target_name, scope: scope, token: token)
|
|
216
|
+
# Only the names are needed so avoid parsing every packet definition
|
|
214
217
|
begin
|
|
215
|
-
|
|
218
|
+
names = TargetModel.packet_names(target_name, type: :CMD, scope: scope)
|
|
219
|
+
names -= TargetModel.hidden_packet_names(target_name, type: :CMD, scope: scope) unless hidden
|
|
216
220
|
rescue RuntimeError
|
|
217
|
-
|
|
218
|
-
end
|
|
219
|
-
names = []
|
|
220
|
-
packets.each do |packet|
|
|
221
|
-
if hidden
|
|
222
|
-
names << packet['packet_name']
|
|
223
|
-
else
|
|
224
|
-
names << packet['packet_name'] unless packet['hidden']
|
|
225
|
-
end
|
|
221
|
+
names = []
|
|
226
222
|
end
|
|
227
223
|
return names
|
|
228
224
|
end
|
|
@@ -364,13 +360,14 @@ module OpenC3
|
|
|
364
360
|
time = 0
|
|
365
361
|
command_name = nil
|
|
366
362
|
targets.each do |cur_target|
|
|
367
|
-
|
|
368
|
-
|
|
363
|
+
# Only the names are needed so avoid parsing every packet definition
|
|
364
|
+
TargetModel.packet_names(cur_target, type: :CMD, scope: scope).each do |packet_name|
|
|
365
|
+
cur_time = CommandDecomTopic.get_cmd_item(cur_target, packet_name, 'RECEIVED_TIMESECONDS', type: :CONVERTED, scope: scope)
|
|
369
366
|
next unless cur_time
|
|
370
367
|
|
|
371
368
|
if cur_time > time
|
|
372
369
|
time = cur_time
|
|
373
|
-
command_name =
|
|
370
|
+
command_name = packet_name
|
|
374
371
|
target_name = cur_target
|
|
375
372
|
end
|
|
376
373
|
end
|
|
@@ -48,8 +48,7 @@ module OpenC3
|
|
|
48
48
|
authorize(permission: 'system', interface_name: interface_name, manual: manual, scope: scope, token: token)
|
|
49
49
|
interface = InterfaceModel.get(name: interface_name, scope: scope)
|
|
50
50
|
raise "Interface '#{interface_name}' does not exist (get_interface)" unless interface
|
|
51
|
-
|
|
52
|
-
interface.merge(InterfaceStatusModel.get(name: interface_name, scope: scope))
|
|
51
|
+
interface.merge(InterfaceStatusModel.get_or_default(name: interface_name, scope: scope))
|
|
53
52
|
end
|
|
54
53
|
|
|
55
54
|
# @return [Array<String>] All the interface names
|
|
@@ -457,7 +457,7 @@ module OpenC3
|
|
|
457
457
|
# @return Hash The requested item based on the packet name
|
|
458
458
|
def _get_item(target_name, packet_name, item_name, cache_timeout: nil, scope:)
|
|
459
459
|
# Determine if this item exists, it will raise appropriate errors if not
|
|
460
|
-
packet_name = CvtModel.determine_latest_packet_for_item(target_name, item_name, cache_timeout: cache_timeout, scope:
|
|
460
|
+
packet_name = CvtModel.determine_latest_packet_for_item(target_name, item_name, cache_timeout: cache_timeout, scope: scope) if packet_name == 'LATEST'
|
|
461
461
|
return TargetModel.packet_item(target_name, packet_name, item_name, scope: scope)
|
|
462
462
|
end
|
|
463
463
|
end
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
require 'openc3/models/metric_model'
|
|
15
15
|
require 'openc3/utilities/authentication'
|
|
16
16
|
require 'openc3/utilities/store'
|
|
17
|
+
require 'openc3/utilities/topic_lag_monitor'
|
|
17
18
|
|
|
18
19
|
module OpenC3
|
|
19
20
|
module Api
|
|
@@ -53,8 +54,12 @@ module OpenC3
|
|
|
53
54
|
SUM_METRICS['tsdb_ingest_total'] = 0
|
|
54
55
|
SUM_METRICS['tsdb_ingest_error_total'] = 0
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
# Metrics shown per service in Data Flows
|
|
58
|
+
FLOW_METRIC_SUFFIXES = %w(_topic_delta_seconds _duration_seconds _read_queue_bytes _total).freeze
|
|
59
|
+
|
|
60
|
+
def get_metrics(detailed: false, manual: false, scope: $openc3_scope, token: $openc3_token)
|
|
57
61
|
authorize(permission: 'system', manual: manual, scope: scope, token: token)
|
|
62
|
+
return _flow_metrics(scope) if detailed
|
|
58
63
|
|
|
59
64
|
sum_metrics = SUM_METRICS.dup
|
|
60
65
|
duration_metrics = DURATION_METRICS.dup
|
|
@@ -93,5 +98,25 @@ module OpenC3
|
|
|
93
98
|
|
|
94
99
|
return result
|
|
95
100
|
end
|
|
101
|
+
|
|
102
|
+
# Per service metrics for Data Flows. Reads only what each microservice
|
|
103
|
+
# reported itself so updated_at shows when that service last published;
|
|
104
|
+
# the operator's process metrics would otherwise keep it fresh while the
|
|
105
|
+
# process is alive. Sends just the values Data Flows displays, plus the
|
|
106
|
+
# lag thresholds so its indicators match the falling behind notifications.
|
|
107
|
+
def _flow_metrics(scope)
|
|
108
|
+
services = {}
|
|
109
|
+
MetricModel.all_self_reported(scope: scope).each do |name, entry|
|
|
110
|
+
next unless entry and entry['values']
|
|
111
|
+
values = {}
|
|
112
|
+
entry['values'].each do |metric_name, data|
|
|
113
|
+
next unless data.is_a?(Hash) and metric_name.end_with?(*FLOW_METRIC_SUFFIXES)
|
|
114
|
+
values[metric_name] = { 'value' => data['value'] }
|
|
115
|
+
end
|
|
116
|
+
next if values.empty?
|
|
117
|
+
services[name] = { 'updated_at' => entry['updated_at'], 'values' => values }
|
|
118
|
+
end
|
|
119
|
+
{ 'lag_thresholds' => TopicLagMonitor.thresholds, 'services' => services }
|
|
120
|
+
end
|
|
96
121
|
end
|
|
97
122
|
end
|
|
@@ -47,8 +47,7 @@ module OpenC3
|
|
|
47
47
|
authorize(permission: 'system', router_name: router_name, manual: manual, scope: scope, token: token)
|
|
48
48
|
router = RouterModel.get(name: router_name, scope: scope)
|
|
49
49
|
raise "Router '#{router_name}' does not exist (get_router)" unless router
|
|
50
|
-
|
|
51
|
-
router.merge(RouterStatusModel.get(name: router_name, scope: scope))
|
|
50
|
+
router.merge(RouterStatusModel.get_or_default(name: router_name, scope: scope))
|
|
52
51
|
end
|
|
53
52
|
|
|
54
53
|
# @return [Array<String>] All the router names
|
data/lib/openc3/api/tlm_api.rb
CHANGED
|
@@ -373,18 +373,14 @@ module OpenC3
|
|
|
373
373
|
# @param target_name [String] Name of the target
|
|
374
374
|
# @return [Array<String>] Array of all telemetry packet names
|
|
375
375
|
def get_all_tlm_names(target_name, hidden: false, manual: false, scope: $openc3_scope, token: $openc3_token)
|
|
376
|
+
target_name = target_name.upcase
|
|
377
|
+
authorize(permission: 'tlm', target_name: target_name, manual: manual, scope: scope, token: token)
|
|
378
|
+
# Only the names are needed so avoid parsing every packet definition
|
|
376
379
|
begin
|
|
377
|
-
|
|
380
|
+
names = TargetModel.packet_names(target_name, type: :TLM, scope: scope)
|
|
381
|
+
names -= TargetModel.hidden_packet_names(target_name, type: :TLM, scope: scope) unless hidden
|
|
378
382
|
rescue RuntimeError
|
|
379
|
-
|
|
380
|
-
end
|
|
381
|
-
names = []
|
|
382
|
-
packets.each do |packet|
|
|
383
|
-
if hidden
|
|
384
|
-
names << packet['packet_name']
|
|
385
|
-
else
|
|
386
|
-
names << packet['packet_name'] unless packet['hidden']
|
|
387
|
-
end
|
|
383
|
+
names = []
|
|
388
384
|
end
|
|
389
385
|
return names
|
|
390
386
|
end
|
|
@@ -319,8 +319,10 @@ module OpenC3
|
|
|
319
319
|
# primitive. All other values are simply returned.
|
|
320
320
|
#
|
|
321
321
|
# @param value [Object]
|
|
322
|
+
# @param default [Object] Returned if value is nil
|
|
322
323
|
# @return [true|false|Object]
|
|
323
|
-
def self.handle_true_false(value)
|
|
324
|
+
def self.handle_true_false(value, default: nil)
|
|
325
|
+
return default if value.nil?
|
|
324
326
|
if String === value
|
|
325
327
|
case value.upcase
|
|
326
328
|
when 'TRUE'
|
|
@@ -76,6 +76,9 @@ module OpenC3
|
|
|
76
76
|
|
|
77
77
|
def connect
|
|
78
78
|
super() # Reset the protocols
|
|
79
|
+
# Discard a disconnect marker left over from a previous connection. Entries only
|
|
80
|
+
# wake read_interface, which finds files by listing the folder, so none are lost.
|
|
81
|
+
@queue.clear
|
|
79
82
|
|
|
80
83
|
if @telemetry_read_folder
|
|
81
84
|
@listener = Listen.to(@telemetry_read_folder, force_polling: @polling) do |modified, added, removed|
|
|
@@ -96,7 +99,9 @@ module OpenC3
|
|
|
96
99
|
@sleeper.cancel if @sleeper
|
|
97
100
|
@listener.stop if @listener
|
|
98
101
|
@listener = nil
|
|
99
|
-
|
|
102
|
+
# Only wake read_interface for the first disconnect of a connection, otherwise the
|
|
103
|
+
# unconsumed marker would immediately disconnect the next connection
|
|
104
|
+
@queue << nil if @connected
|
|
100
105
|
super()
|
|
101
106
|
@connected = false
|
|
102
107
|
end
|
|
@@ -51,6 +51,10 @@ module OpenC3
|
|
|
51
51
|
|
|
52
52
|
# Connects the interface to its target(s)
|
|
53
53
|
def connect
|
|
54
|
+
# Discard anything still queued from a previous connection, such as a response to a
|
|
55
|
+
# request that completed after disconnect. A request still in flight across the
|
|
56
|
+
# reconnect can queue its response after this; that is a real response and is read normally.
|
|
57
|
+
@response_queue.clear
|
|
54
58
|
# Per https://github.com/lostisland/faraday/blob/main/lib/faraday/options/env.rb
|
|
55
59
|
# :timeout - time limit for the entire request (Integer in seconds)
|
|
56
60
|
# :open_timeout - time limit for just the connection phase (e.g. handshake) (Integer in seconds)
|
|
@@ -79,15 +83,19 @@ module OpenC3
|
|
|
79
83
|
|
|
80
84
|
# Disconnects the interface from its target(s)
|
|
81
85
|
def disconnect
|
|
86
|
+
was_connected = !@http.nil?
|
|
82
87
|
@http.close if @http
|
|
83
88
|
@http = nil
|
|
84
|
-
# Clear the response queue
|
|
85
|
-
while @response_queue.length > 0
|
|
86
|
-
@response_queue.pop
|
|
87
|
-
end
|
|
88
89
|
super()
|
|
89
|
-
#
|
|
90
|
-
|
|
90
|
+
# disconnect is called more than once per connection loss (e.g. again after
|
|
91
|
+
# read_interface returns nil), so only the first call queues the marker. Another
|
|
92
|
+
# marker would be left unconsumed and immediately disconnect the next connection,
|
|
93
|
+
# and clearing again could discard a marker the reader hasn't consumed yet.
|
|
94
|
+
if was_connected
|
|
95
|
+
@response_queue.clear
|
|
96
|
+
# Push nil to cause read_interface to return nil
|
|
97
|
+
@response_queue.push(nil)
|
|
98
|
+
end
|
|
91
99
|
end
|
|
92
100
|
|
|
93
101
|
# Called to convert a packet into a data buffer. Write protocols then
|
|
@@ -70,6 +70,9 @@ module OpenC3
|
|
|
70
70
|
# this interface
|
|
71
71
|
attr_accessor :disable_disconnect
|
|
72
72
|
|
|
73
|
+
# @return [Boolean] Flag indicating if the interface is allowed to read
|
|
74
|
+
attr_writer :read_allowed
|
|
75
|
+
|
|
73
76
|
# @return [StreamLogPair] StreamLogPair instance or nil
|
|
74
77
|
attr_accessor :stream_log_pair
|
|
75
78
|
|
|
@@ -326,6 +329,12 @@ module OpenC3
|
|
|
326
329
|
# Return packet
|
|
327
330
|
@read_count += 1
|
|
328
331
|
Logger.warn("#{@name}: Interface unexpectedly requested disconnect") unless packet
|
|
332
|
+
# Timestamp buffered reads with when the data arrived rather than when
|
|
333
|
+
# it was processed (otherwise received_time is set when it is handled)
|
|
334
|
+
if packet and !packet.received_time
|
|
335
|
+
data_time = read_queue_data_time
|
|
336
|
+
packet.received_time = data_time if data_time
|
|
337
|
+
end
|
|
329
338
|
return packet
|
|
330
339
|
end
|
|
331
340
|
rescue Exception => e
|
|
@@ -349,7 +358,8 @@ module OpenC3
|
|
|
349
358
|
if packet == :DISCONNECT
|
|
350
359
|
Logger.info("#{@name}: Protocol #{protocol.class} write_packet requested disconnect")
|
|
351
360
|
disconnect()
|
|
352
|
-
|
|
361
|
+
# Nothing was written so reject rather than report success
|
|
362
|
+
raise WriteRejectError.new("Protocol #{protocol.class} write_packet requested disconnect")
|
|
353
363
|
end
|
|
354
364
|
return if packet == :STOP
|
|
355
365
|
end
|
|
@@ -369,7 +379,8 @@ module OpenC3
|
|
|
369
379
|
if data == :DISCONNECT
|
|
370
380
|
Logger.info("#{@name}: Protocol #{protocol.class} write_data requested disconnect")
|
|
371
381
|
disconnect()
|
|
372
|
-
|
|
382
|
+
# Nothing was written so reject rather than report success
|
|
383
|
+
raise WriteRejectError.new("Protocol #{protocol.class} write_data requested disconnect")
|
|
373
384
|
end
|
|
374
385
|
return if data == :STOP
|
|
375
386
|
end
|
|
@@ -443,6 +454,19 @@ module OpenC3
|
|
|
443
454
|
config
|
|
444
455
|
end
|
|
445
456
|
|
|
457
|
+
# @return [Integer] The number of bytes waiting on the read queue.
|
|
458
|
+
# Interfaces which buffer raw reads override this (see ReadQueueInterface).
|
|
459
|
+
def read_queue_bytes
|
|
460
|
+
0
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# @return [Time, nil] When the data most recently returned by
|
|
464
|
+
# read_interface was read from the underlying source, or nil if unknown.
|
|
465
|
+
# Interfaces which buffer raw reads override this (see ReadQueueInterface).
|
|
466
|
+
def read_queue_data_time
|
|
467
|
+
nil
|
|
468
|
+
end
|
|
469
|
+
|
|
446
470
|
# @return [Boolean] Whether reading is allowed
|
|
447
471
|
def read_allowed?
|
|
448
472
|
@read_allowed
|
|
@@ -564,7 +588,7 @@ module OpenC3
|
|
|
564
588
|
# @return [String] Raw packet data
|
|
565
589
|
def read_interface_base(data, _extra = nil)
|
|
566
590
|
if @save_raw_data
|
|
567
|
-
@read_raw_data_time = Time.now
|
|
591
|
+
@read_raw_data_time = read_queue_data_time || Time.now
|
|
568
592
|
@read_raw_data = data.clone
|
|
569
593
|
end
|
|
570
594
|
@bytes_read += data.length
|