openc3 7.0.0 → 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 +105 -13
- data/bin/pipinstall +38 -6
- data/data/config/command_modifiers.yaml +1 -0
- data/data/config/item_modifiers.yaml +2 -1
- data/data/config/microservice.yaml +12 -1
- data/data/config/parameter_modifiers.yaml +49 -7
- data/data/config/table_parameter_modifiers.yaml +3 -1
- data/data/config/target.yaml +11 -0
- data/data/config/target_config.yaml +6 -2
- data/lib/openc3/accessors/template_accessor.rb +9 -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/interfaces/interface.rb +1 -6
- 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 +27 -96
- data/lib/openc3/microservices/interface_decom_common.rb +28 -10
- data/lib/openc3/microservices/interface_microservice.rb +16 -9
- 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 +9 -1
- data/lib/openc3/models/python_package_model.rb +1 -1
- data/lib/openc3/models/reaction_model.rb +27 -9
- 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 +24 -7
- data/lib/openc3/packets/packet_config.rb +4 -1
- data/lib/openc3/script/api_shared.rb +39 -2
- data/lib/openc3/script/calendar.rb +32 -10
- data/lib/openc3/script/extract.rb +46 -13
- 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 +31 -11
- data/lib/openc3/topics/interface_topic.rb +88 -27
- 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/ctrf.rb +231 -0
- data/lib/openc3/utilities/metric.rb +15 -1
- data/lib/openc3/utilities/questdb_client.rb +177 -40
- 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 +5 -5
- 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 -4
- 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: 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
|
|
|
@@ -75,6 +76,7 @@ def print_usage
|
|
|
75
76
|
puts " cli pkguninstall PKGFILENAME SCOPE # Uninstall loaded package (Ruby gem or python package)"
|
|
76
77
|
puts " cli xtce_converter # Convert to and from the XTCE format. Run with --help for more info."
|
|
77
78
|
puts " cli cstol_converter # Converts CSTOL files (.prc) to COSMOS. Run with --help for more info."
|
|
79
|
+
puts " cli setpassword # Set the initial password from OPENC3_API_PASSWORD env var"
|
|
78
80
|
puts ""
|
|
79
81
|
end
|
|
80
82
|
|
|
@@ -588,35 +590,44 @@ def run_bridge(filename, params)
|
|
|
588
590
|
end
|
|
589
591
|
end
|
|
590
592
|
|
|
591
|
-
def cli_script_monitor(script_id)
|
|
593
|
+
def cli_script_monitor(script_id, format: 'text')
|
|
592
594
|
ret_code = ERROR_CODE
|
|
593
595
|
require 'openc3/script'
|
|
596
|
+
require 'openc3/utilities/ctrf'
|
|
594
597
|
begin
|
|
595
598
|
OpenC3::RunningScriptWebSocketApi.new(id: script_id) do |api|
|
|
596
599
|
while (resp = api.read) do
|
|
597
600
|
# see ScriptRunner.vue for types and states
|
|
598
601
|
case resp['type']
|
|
599
602
|
when 'file'
|
|
600
|
-
puts "Filename #{resp['filename']} scope #{resp['scope']}"
|
|
603
|
+
puts "Filename #{resp['filename']} scope #{resp['scope']}" unless format == 'ctrf'
|
|
601
604
|
when 'line'
|
|
602
605
|
fn = resp['filename'].nil? ? '<no file>' : resp['filename']
|
|
603
|
-
puts "At [#{fn}:#{resp['line_no']}] state [#{resp['state']}]"
|
|
606
|
+
puts "At [#{fn}:#{resp['line_no']}] state [#{resp['state']}]" unless format == 'ctrf'
|
|
604
607
|
if resp['state'] == 'error' or resp['state'] == 'crashed'
|
|
605
608
|
$script_interrupt_text = ''
|
|
606
|
-
puts 'script failed'
|
|
609
|
+
puts 'script failed' unless format == 'ctrf'
|
|
607
610
|
break
|
|
608
611
|
end
|
|
609
612
|
when 'output'
|
|
610
|
-
puts resp['line']
|
|
613
|
+
puts resp['line'] unless format == 'ctrf'
|
|
611
614
|
when 'complete'
|
|
612
615
|
$script_interrupt_text = ''
|
|
613
|
-
|
|
616
|
+
if resp['report']
|
|
617
|
+
if format == 'ctrf'
|
|
618
|
+
ctrf_data = OpenC3::Ctrf.convert_report(resp['report'])
|
|
619
|
+
puts JSON.pretty_generate(ctrf_data)
|
|
620
|
+
else
|
|
621
|
+
puts resp['report']
|
|
622
|
+
end
|
|
623
|
+
end
|
|
624
|
+
puts 'script complete' unless format == 'ctrf'
|
|
614
625
|
ret_code = 0
|
|
615
626
|
break
|
|
616
627
|
# These conditions are all handled by the else
|
|
617
628
|
# when 'running', 'breakpoint', 'waiting', 'time'
|
|
618
629
|
else
|
|
619
|
-
puts resp.pretty_inspect
|
|
630
|
+
puts resp.pretty_inspect unless format == 'ctrf'
|
|
620
631
|
end
|
|
621
632
|
end
|
|
622
633
|
end
|
|
@@ -692,10 +703,10 @@ def cli_script_run(args, options)
|
|
|
692
703
|
puts id
|
|
693
704
|
$script_interrupt_text = " Script #{args[1]} still running remotely.\n" # for Ctrl-C
|
|
694
705
|
if (options[:wait] < 1) then
|
|
695
|
-
ret_code = cli_script_monitor(id)
|
|
706
|
+
ret_code = cli_script_monitor(id, format: options[:format])
|
|
696
707
|
else
|
|
697
708
|
Timeout::timeout(options[:wait], nil, "--wait #{options[:wait]} exceeded") do
|
|
698
|
-
ret_code = cli_script_monitor(id)
|
|
709
|
+
ret_code = cli_script_monitor(id, format: options[:format])
|
|
699
710
|
rescue Timeout::ExitException, Timeout::Error => e
|
|
700
711
|
# Timeout exceptions are also raised by the Websocket API, so we check
|
|
701
712
|
if e.message =~ /^--wait /
|
|
@@ -795,7 +806,7 @@ rescue => e
|
|
|
795
806
|
end
|
|
796
807
|
|
|
797
808
|
def cli_script(args=[])
|
|
798
|
-
options = {scope: 'DEFAULT', disconnect: false, wait: 0, verbose: false}
|
|
809
|
+
options = {scope: 'DEFAULT', disconnect: false, wait: 0, verbose: false, format: 'text'}
|
|
799
810
|
option_parser = OptionParser.new do |opts|
|
|
800
811
|
opts.banner = "Usage: script --scope SCOPE [init | list | spawn | run]\n" +
|
|
801
812
|
" init Initialize running scripts (Enterprise Only)\n" +
|
|
@@ -812,6 +823,9 @@ def cli_script(args=[])
|
|
|
812
823
|
opts.on("--scope SCOPE", "Run with specified scope (default = DEFAULT)") do |arg|
|
|
813
824
|
options[:scope] = arg
|
|
814
825
|
end
|
|
826
|
+
opts.on("--format FORMAT", "Output format: text or ctrf (default = text)") do |arg|
|
|
827
|
+
options[:format] = arg
|
|
828
|
+
end
|
|
815
829
|
opts.on("--suite SUITE", "Run with specified suite") do |arg|
|
|
816
830
|
options[:suite] = arg
|
|
817
831
|
end
|
|
@@ -892,6 +906,25 @@ def cli_script(args=[])
|
|
|
892
906
|
exit(ret_code)
|
|
893
907
|
end
|
|
894
908
|
|
|
909
|
+
def set_password
|
|
910
|
+
password = ENV['OPENC3_API_PASSWORD']
|
|
911
|
+
argon2_profile = ENV["OPENC3_ARGON2_PROFILE"]&.to_sym || :rfc_9106_low_memory
|
|
912
|
+
if password.nil? or password.empty?
|
|
913
|
+
abort "OPENC3_API_PASSWORD environment variable is required"
|
|
914
|
+
end
|
|
915
|
+
if password.length < 8
|
|
916
|
+
abort "Password must be at least 8 characters"
|
|
917
|
+
end
|
|
918
|
+
redis = Redis.new(url: $redis_url, username: ENV['OPENC3_REDIS_USERNAME'], password: ENV['OPENC3_REDIS_PASSWORD'])
|
|
919
|
+
if redis.exists('OPENC3__TOKEN') == 1
|
|
920
|
+
abort "Password has already been set. Use the web interface to change the password."
|
|
921
|
+
end
|
|
922
|
+
pw_hash = Argon2::Password.create(password, profile: argon2_profile)
|
|
923
|
+
redis.set('OPENC3__TOKEN', pw_hash)
|
|
924
|
+
puts "Password set successfully."
|
|
925
|
+
exit 0
|
|
926
|
+
end
|
|
927
|
+
|
|
895
928
|
def migrate_password_hash
|
|
896
929
|
password = ENV['OPENC3_API_PASSWORD']
|
|
897
930
|
argon2_profile = ENV["OPENC3_ARGON2_PROFILE"]&.to_sym || :rfc_9106_low_memory
|
|
@@ -1111,6 +1144,48 @@ if not ARGV[0].nil? # argument(s) given
|
|
|
1111
1144
|
end
|
|
1112
1145
|
cli_pkg_uninstall(ARGV[1], scope: ARGV[2])
|
|
1113
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
|
+
|
|
1114
1189
|
when 'generate'
|
|
1115
1190
|
# To test against a local copy call this file from the root cosmos directory like this:
|
|
1116
1191
|
# ruby -Iopenc3/lib openc3/bin/openc3cli generate ...
|
|
@@ -1358,8 +1433,9 @@ if not ARGV[0].nil? # argument(s) given
|
|
|
1358
1433
|
end
|
|
1359
1434
|
end
|
|
1360
1435
|
end
|
|
1361
|
-
# Unless explicitly disabled, ensure the tools bucket is public
|
|
1362
|
-
|
|
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)
|
|
1363
1439
|
client.ensure_public(ENV['OPENC3_TOOLS_BUCKET'])
|
|
1364
1440
|
end
|
|
1365
1441
|
# Always ensure the scriptrunner policy is in place since it is required for script execution
|
|
@@ -1377,6 +1453,22 @@ if not ARGV[0].nil? # argument(s) given
|
|
|
1377
1453
|
end
|
|
1378
1454
|
run_migrations(ARGV[1])
|
|
1379
1455
|
|
|
1456
|
+
when 'setpassword'
|
|
1457
|
+
if ARGV[1] == '--help' || ARGV[1] == '-h'
|
|
1458
|
+
puts "Usage: cli setpassword"
|
|
1459
|
+
puts ""
|
|
1460
|
+
puts "Set the initial COSMOS password from the OPENC3_API_PASSWORD environment variable."
|
|
1461
|
+
puts "This allows you to skip the password creation screen in the web interface."
|
|
1462
|
+
puts ""
|
|
1463
|
+
puts "The password must be at least 8 characters. This command will fail if a"
|
|
1464
|
+
puts "password has already been set."
|
|
1465
|
+
puts ""
|
|
1466
|
+
puts "Options:"
|
|
1467
|
+
puts " -h, --help Show this help message"
|
|
1468
|
+
exit 0
|
|
1469
|
+
end
|
|
1470
|
+
set_password()
|
|
1471
|
+
|
|
1380
1472
|
when 'migratepassword'
|
|
1381
1473
|
migrate_password_hash()
|
|
1382
1474
|
|
data/bin/pipinstall
CHANGED
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
uv venv "$PYTHONUSERBASE"
|
|
2
|
+
uv venv "$PYTHONUSERBASE" --allow-existing
|
|
3
3
|
echo "uv pip install $@"
|
|
4
4
|
uv pip install --python "$PYTHONUSERBASE" "$@"
|
|
5
5
|
if [ $? -eq 0 ]; then
|
|
6
6
|
echo "Command succeeded"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
exit 0
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
# Collect the last arg and all preceding args
|
|
11
|
+
LAST_ARG=""
|
|
12
|
+
OPTS=""
|
|
13
|
+
PREV=""
|
|
14
|
+
for ARG in "$@"; do
|
|
15
|
+
if [ -n "$PREV" ]; then
|
|
16
|
+
OPTS="${OPTS} ${PREV}"
|
|
17
|
+
fi
|
|
18
|
+
PREV="$ARG"
|
|
19
|
+
done
|
|
20
|
+
LAST_ARG="$PREV"
|
|
21
|
+
|
|
22
|
+
# If last arg is a directory with pyproject.toml, the build may have failed
|
|
23
|
+
# (e.g. Poetry package-mode = false). Try compiling and installing declared
|
|
24
|
+
# dependencies only, without attempting to build the package itself.
|
|
25
|
+
if [ -d "$LAST_ARG" ] && [ -f "$LAST_ARG/pyproject.toml" ]; then
|
|
26
|
+
echo "Warning: Failed to build Python package, attempting to install declared dependencies from pyproject.toml"
|
|
27
|
+
TMPFILE=$(mktemp)
|
|
28
|
+
uv pip compile ${OPTS} "${LAST_ARG}/pyproject.toml" > "$TMPFILE"
|
|
29
|
+
if [ $? -eq 0 ] && [ -s "$TMPFILE" ]; then
|
|
30
|
+
uv pip install --python "$PYTHONUSERBASE" ${OPTS} -r "$TMPFILE"
|
|
31
|
+
if [ $? -eq 0 ]; then
|
|
32
|
+
echo "Dependencies installed successfully"
|
|
33
|
+
rm -f "$TMPFILE"
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
12
36
|
fi
|
|
37
|
+
rm -f "$TMPFILE"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo "Warning: Install failed - retrying with --no-index"
|
|
41
|
+
uv pip install --python "$PYTHONUSERBASE" --no-index "$@"
|
|
42
|
+
if [ $? -ne 0 ]; then
|
|
43
|
+
echo "ERROR: uv pip install failed"
|
|
44
|
+
exit 1
|
|
13
45
|
fi
|
|
@@ -199,6 +199,7 @@ HIDDEN:
|
|
|
199
199
|
summary: Hides this command from all OpenC3 tools such as Command Sender and Handbook Creator
|
|
200
200
|
description: Hidden commands do not appear in the Script Runner popup helper when writing scripts.
|
|
201
201
|
The command still exists in the system and can be sent by scripts.
|
|
202
|
+
since: 6.10.1
|
|
202
203
|
DISABLED:
|
|
203
204
|
summary: Disables this command from being sent
|
|
204
205
|
description: Hides the command and also disables it from being sent by scripts.
|
|
@@ -192,4 +192,5 @@ HIDDEN:
|
|
|
192
192
|
summary: Hides this item from all the OpenC3 tools
|
|
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
|
-
when writing scripts. The item will also not be included in decom data.
|
|
195
|
+
when writing scripts. The item will also not be included in decom data.
|
|
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
|
|
@@ -3,7 +3,9 @@ HIDDEN:
|
|
|
3
3
|
summary: Indicates that the parameter should not be shown to the user in the Table Manager GUI
|
|
4
4
|
description: Hidden parameters still exist and will be saved to the resulting
|
|
5
5
|
binary. This is useful for padding and other essential but non-user editable fields.
|
|
6
|
+
since: 6.10.1
|
|
6
7
|
UNEDITABLE:
|
|
7
8
|
summary: Indicates that the parameter should be shown to the user but not editable.
|
|
8
|
-
description:
|
|
9
|
+
description:
|
|
10
|
+
Uneditable parameters are useful for control fields which the user
|
|
9
11
|
may be interested in but should not be able to edit.
|
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
|
|
@@ -53,6 +53,9 @@ module OpenC3
|
|
|
53
53
|
return nil if item.data_type == :DERIVED
|
|
54
54
|
configure()
|
|
55
55
|
|
|
56
|
+
# No template items to read (e.g. command with fixed template string)
|
|
57
|
+
return nil if @item_keys.empty?
|
|
58
|
+
|
|
56
59
|
# Scan the response for all the variables in brackets <VARIABLE>
|
|
57
60
|
values = buffer.scan(@read_regexp)[0]
|
|
58
61
|
if !values || (values.length != @item_keys.length)
|
|
@@ -73,6 +76,12 @@ module OpenC3
|
|
|
73
76
|
result = {}
|
|
74
77
|
configure()
|
|
75
78
|
|
|
79
|
+
# No template items to read (e.g. command with fixed template string)
|
|
80
|
+
if @item_keys.empty?
|
|
81
|
+
items.each { |item| result[item.name] = nil }
|
|
82
|
+
return result
|
|
83
|
+
end
|
|
84
|
+
|
|
76
85
|
# Scan the response for all the variables in brackets <VARIABLE>
|
|
77
86
|
values = buffer.scan(@read_regexp)[0]
|
|
78
87
|
if !values || (values.length != @item_keys.length)
|
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?
|
|
@@ -295,17 +295,12 @@ module OpenC3
|
|
|
295
295
|
else
|
|
296
296
|
data, extra = protocol.read_data(data)
|
|
297
297
|
end
|
|
298
|
-
protocol.read_protocol_output_base(data, extra) unless blank_test
|
|
298
|
+
protocol.read_protocol_output_base(data, extra) unless blank_test or data == :STOP or data == :DISCONNECT
|
|
299
299
|
if data == :DISCONNECT
|
|
300
300
|
Logger.info("#{@name}: Protocol #{protocol.class} read_data requested disconnect")
|
|
301
301
|
return nil
|
|
302
302
|
end
|
|
303
303
|
break if data == :STOP
|
|
304
|
-
if blank_test
|
|
305
|
-
# This means the blank test returned something so we can log
|
|
306
|
-
protocol.read_protocol_input_base('', nil)
|
|
307
|
-
protocol.read_protocol_output_base(data, extra)
|
|
308
|
-
end
|
|
309
304
|
end
|
|
310
305
|
next if data == :STOP
|
|
311
306
|
|
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
|