openc3 7.0.1 → 7.1.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/openc3cli +47 -3
- data/data/config/item_modifiers.yaml +1 -1
- data/data/config/microservice.yaml +12 -1
- data/data/config/parameter_modifiers.yaml +49 -7
- data/data/config/target.yaml +11 -0
- data/data/config/target_config.yaml +6 -2
- 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 +26 -95
- 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/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/packets/packet_config.rb +4 -1
- data/lib/openc3/script/script.rb +2 -2
- 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 +8 -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 +2 -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 +3 -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/store_autoload.rb +78 -52
- data/lib/openc3/utilities/store_queued.rb +20 -12
- data/lib/openc3/version.rb +6 -6
- 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 +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa824943ce72cce586cdfe03c1a5a8395120801bd845174c25433a15bf986306
|
|
4
|
+
data.tar.gz: 620cc66576e14e7e696419e5be0b5704190452d29efc35a315a489099b20f82e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f392d51b2e5870128a995aa9e5717f9a4ed3bf0b8f55952b9141ec34898406734a2a834cf18d09b78d6e82f5675c2c0f3269e2ef62964ec58f2b96b8933ce80
|
|
7
|
+
data.tar.gz: a92a073ff672da55a44b85b700cb5b6a908208e9a10ddcad77828f02de0f877c3db2252d7d93857ab5b65d55b39594dfc109d72028a82cbfeaa6e468878eef94
|
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
|
|
|
@@ -1143,6 +1144,48 @@ if not ARGV[0].nil? # argument(s) given
|
|
|
1143
1144
|
end
|
|
1144
1145
|
cli_pkg_uninstall(ARGV[1], scope: ARGV[2])
|
|
1145
1146
|
|
|
1147
|
+
when 'reingest'
|
|
1148
|
+
# Internal command spawned by StorageController via ProcessManager so the
|
|
1149
|
+
# reingest runs in its own process and System singleton resets cannot
|
|
1150
|
+
# collide with the cmd-tlm-api Rails server.
|
|
1151
|
+
if ARGV[1].nil? || ARGV[2].nil? || ARGV[1] == '--help' || ARGV[1] == '-h'
|
|
1152
|
+
puts "Usage: cli reingest JOB_ID SCOPE"
|
|
1153
|
+
exit(ARGV[1].nil? ? 1 : 0)
|
|
1154
|
+
end
|
|
1155
|
+
require 'openc3/utilities/reingest_job'
|
|
1156
|
+
require 'openc3/models/reingest_job_model'
|
|
1157
|
+
job_id = ARGV[1]
|
|
1158
|
+
scope = ARGV[2]
|
|
1159
|
+
job = OpenC3::ReingestJobModel.get_model(name: job_id, scope: scope)
|
|
1160
|
+
if job.nil?
|
|
1161
|
+
OpenC3::Logger.error("Reingest job #{job_id} not found in scope #{scope}")
|
|
1162
|
+
exit(1)
|
|
1163
|
+
end
|
|
1164
|
+
begin
|
|
1165
|
+
OpenC3::ReingestJob.new(
|
|
1166
|
+
job_id: job_id,
|
|
1167
|
+
files: job.files,
|
|
1168
|
+
path: job.path,
|
|
1169
|
+
bucket: job.bucket,
|
|
1170
|
+
scope: scope,
|
|
1171
|
+
target_version: job.target_version,
|
|
1172
|
+
).run
|
|
1173
|
+
rescue Exception => e
|
|
1174
|
+
# ReingestJob#run already marks Crashed for errors raised during the run.
|
|
1175
|
+
# This catches failures from the constructor itself (or anything before
|
|
1176
|
+
# run gets its rescue installed) so the model doesn't sit in Queued forever.
|
|
1177
|
+
OpenC3::Logger.error("Reingest job #{job_id} crashed before run: #{e.formatted}")
|
|
1178
|
+
begin
|
|
1179
|
+
job.state = 'Crashed'
|
|
1180
|
+
job.error = e.message
|
|
1181
|
+
job.finished_at = Time.now.utc.iso8601
|
|
1182
|
+
job.update
|
|
1183
|
+
rescue => e2
|
|
1184
|
+
OpenC3::Logger.error("Reingest job #{job_id} failed to mark Crashed: #{e2.message}")
|
|
1185
|
+
end
|
|
1186
|
+
exit(1)
|
|
1187
|
+
end
|
|
1188
|
+
|
|
1146
1189
|
when 'generate'
|
|
1147
1190
|
# To test against a local copy call this file from the root cosmos directory like this:
|
|
1148
1191
|
# ruby -Iopenc3/lib openc3/bin/openc3cli generate ...
|
|
@@ -1390,8 +1433,9 @@ if not ARGV[0].nil? # argument(s) given
|
|
|
1390
1433
|
end
|
|
1391
1434
|
end
|
|
1392
1435
|
end
|
|
1393
|
-
# Unless explicitly disabled, ensure the tools bucket is public
|
|
1394
|
-
|
|
1436
|
+
# Unless explicitly disabled, ensure the tools bucket is public.
|
|
1437
|
+
# OPENC3_TOOLS_BUCKET_PRIVATE keeps the tools bucket private; the cmd-tlm-api proxies reads via ToolsController.
|
|
1438
|
+
unless ENV.fetch("OPENC3_NO_BUCKET_POLICY", false) || ENV.fetch("OPENC3_TOOLS_BUCKET_PRIVATE", false)
|
|
1395
1439
|
client.ensure_public(ENV['OPENC3_TOOLS_BUCKET'])
|
|
1396
1440
|
end
|
|
1397
1441
|
# Always ensure the scriptrunner policy is in place since it is required for script execution
|
|
@@ -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
|
|
@@ -168,11 +168,22 @@ MICROSERVICE:
|
|
|
168
168
|
since: 6.0.0
|
|
169
169
|
parameters:
|
|
170
170
|
- name: Shard
|
|
171
|
-
required:
|
|
171
|
+
required: true
|
|
172
172
|
description: Shard number starting from 0
|
|
173
173
|
values: \d+
|
|
174
174
|
example: |
|
|
175
175
|
SHARD 0
|
|
176
|
+
DB_SHARD:
|
|
177
|
+
summary: Shard for target database database if sharding Redis/TSDB
|
|
178
|
+
description: DB Shard. Only used if running multiple database shards typically in Kubernetes
|
|
179
|
+
since: 7.1.0
|
|
180
|
+
parameters:
|
|
181
|
+
- name: DB Shard
|
|
182
|
+
required: true
|
|
183
|
+
description: DB Shard number starting from 0
|
|
184
|
+
values: \d+
|
|
185
|
+
example: |
|
|
186
|
+
DB_SHARD 0
|
|
176
187
|
STOPPED:
|
|
177
188
|
summary: Initially creates the microservice in a stopped state (not enabled)
|
|
178
189
|
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/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/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
|
|
@@ -0,0 +1,128 @@
|
|
|
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 'openc3/system/system'
|
|
15
|
+
require 'openc3/microservices/interface_microservice'
|
|
16
|
+
require 'openc3/topics/telemetry_decom_topic'
|
|
17
|
+
require 'openc3/models/target_model'
|
|
18
|
+
|
|
19
|
+
module OpenC3
|
|
20
|
+
# Shared decom pipeline used by DecomMicroservice (live telemetry) and
|
|
21
|
+
# ReingestJob (historical raw log replay). The reingest path passes
|
|
22
|
+
# check_limits: false so historical data does not re-fire limits events.
|
|
23
|
+
module DecomCommon
|
|
24
|
+
extend self
|
|
25
|
+
|
|
26
|
+
# Decommutate a Packet and publish it on the TelemetryDecomTopic. This is the
|
|
27
|
+
# step that lands data in the CVT and in the Python TsdbMicroservice → QuestDB.
|
|
28
|
+
#
|
|
29
|
+
# @param packet [Packet] A fully buffered Packet. Caller sets received_time,
|
|
30
|
+
# received_count, stored, extra, buffer.
|
|
31
|
+
# @param scope [String] Scope name.
|
|
32
|
+
# @param target_names [Array<String>] Used when a subpacket must be re-identified.
|
|
33
|
+
# @param logger [Logger] Destination for warnings.
|
|
34
|
+
# @param name [String] Identifier used in subpacket warning messages
|
|
35
|
+
# (microservice name, or "REINGEST:<job_id>").
|
|
36
|
+
# @param check_limits [Boolean] When false, skips the Packet#check_limits call.
|
|
37
|
+
# Reingest passes false so historical data does not re-fire limits events.
|
|
38
|
+
# @param metric [Metric, nil] Optional; when set, records decom_duration_seconds.
|
|
39
|
+
# @param error_callback [Proc, nil] Called as error_callback.call(exception) when
|
|
40
|
+
# Packet#process or Packet#check_limits raises. The microservice uses this to
|
|
41
|
+
# bump its decom_error_total metric.
|
|
42
|
+
# @return [Integer] Number of (sub)packets published.
|
|
43
|
+
def decom_and_publish(packet, scope:, target_names:, logger:, name:,
|
|
44
|
+
check_limits: true, metric: nil, error_callback: nil)
|
|
45
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC) if metric
|
|
46
|
+
published = 0
|
|
47
|
+
|
|
48
|
+
packet_and_subpackets = packet.subpacketize
|
|
49
|
+
packet_and_subpackets.each do |packet_or_subpacket|
|
|
50
|
+
if packet_or_subpacket.subpacket
|
|
51
|
+
packet_or_subpacket = handle_subpacket(packet, packet_or_subpacket,
|
|
52
|
+
target_names: target_names,
|
|
53
|
+
scope: scope,
|
|
54
|
+
logger: logger,
|
|
55
|
+
name: name)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
begin
|
|
59
|
+
packet_or_subpacket.process
|
|
60
|
+
rescue Exception => e
|
|
61
|
+
error_callback&.call(e)
|
|
62
|
+
logger.error e.message
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
packet_or_subpacket.check_limits(System.limits_set) if check_limits
|
|
66
|
+
|
|
67
|
+
TelemetryDecomTopic.write_packet(packet_or_subpacket, scope: scope)
|
|
68
|
+
published += 1
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if metric
|
|
72
|
+
diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
73
|
+
metric.set(name: 'decom_duration_seconds', value: diff, type: 'gauge', unit: 'seconds')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
published
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Identify a subpacket and (except for stored telemetry) update the CVT.
|
|
80
|
+
# Extracted from DecomMicroservice so reingest can handle subpackets too.
|
|
81
|
+
def handle_subpacket(packet, subpacket, target_names:, scope:, logger:, name:)
|
|
82
|
+
subpacket.received_time = packet.received_time
|
|
83
|
+
subpacket.stored = packet.stored
|
|
84
|
+
subpacket.extra = packet.extra
|
|
85
|
+
|
|
86
|
+
if subpacket.stored
|
|
87
|
+
identified_subpacket = System.telemetry.identify_and_define_packet(subpacket, target_names, subpackets: true)
|
|
88
|
+
else
|
|
89
|
+
if subpacket.identified?
|
|
90
|
+
begin
|
|
91
|
+
identified_subpacket = System.telemetry.update!(subpacket.target_name,
|
|
92
|
+
subpacket.packet_name,
|
|
93
|
+
subpacket.buffer)
|
|
94
|
+
rescue RuntimeError
|
|
95
|
+
logger.warn "#{name}: Received unknown identified subpacket: #{subpacket.target_name} #{subpacket.packet_name}"
|
|
96
|
+
subpacket.target_name = nil
|
|
97
|
+
subpacket.packet_name = nil
|
|
98
|
+
identified_subpacket = System.telemetry.identify!(subpacket.buffer,
|
|
99
|
+
target_names, subpackets: true)
|
|
100
|
+
end
|
|
101
|
+
else
|
|
102
|
+
identified_subpacket = System.telemetry.identify!(subpacket.buffer,
|
|
103
|
+
target_names, subpackets: true)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if identified_subpacket
|
|
108
|
+
identified_subpacket.received_time = subpacket.received_time
|
|
109
|
+
identified_subpacket.stored = subpacket.stored
|
|
110
|
+
identified_subpacket.extra = subpacket.extra
|
|
111
|
+
subpacket = identified_subpacket
|
|
112
|
+
else
|
|
113
|
+
unknown_subpacket = System.telemetry.update!('UNKNOWN', 'UNKNOWN', subpacket.buffer)
|
|
114
|
+
unknown_subpacket.received_time = subpacket.received_time
|
|
115
|
+
unknown_subpacket.stored = subpacket.stored
|
|
116
|
+
unknown_subpacket.extra = subpacket.extra
|
|
117
|
+
subpacket = unknown_subpacket
|
|
118
|
+
num_bytes_to_print = [InterfaceMicroservice::UNKNOWN_BYTES_TO_PRINT, subpacket.length].min
|
|
119
|
+
data = subpacket.buffer(false)[0..(num_bytes_to_print - 1)]
|
|
120
|
+
prefix = data.each_byte.map { |byte| sprintf("%02X", byte) }.join()
|
|
121
|
+
logger.warn "#{name} #{subpacket.target_name} packet length: #{subpacket.length} starting with: #{prefix}"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
TargetModel.sync_tlm_packet_counts(subpacket, target_names, scope: scope)
|
|
125
|
+
subpacket
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|