openc3 7.0.1 → 7.1.1
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/openc3cli +50 -3
- data/data/config/interface_modifiers.yaml +3 -1
- data/data/config/item_modifiers.yaml +1 -1
- data/data/config/microservice.yaml +15 -2
- data/data/config/parameter_modifiers.yaml +49 -7
- data/data/config/plugins.yaml +1 -0
- data/data/config/target.yaml +11 -0
- data/data/config/target_config.yaml +6 -2
- data/lib/openc3/api/api.rb +1 -0
- data/lib/openc3/api/calendar_api.rb +183 -0
- data/lib/openc3/api/cmd_api.rb +2 -1
- data/lib/openc3/api/metrics_api.rb +11 -1
- data/lib/openc3/api/tlm_api.rb +21 -6
- data/lib/openc3/core_ext/faraday.rb +1 -1
- data/lib/openc3/io/json_api.rb +1 -1
- data/lib/openc3/logs/log_writer.rb +3 -1
- data/lib/openc3/microservices/decom_common.rb +128 -0
- data/lib/openc3/microservices/decom_microservice.rb +30 -97
- data/lib/openc3/microservices/interface_decom_common.rb +6 -2
- data/lib/openc3/microservices/interface_microservice.rb +10 -8
- data/lib/openc3/microservices/log_microservice.rb +1 -1
- data/lib/openc3/microservices/microservice.rb +3 -2
- data/lib/openc3/microservices/queue_microservice.rb +1 -1
- data/lib/openc3/microservices/scope_cleanup_microservice.rb +60 -46
- data/lib/openc3/microservices/text_log_microservice.rb +1 -2
- data/lib/openc3/models/cvt_model.rb +24 -13
- data/lib/openc3/models/db_sharded_model.rb +110 -0
- data/lib/openc3/models/interface_model.rb +9 -0
- data/lib/openc3/models/interface_status_model.rb +33 -3
- data/lib/openc3/models/metric_model.rb +96 -37
- data/lib/openc3/models/microservice_model.rb +7 -0
- data/lib/openc3/models/microservice_status_model.rb +30 -3
- data/lib/openc3/models/plugin_model.rb +20 -8
- data/lib/openc3/models/queue_model.rb +36 -46
- data/lib/openc3/models/reingest_job_model.rb +153 -0
- data/lib/openc3/models/scope_model.rb +3 -2
- data/lib/openc3/models/script_status_model.rb +4 -20
- data/lib/openc3/models/target_model.rb +113 -100
- data/lib/openc3/models/trigger_model.rb +1 -1
- data/lib/openc3/packets/packet_config.rb +4 -1
- data/lib/openc3/packets/parsers/xtce_parser.rb +23 -1
- data/lib/openc3/script/script.rb +6 -4
- data/lib/openc3/script/script_runner.rb +4 -4
- data/lib/openc3/script/telemetry.rb +3 -3
- data/lib/openc3/script/web_socket_api.rb +29 -22
- data/lib/openc3/system/system.rb +20 -3
- data/lib/openc3/topics/command_decom_topic.rb +4 -2
- data/lib/openc3/topics/command_topic.rb +9 -5
- data/lib/openc3/topics/decom_interface_topic.rb +15 -10
- data/lib/openc3/topics/interface_topic.rb +71 -29
- data/lib/openc3/topics/limits_event_topic.rb +62 -41
- data/lib/openc3/topics/router_topic.rb +61 -21
- data/lib/openc3/topics/system_events_topic.rb +18 -1
- data/lib/openc3/topics/telemetry_decom_topic.rb +3 -1
- data/lib/openc3/topics/telemetry_topic.rb +4 -2
- data/lib/openc3/topics/topic.rb +77 -5
- data/lib/openc3/utilities/aws_bucket.rb +2 -0
- data/lib/openc3/utilities/cli_generator.rb +10 -2
- data/lib/openc3/utilities/metric.rb +15 -1
- data/lib/openc3/utilities/questdb_client.rb +173 -37
- data/lib/openc3/utilities/reingest_job.rb +377 -0
- data/lib/openc3/utilities/ruby_lex_utils.rb +2 -0
- data/lib/openc3/utilities/running_script.rb +8 -10
- data/lib/openc3/utilities/store_autoload.rb +78 -52
- data/lib/openc3/utilities/store_queued.rb +20 -12
- data/lib/openc3/version.rb +5 -5
- data/templates/microservice/microservices/TEMPLATE/microservice.py +9 -0
- data/templates/plugin/plugin.gemspec +13 -1
- data/templates/tool_angular/package.json +2 -2
- data/templates/tool_react/package.json +1 -1
- data/templates/tool_svelte/package.json +1 -1
- data/templates/tool_vue/package.json +3 -3
- data/templates/tool_vue/src/router.js +2 -2
- data/templates/widget/package.json +2 -2
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 719b55747a7004cc44da1976c3f15eec6c67a3fca702b56792862f59f02cb8b5
|
|
4
|
+
data.tar.gz: c80082e75c059dac71d15a56264cba9c968f82c494e564e33b598c75ac01a6c2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5b3c1ca82717f1debd25ab4a6ac0239aa9baee2b5f736f61b7f52103397b608ab3de8fd0d50e10f0a4481b507b2a519987a4c4c68f7d0f3770cd48f3e165b8eb
|
|
7
|
+
data.tar.gz: d2bc96742a2555be7c8149dd728155a76ba7c1a16474eb26a36388bdedaacc1fba9c447ab373beebd8fd58ac81248541531de1a163bc156be195c76e34b515f2
|
data/bin/openc3cli
CHANGED
|
@@ -45,7 +45,8 @@ require 'irb/completion'
|
|
|
45
45
|
require 'digest'
|
|
46
46
|
require 'argon2'
|
|
47
47
|
|
|
48
|
-
$
|
|
48
|
+
$redis_shardnum = ENV['OPENC3_SHARDNUM'] || "0"
|
|
49
|
+
$redis_url = "redis://#{ENV['OPENC3_REDIS_HOSTNAME'].to_s.gsub("SHARDNUM", $redis_shardnum)}:#{ENV['OPENC3_REDIS_PORT']}"
|
|
49
50
|
|
|
50
51
|
ERROR_CODE = 1
|
|
51
52
|
|
|
@@ -443,6 +444,9 @@ def unload_plugin(plugin_name, scope:)
|
|
|
443
444
|
plugin_model = OpenC3::PluginModel.get_model(name: plugin_name, scope: scope)
|
|
444
445
|
plugin_model.destroy
|
|
445
446
|
OpenC3::LocalMode.remove_local_plugin(plugin_name, scope: scope)
|
|
447
|
+
# Remove the backing gem now that no PluginModel references it,
|
|
448
|
+
# so it disappears from the admin Packages tab.
|
|
449
|
+
OpenC3::PluginModel.cleanup_gem(plugin_name, scope: scope)
|
|
446
450
|
OpenC3::Logger.info("PluginModel destroyed: #{plugin_name}", scope: scope)
|
|
447
451
|
rescue => e
|
|
448
452
|
abort("Error uninstalling plugin: #{scope}: #{plugin_name}: #{e.formatted}")
|
|
@@ -1143,6 +1147,48 @@ if not ARGV[0].nil? # argument(s) given
|
|
|
1143
1147
|
end
|
|
1144
1148
|
cli_pkg_uninstall(ARGV[1], scope: ARGV[2])
|
|
1145
1149
|
|
|
1150
|
+
when 'reingest'
|
|
1151
|
+
# Internal command spawned by StorageController via ProcessManager so the
|
|
1152
|
+
# reingest runs in its own process and System singleton resets cannot
|
|
1153
|
+
# collide with the cmd-tlm-api Rails server.
|
|
1154
|
+
if ARGV[1].nil? || ARGV[2].nil? || ARGV[1] == '--help' || ARGV[1] == '-h'
|
|
1155
|
+
puts "Usage: cli reingest JOB_ID SCOPE"
|
|
1156
|
+
exit(ARGV[1].nil? ? 1 : 0)
|
|
1157
|
+
end
|
|
1158
|
+
require 'openc3/utilities/reingest_job'
|
|
1159
|
+
require 'openc3/models/reingest_job_model'
|
|
1160
|
+
job_id = ARGV[1]
|
|
1161
|
+
scope = ARGV[2]
|
|
1162
|
+
job = OpenC3::ReingestJobModel.get_model(name: job_id, scope: scope)
|
|
1163
|
+
if job.nil?
|
|
1164
|
+
OpenC3::Logger.error("Reingest job #{job_id} not found in scope #{scope}")
|
|
1165
|
+
exit(1)
|
|
1166
|
+
end
|
|
1167
|
+
begin
|
|
1168
|
+
OpenC3::ReingestJob.new(
|
|
1169
|
+
job_id: job_id,
|
|
1170
|
+
files: job.files,
|
|
1171
|
+
path: job.path,
|
|
1172
|
+
bucket: job.bucket,
|
|
1173
|
+
scope: scope,
|
|
1174
|
+
target_version: job.target_version,
|
|
1175
|
+
).run
|
|
1176
|
+
rescue Exception => e
|
|
1177
|
+
# ReingestJob#run already marks Crashed for errors raised during the run.
|
|
1178
|
+
# This catches failures from the constructor itself (or anything before
|
|
1179
|
+
# run gets its rescue installed) so the model doesn't sit in Queued forever.
|
|
1180
|
+
OpenC3::Logger.error("Reingest job #{job_id} crashed before run: #{e.formatted}")
|
|
1181
|
+
begin
|
|
1182
|
+
job.state = 'Crashed'
|
|
1183
|
+
job.error = e.message
|
|
1184
|
+
job.finished_at = Time.now.utc.iso8601
|
|
1185
|
+
job.update
|
|
1186
|
+
rescue => e2
|
|
1187
|
+
OpenC3::Logger.error("Reingest job #{job_id} failed to mark Crashed: #{e2.message}")
|
|
1188
|
+
end
|
|
1189
|
+
exit(1)
|
|
1190
|
+
end
|
|
1191
|
+
|
|
1146
1192
|
when 'generate'
|
|
1147
1193
|
# To test against a local copy call this file from the root cosmos directory like this:
|
|
1148
1194
|
# ruby -Iopenc3/lib openc3/bin/openc3cli generate ...
|
|
@@ -1390,8 +1436,9 @@ if not ARGV[0].nil? # argument(s) given
|
|
|
1390
1436
|
end
|
|
1391
1437
|
end
|
|
1392
1438
|
end
|
|
1393
|
-
# Unless explicitly disabled, ensure the tools bucket is public
|
|
1394
|
-
|
|
1439
|
+
# Unless explicitly disabled, ensure the tools bucket is public.
|
|
1440
|
+
# OPENC3_TOOLS_BUCKET_PRIVATE keeps the tools bucket private; the cmd-tlm-api proxies reads via ToolsController.
|
|
1441
|
+
unless ENV.fetch("OPENC3_NO_BUCKET_POLICY", false) || ENV.fetch("OPENC3_TOOLS_BUCKET_PRIVATE", false)
|
|
1395
1442
|
client.ensure_public(ENV['OPENC3_TOOLS_BUCKET'])
|
|
1396
1443
|
end
|
|
1397
1444
|
# Always ensure the scriptrunner policy is in place since it is required for script execution
|
|
@@ -212,7 +212,9 @@ WORK_DIR:
|
|
|
212
212
|
WORK_DIR '/openc3/lib/openc3/microservices'
|
|
213
213
|
PORT:
|
|
214
214
|
summary: Open port for the microservice
|
|
215
|
-
description:
|
|
215
|
+
description:
|
|
216
|
+
Kubernetes needs a Service to be applied to open a port so this is required for Kubernetes support
|
|
217
|
+
See [Exposing Microservices](/docs/guides/exposing-microservices) for more information.
|
|
216
218
|
since: 5.7.0
|
|
217
219
|
parameters:
|
|
218
220
|
- name: Number
|
|
@@ -193,4 +193,4 @@ HIDDEN:
|
|
|
193
193
|
description: This item will not appear in PacketViewer or Item Choosers.
|
|
194
194
|
It also hides this item from appearing in the Script Runner popup helper
|
|
195
195
|
when writing scripts. The item will also not be included in decom data.
|
|
196
|
-
since: 6.10.
|
|
196
|
+
since: 6.10.0
|
|
@@ -40,7 +40,9 @@ MICROSERVICE:
|
|
|
40
40
|
WORK_DIR .
|
|
41
41
|
PORT:
|
|
42
42
|
summary: Open port for the microservice
|
|
43
|
-
description:
|
|
43
|
+
description:
|
|
44
|
+
Kubernetes needs a Service to be applied to open a port so this is required for Kubernetes support.
|
|
45
|
+
See [Exposing Microservices](/docs/guides/exposing-microservices) for more information.
|
|
44
46
|
since: 5.0.10
|
|
45
47
|
parameters:
|
|
46
48
|
- name: Number
|
|
@@ -168,11 +170,22 @@ MICROSERVICE:
|
|
|
168
170
|
since: 6.0.0
|
|
169
171
|
parameters:
|
|
170
172
|
- name: Shard
|
|
171
|
-
required:
|
|
173
|
+
required: true
|
|
172
174
|
description: Shard number starting from 0
|
|
173
175
|
values: \d+
|
|
174
176
|
example: |
|
|
175
177
|
SHARD 0
|
|
178
|
+
DB_SHARD:
|
|
179
|
+
summary: Shard for target database database if sharding Redis/TSDB
|
|
180
|
+
description: DB Shard. Only used if running multiple database shards typically in Kubernetes
|
|
181
|
+
since: 7.1.0
|
|
182
|
+
parameters:
|
|
183
|
+
- name: DB Shard
|
|
184
|
+
required: true
|
|
185
|
+
description: DB Shard number starting from 0
|
|
186
|
+
values: \d+
|
|
187
|
+
example: |
|
|
188
|
+
DB_SHARD 0
|
|
176
189
|
STOPPED:
|
|
177
190
|
summary: Initially creates the microservice in a stopped state (not enabled)
|
|
178
191
|
since: 6.2.0
|
|
@@ -82,14 +82,15 @@ WRITE_CONVERSION:
|
|
|
82
82
|
[INST inst_cmds.txt](https://github.com/OpenC3/cosmos/blob/main/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST/cmd_tlm/inst_cmds.txt)
|
|
83
83
|
or [INST2 inst_cmds.txt](https://github.com/OpenC3/cosmos/blob/main/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST2/cmd_tlm/inst_cmds.txt).
|
|
84
84
|
|
|
85
|
-
:::info
|
|
85
|
+
:::info[Multiple write conversions on command parameters]
|
|
86
86
|
When a command is built, each item gets written (and write conversions are run)
|
|
87
87
|
to set the default value. Then items are written (again write conversions are run)
|
|
88
88
|
with user provided values. Thus write conversions can be run twice. Also there are
|
|
89
89
|
no guarantees which parameters have already been written. The packet itself has a
|
|
90
|
-
given_values
|
|
90
|
+
`given_values` attribute which can be used to retrieve a hash of the user provided
|
|
91
91
|
values to the command. That can be used to check parameter values passed in.
|
|
92
92
|
:::
|
|
93
|
+
|
|
93
94
|
parameters:
|
|
94
95
|
- name: Class Filename
|
|
95
96
|
required: true
|
|
@@ -105,9 +106,48 @@ WRITE_CONVERSION:
|
|
|
105
106
|
to the class constructor.
|
|
106
107
|
values: .*
|
|
107
108
|
ruby_example: |
|
|
108
|
-
WRITE_CONVERSION
|
|
109
|
+
# Example command with a WRITE_CONVERSION that sets a command parameter
|
|
110
|
+
# based on the given values of other parameters
|
|
111
|
+
COMMAND INST BLOCK BIG_ENDIAN "Send variable block of data"
|
|
112
|
+
APPEND_PARAMETER BYTE 8 UINT MIN MAX 0x55 "Byte to duplicate"
|
|
113
|
+
FORMAT_STRING "0x%0X"
|
|
114
|
+
APPEND_PARAMETER LENGTH 32 UINT MIN MAX 0 "Length of data"
|
|
115
|
+
APPEND_PARAMETER DATA 0 BLOCK "" "Variable block of data"
|
|
116
|
+
WRITE_CONVERSION block_conversion.rb
|
|
117
|
+
HIDDEN # Because we're filling it in with a conversion
|
|
118
|
+
|
|
119
|
+
# Implemented in INST/lib/block_conversion.rb:
|
|
120
|
+
require 'openc3/conversions/conversion'
|
|
121
|
+
module OpenC3
|
|
122
|
+
class BlockConversion < Conversion
|
|
123
|
+
def call(value, packet, buffer)
|
|
124
|
+
# Use the packet.given_values hash to access user provided values to the command
|
|
125
|
+
byte = packet.given_values['BYTE'] || 0x55
|
|
126
|
+
length = packet.given_values['LENGTH'] || 0
|
|
127
|
+
[byte].pack('C') * length
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
109
131
|
python_example: |
|
|
110
|
-
WRITE_CONVERSION
|
|
132
|
+
# Example command with a WRITE_CONVERSION that sets a command parameter
|
|
133
|
+
# based on the given values of other parameters
|
|
134
|
+
COMMAND INST BLOCK BIG_ENDIAN "Send variable block of data"
|
|
135
|
+
APPEND_PARAMETER BYTE 8 UINT MIN MAX 0x55 "Byte to duplicate"
|
|
136
|
+
FORMAT_STRING "0x%0X"
|
|
137
|
+
APPEND_PARAMETER LENGTH 32 UINT MIN MAX 0 "Length of data"
|
|
138
|
+
APPEND_PARAMETER DATA 0 BLOCK "" "Variable block of data"
|
|
139
|
+
WRITE_CONVERSION block_conversion.py
|
|
140
|
+
HIDDEN # Because we're filling it in with a conversion
|
|
141
|
+
|
|
142
|
+
# Implemented in INST/lib/block_conversion.py:
|
|
143
|
+
from openc3.conversions.conversion import Conversion
|
|
144
|
+
class BlockConversion(Conversion):
|
|
145
|
+
def call(self, value, packet, buffer):
|
|
146
|
+
# Use the packet.given_values hash to access user provided values to the command
|
|
147
|
+
byte = packet.given_values.get('BYTE', 0x55)
|
|
148
|
+
length = packet.given_values.get('LENGTH', 0)
|
|
149
|
+
return bytes([byte]) * length
|
|
150
|
+
|
|
111
151
|
POLY_WRITE_CONVERSION:
|
|
112
152
|
summary: Adds a polynomial conversion factor to the current command parameter
|
|
113
153
|
description: See [Polynomial Conversion](/docs/configuration/conversions#polynomial_conversion) for more information.
|
|
@@ -127,14 +167,15 @@ GENERIC_WRITE_CONVERSION_START:
|
|
|
127
167
|
value. The GENERIC_WRITE_CONVERSION_END keyword specifies that all lines of
|
|
128
168
|
code for the conversion have been given.
|
|
129
169
|
|
|
130
|
-
:::info
|
|
170
|
+
:::info[Multiple write conversions on command parameters]
|
|
131
171
|
When a command is built, each item gets written (and write conversions are run)
|
|
132
172
|
to set the default value. Then items are written (again write conversions are run)
|
|
133
173
|
with user provided values. Thus write conversions can be run twice. Also there are
|
|
134
174
|
no guarantees which parameters have already been written. The packet itself has a
|
|
135
|
-
given_values
|
|
175
|
+
`given_values` attribute which can be used to retrieve a hash of the user provided
|
|
136
176
|
values to the command. That can be used to check parameter values passed in.
|
|
137
177
|
:::
|
|
178
|
+
|
|
138
179
|
warning: Generic conversions are not a good long term solution. Consider creating
|
|
139
180
|
a conversion class and using WRITE_CONVERSION instead. WRITE_CONVERSION is easier
|
|
140
181
|
to debug and higher performance.
|
|
@@ -171,4 +212,5 @@ HIDDEN:
|
|
|
171
212
|
summary: Hides this parameter from all the OpenC3 tools
|
|
172
213
|
description: This item will not appear in CmdSender.
|
|
173
214
|
It also hides this item from appearing in the Script Runner popup helper
|
|
174
|
-
when writing scripts. The parameter should not be provided to commands.
|
|
215
|
+
when writing scripts. The parameter should not be provided to commands.
|
|
216
|
+
since: 6.10.0
|
data/data/config/plugins.yaml
CHANGED
data/data/config/target.yaml
CHANGED
|
@@ -159,3 +159,14 @@ TARGET:
|
|
|
159
159
|
values: \d+
|
|
160
160
|
example: |
|
|
161
161
|
SHARD 0
|
|
162
|
+
DB_SHARD:
|
|
163
|
+
summary: Shard for target database database if sharding Redis/TSDB
|
|
164
|
+
description: DB Shard. Only used if running multiple database shards typically in Kubernetes
|
|
165
|
+
since: 7.1.0
|
|
166
|
+
parameters:
|
|
167
|
+
- name: DB Shard
|
|
168
|
+
required: true
|
|
169
|
+
description: DB Shard number starting from 0
|
|
170
|
+
values: \d+
|
|
171
|
+
example: |
|
|
172
|
+
DB_SHARD 0
|
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
LANGUAGE:
|
|
3
3
|
summary: Programming language of the target interfaces and microservices
|
|
4
4
|
description: The target language must be either Ruby or Python. The language
|
|
5
|
-
determines how the target's interfaces and microservices are run.
|
|
6
|
-
|
|
5
|
+
determines how the target's interfaces and microservices are run. A target
|
|
6
|
+
must pick one language for its interfaces and microservices — you cannot
|
|
7
|
+
mix Ruby and Python interfaces/microservices within the same target. Scripts
|
|
8
|
+
executed in Script Runner are independent of this setting and may be written
|
|
9
|
+
in either Ruby or Python regardless of the target's LANGUAGE. Note that both
|
|
10
|
+
Ruby and Python still use ERB to perform templating.
|
|
7
11
|
example: LANGUAGE python
|
|
8
12
|
parameters:
|
|
9
13
|
- language: Programming language
|
data/lib/openc3/api/api.rb
CHANGED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
3
|
+
# Copyright 2026 OpenC3, Inc.
|
|
4
|
+
# All Rights Reserved.
|
|
5
|
+
#
|
|
6
|
+
# This program is distributed in the hope that it will be useful,
|
|
7
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
8
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
9
|
+
# See LICENSE.md for more details.
|
|
10
|
+
#
|
|
11
|
+
# This file may also be used under the terms of a commercial license
|
|
12
|
+
# if purchased from OpenC3, Inc.
|
|
13
|
+
|
|
14
|
+
require 'date'
|
|
15
|
+
require 'openc3/models/timeline_model'
|
|
16
|
+
require 'openc3/models/activity_model'
|
|
17
|
+
require 'openc3/topics/timeline_topic'
|
|
18
|
+
|
|
19
|
+
module OpenC3
|
|
20
|
+
module Api
|
|
21
|
+
# NOTE: These methods are intentionally NOT added to WHITELIST. Their signatures
|
|
22
|
+
# match openc3/lib/openc3/script/calendar.rb (no manual:/token: kwargs), so they
|
|
23
|
+
# cannot be dispatched via JSON-RPC (which auto-injects manual/token from headers).
|
|
24
|
+
# The script-side calendar methods reach the server through the timeline/activity
|
|
25
|
+
# HTTP controllers, which call these helpers after performing their own
|
|
26
|
+
# authorization.
|
|
27
|
+
|
|
28
|
+
# Returns an array of all timelines for the given scope.
|
|
29
|
+
def list_timelines(scope: $openc3_scope)
|
|
30
|
+
ret = []
|
|
31
|
+
TimelineModel.all.each do |timeline, value|
|
|
32
|
+
if scope == timeline.split('__')[0]
|
|
33
|
+
ret << value
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
ret
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Creates a new timeline and deploys its microservice.
|
|
40
|
+
# @return [Hash] the created timeline as a hash
|
|
41
|
+
def create_timeline(name, color: nil, scope: $openc3_scope)
|
|
42
|
+
model = TimelineModel.new(name: name, color: color, scope: scope)
|
|
43
|
+
model.create()
|
|
44
|
+
model.deploy()
|
|
45
|
+
model.as_json()
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Hash, nil] the timeline as a hash, or nil if not found
|
|
49
|
+
def get_timeline(name, scope: $openc3_scope)
|
|
50
|
+
model = TimelineModel.get(name: name, scope: scope)
|
|
51
|
+
return nil if model.nil?
|
|
52
|
+
model.as_json()
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Updates the color of an existing timeline.
|
|
56
|
+
# @return [Hash, nil] the updated timeline as a hash, or nil if not found
|
|
57
|
+
def set_timeline_color(name, color, scope: $openc3_scope)
|
|
58
|
+
model = TimelineModel.get(name: name, scope: scope)
|
|
59
|
+
return nil if model.nil?
|
|
60
|
+
model.color = color
|
|
61
|
+
model.update()
|
|
62
|
+
model.notify(kind: 'updated')
|
|
63
|
+
model.as_json()
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Updates the execute flag of an existing timeline.
|
|
67
|
+
# @return [Hash, nil] the updated timeline as a hash, or nil if not found
|
|
68
|
+
def set_timeline_execute(name, enable, scope: $openc3_scope)
|
|
69
|
+
model = TimelineModel.get(name: name, scope: scope)
|
|
70
|
+
return nil if model.nil?
|
|
71
|
+
model.execute = enable
|
|
72
|
+
model.update()
|
|
73
|
+
model.notify(kind: 'updated')
|
|
74
|
+
model.as_json()
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Deletes a timeline (and optionally all of its activities when force is true).
|
|
78
|
+
# @return [Hash, nil] {'name' => name}, or nil if not found
|
|
79
|
+
def delete_timeline(name, force: false, scope: $openc3_scope)
|
|
80
|
+
model = TimelineModel.get(name: name, scope: scope)
|
|
81
|
+
return nil if model.nil?
|
|
82
|
+
TimelineModel.delete(name: name, scope: scope, force: force)
|
|
83
|
+
model.undeploy()
|
|
84
|
+
model.notify(kind: 'deleted')
|
|
85
|
+
{ 'name' => name }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Creates a new activity on the specified timeline.
|
|
89
|
+
# username is read from data['username'] if present and is used for the audit event.
|
|
90
|
+
# @return [Hash] the created activity as a hash
|
|
91
|
+
def create_timeline_activity(name, kind:, start:, stop:, data: {}, recurring: nil, scope: $openc3_scope)
|
|
92
|
+
data ||= {}
|
|
93
|
+
hash = {
|
|
94
|
+
kind: kind,
|
|
95
|
+
start: _cal_to_epoch(start),
|
|
96
|
+
stop: _cal_to_epoch(stop),
|
|
97
|
+
data: data,
|
|
98
|
+
}
|
|
99
|
+
if recurring
|
|
100
|
+
recurring = recurring.dup
|
|
101
|
+
if recurring['end']
|
|
102
|
+
recurring['end'] = _cal_to_epoch(recurring['end'])
|
|
103
|
+
end
|
|
104
|
+
hash[:recurring] = recurring
|
|
105
|
+
end
|
|
106
|
+
model = ActivityModel.from_json(hash, name: name, scope: scope)
|
|
107
|
+
model.create(username: data['username'])
|
|
108
|
+
model.as_json()
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Updates an existing activity on the specified timeline.
|
|
112
|
+
# @return [Hash, nil] the updated activity as a hash, or nil if not found
|
|
113
|
+
def update_timeline_activity(name, id:, kind:, start:, stop:, uuid:, data: {}, scope: $openc3_scope)
|
|
114
|
+
data ||= {}
|
|
115
|
+
model = ActivityModel.score(name: name, score: id.to_i, uuid: uuid, scope: scope)
|
|
116
|
+
return nil if model.nil?
|
|
117
|
+
model.update(
|
|
118
|
+
start: _cal_to_epoch(start),
|
|
119
|
+
stop: _cal_to_epoch(stop),
|
|
120
|
+
kind: kind,
|
|
121
|
+
data: data,
|
|
122
|
+
username: data['username'],
|
|
123
|
+
)
|
|
124
|
+
model.as_json()
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @return [Hash, nil] the activity as a hash, or nil if not found
|
|
128
|
+
def get_timeline_activity(name, start, uuid, scope: $openc3_scope)
|
|
129
|
+
model = ActivityModel.score(name: name, score: start.to_i, uuid: uuid, scope: scope)
|
|
130
|
+
return nil if model.nil?
|
|
131
|
+
model.as_json()
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Returns activities on the timeline in the given window.
|
|
135
|
+
# When start/stop are nil, defaults to a window of [now - 7 days, now + 7 days].
|
|
136
|
+
# When limit is nil, defaults to one event per minute over the window.
|
|
137
|
+
# @return [Array<Hash>] the matching activities
|
|
138
|
+
def get_timeline_activities(name, start: nil, stop: nil, limit: nil, scope: $openc3_scope)
|
|
139
|
+
now = DateTime.now.new_offset(0)
|
|
140
|
+
start_score = start.nil? ? (now - 7).strftime('%s').to_i : _cal_to_epoch(start)
|
|
141
|
+
stop_score = stop.nil? ? (now + 7).strftime('%s').to_i : _cal_to_epoch(stop)
|
|
142
|
+
limit ||= ((stop_score - start_score) / 60).to_i
|
|
143
|
+
ActivityModel.get(name: name, scope: scope, start: start_score, stop: stop_score, limit: limit)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Removes an activity (or all members of its recurring group when recurring is truthy).
|
|
147
|
+
# @return [Integer] number of activities removed (0 indicates not found)
|
|
148
|
+
def delete_timeline_activity(name, start, uuid, recurring: nil, scope: $openc3_scope)
|
|
149
|
+
ActivityModel.destroy(name: name, scope: scope, score: start.to_i, uuid: uuid, recurring: recurring)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @return [Integer] count of activities on the timeline
|
|
153
|
+
def count_timeline_activities(name, scope: $openc3_scope)
|
|
154
|
+
ActivityModel.count(name: name, scope: scope)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Commits an event to an existing activity.
|
|
158
|
+
# @return [Hash, nil] the activity as a hash, or nil if not found
|
|
159
|
+
def commit_timeline_activity(name, start, uuid, status:, message: nil, scope: $openc3_scope)
|
|
160
|
+
model = ActivityModel.score(name: name, score: start.to_i, uuid: uuid, scope: scope)
|
|
161
|
+
return nil if model.nil?
|
|
162
|
+
model.commit(status: status, message: message)
|
|
163
|
+
model.as_json()
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Convert a value to an epoch integer. Accepts Integer/Numeric (treated as already-epoch),
|
|
167
|
+
# numeric strings, and ISO-style date/time strings or DateTime/Time objects.
|
|
168
|
+
def _cal_to_epoch(value)
|
|
169
|
+
case value
|
|
170
|
+
when Integer
|
|
171
|
+
value
|
|
172
|
+
when Numeric
|
|
173
|
+
value.to_i
|
|
174
|
+
when DateTime, Time, Date
|
|
175
|
+
value.to_datetime.strftime('%s').to_i
|
|
176
|
+
else
|
|
177
|
+
s = value.to_s
|
|
178
|
+
return s.to_i if s.match?(/\A-?\d+\z/)
|
|
179
|
+
DateTime.parse(s).strftime('%s').to_i
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
data/lib/openc3/api/cmd_api.rb
CHANGED
|
@@ -183,7 +183,8 @@ module OpenC3
|
|
|
183
183
|
authorize(permission: 'cmd_info', target_name: target_name, packet_name: command_name, manual: manual, scope: scope, token: token)
|
|
184
184
|
TargetModel.packet(target_name, command_name, type: :CMD, scope: scope)
|
|
185
185
|
topic = "#{scope}__COMMAND__{#{target_name}}__#{command_name}"
|
|
186
|
-
|
|
186
|
+
db_shard = Store.db_shard_for_target(target_name, scope: scope)
|
|
187
|
+
msg_id, msg_hash = Topic.get_newest_message(topic, db_shard: db_shard)
|
|
187
188
|
if msg_id
|
|
188
189
|
msg_hash['buffer'] = msg_hash['buffer'].b
|
|
189
190
|
return msg_hash
|
|
@@ -28,9 +28,11 @@ module OpenC3
|
|
|
28
28
|
DELAY_METRICS['log_topic_delta_seconds'] = 0.0
|
|
29
29
|
DELAY_METRICS['router_topic_delta_seconds'] = 0.0
|
|
30
30
|
DELAY_METRICS['text_log_topic_delta_seconds'] = 0.0
|
|
31
|
+
DELAY_METRICS['tsdb_ingest_topic_delta_seconds'] = 0.0
|
|
31
32
|
|
|
32
33
|
DURATION_METRICS = {}
|
|
33
34
|
DURATION_METRICS['decom_duration_seconds'] = 0.0
|
|
35
|
+
DURATION_METRICS['tsdb_ingest_duration_seconds'] = 0.0
|
|
34
36
|
|
|
35
37
|
SUM_METRICS = {}
|
|
36
38
|
SUM_METRICS['cleanup_total'] = 0
|
|
@@ -48,6 +50,8 @@ module OpenC3
|
|
|
48
50
|
SUM_METRICS['router_directive_total'] = 0
|
|
49
51
|
SUM_METRICS['text_log_total'] = 0
|
|
50
52
|
SUM_METRICS['text_log_error_total'] = 0
|
|
53
|
+
SUM_METRICS['tsdb_ingest_total'] = 0
|
|
54
|
+
SUM_METRICS['tsdb_ingest_error_total'] = 0
|
|
51
55
|
|
|
52
56
|
def get_metrics(manual: false, scope: $openc3_scope, token: $openc3_token)
|
|
53
57
|
authorize(permission: 'system', manual: manual, scope: scope, token: token)
|
|
@@ -79,7 +83,13 @@ module OpenC3
|
|
|
79
83
|
result.merge!(duration_metrics)
|
|
80
84
|
result.merge!(sum_metrics)
|
|
81
85
|
|
|
82
|
-
|
|
86
|
+
redis_metrics = MetricModel.redis_metrics
|
|
87
|
+
redis_metrics.each do |_db_shard, values|
|
|
88
|
+
values.each do |key, value|
|
|
89
|
+
existing = result[key]
|
|
90
|
+
result[key] = value if existing.nil? or value > existing
|
|
91
|
+
end
|
|
92
|
+
end
|
|
83
93
|
|
|
84
94
|
return result
|
|
85
95
|
end
|
data/lib/openc3/api/tlm_api.rb
CHANGED
|
@@ -117,7 +117,7 @@ module OpenC3
|
|
|
117
117
|
# @param packet_name [String] Packet name of the packet
|
|
118
118
|
# @param item_hash [Hash] Hash of item_name and value for each item you want to change from the current value table
|
|
119
119
|
# @param type [Symbol] Telemetry type, :RAW, :CONVERTED (default), :FORMATTED
|
|
120
|
-
def inject_tlm(target_name, packet_name, item_hash = nil, type: :CONVERTED, manual: false, scope: $openc3_scope, token: $openc3_token)
|
|
120
|
+
def inject_tlm(target_name, packet_name, item_hash = nil, type: :CONVERTED, stored: false, manual: false, scope: $openc3_scope, token: $openc3_token)
|
|
121
121
|
authorize(permission: 'tlm_set', target_name: target_name, packet_name: packet_name, manual: manual, scope: scope, token: token)
|
|
122
122
|
type = type.to_s.intern
|
|
123
123
|
target_name = target_name.upcase
|
|
@@ -155,9 +155,9 @@ module OpenC3
|
|
|
155
155
|
|
|
156
156
|
# Use an interface microservice if it exists, other use the decom microservice
|
|
157
157
|
if interface_name
|
|
158
|
-
InterfaceTopic.inject_tlm(interface_name, target_name, packet_name, item_hash, type: type, scope: scope)
|
|
158
|
+
InterfaceTopic.inject_tlm(interface_name, target_name, packet_name, item_hash, type: type, stored: stored, scope: scope)
|
|
159
159
|
else
|
|
160
|
-
DecomInterfaceTopic.inject_tlm(target_name, packet_name, item_hash, type: type, scope: scope)
|
|
160
|
+
DecomInterfaceTopic.inject_tlm(target_name, packet_name, item_hash, type: type, stored: stored, scope: scope)
|
|
161
161
|
end
|
|
162
162
|
end
|
|
163
163
|
|
|
@@ -221,7 +221,8 @@ module OpenC3
|
|
|
221
221
|
return msg_hash
|
|
222
222
|
else
|
|
223
223
|
topic = "#{scope}__TELEMETRY__{#{target_name}}__#{packet_name}"
|
|
224
|
-
|
|
224
|
+
db_shard = Store.db_shard_for_target(target_name, scope: scope)
|
|
225
|
+
msg_id, msg_hash = Topic.get_newest_message(topic, db_shard: db_shard)
|
|
225
226
|
if msg_id
|
|
226
227
|
msg_hash['buffer'] = msg_hash['buffer'].b
|
|
227
228
|
return msg_hash
|
|
@@ -446,7 +447,8 @@ module OpenC3
|
|
|
446
447
|
packet_name = packet_name.upcase
|
|
447
448
|
authorize(permission: 'tlm', target_name: target_name, packet_name: packet_name, manual: manual, scope: scope, token: token)
|
|
448
449
|
topic = "#{scope}__DECOM__{#{target_name}}__#{packet_name}"
|
|
449
|
-
|
|
450
|
+
db_shard = Store.db_shard_for_target(target_name, scope: scope)
|
|
451
|
+
id, = Topic.get_newest_message(topic, db_shard: db_shard)
|
|
450
452
|
results[topic] = id ? id : '0-0'
|
|
451
453
|
end
|
|
452
454
|
results.to_a.join(SUBSCRIPTION_DELIMITER)
|
|
@@ -463,7 +465,20 @@ module OpenC3
|
|
|
463
465
|
authorize(permission: 'tlm', manual: manual, scope: scope, token: token)
|
|
464
466
|
# Split the list of topic, ID values and turn it into a hash for easy updates
|
|
465
467
|
lookup = Hash[*id.split(SUBSCRIPTION_DELIMITER)]
|
|
466
|
-
|
|
468
|
+
# Group topics by db_shard for multi-shard support
|
|
469
|
+
db_shard_groups = {}
|
|
470
|
+
lookup.each do |topic, offset|
|
|
471
|
+
target_name = topic.match(/__\{?([^}_]+)\}?__/)[1] rescue nil
|
|
472
|
+
db_shard = Store.db_shard_for_target(target_name, scope: scope)
|
|
473
|
+
db_shard_groups[db_shard] ||= { topics: [], offsets: [] }
|
|
474
|
+
db_shard_groups[db_shard][:topics] << topic
|
|
475
|
+
db_shard_groups[db_shard][:offsets] << offset
|
|
476
|
+
end
|
|
477
|
+
xread = {}
|
|
478
|
+
db_shard_groups.each do |db_shard, group|
|
|
479
|
+
result = Topic.read_topics(group[:topics], group[:offsets], nil, count, db_shard: db_shard) # Always don't block
|
|
480
|
+
xread.merge!(result) if result
|
|
481
|
+
end
|
|
467
482
|
# Return the original ID and and empty array if we didn't get anything
|
|
468
483
|
packets = []
|
|
469
484
|
return [id, packets] if xread.empty?
|
data/lib/openc3/io/json_api.rb
CHANGED
|
@@ -65,7 +65,7 @@ module OpenC3
|
|
|
65
65
|
|
|
66
66
|
def _request(*method_params, **kw_params)
|
|
67
67
|
kw_params[:scope] = $openc3_scope unless kw_params[:scope]
|
|
68
|
-
kw_params[:json] = true
|
|
68
|
+
kw_params[:json] = true # This is JsonApi so should always be speaking json
|
|
69
69
|
@json_api.request(*method_params, **kw_params)
|
|
70
70
|
end
|
|
71
71
|
end
|
|
@@ -237,7 +237,9 @@ module OpenC3
|
|
|
237
237
|
# Now that the file is in S3, trim the Redis stream up until the previous file.
|
|
238
238
|
# This keeps one minute of data in Redis
|
|
239
239
|
instance.cleanup_offsets[index].each do |redis_topic, cleanup_offset|
|
|
240
|
-
|
|
240
|
+
target_match = redis_topic.match(/__\{?([^}_]+)\}?__/)
|
|
241
|
+
db_shard = target_match ? Store.db_shard_for_target(target_match[1]) : 0
|
|
242
|
+
Topic.trim_topic(redis_topic, cleanup_offset, db_shard: db_shard)
|
|
241
243
|
end
|
|
242
244
|
indexes_to_clear << index
|
|
243
245
|
end
|