openc3 5.17.1 → 5.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -4
  3. data/bin/cstol_converter +14 -14
  4. data/bin/openc3cli +190 -8
  5. data/data/config/_interfaces.yaml +5 -5
  6. data/data/config/command_modifiers.yaml +59 -0
  7. data/data/config/interface_modifiers.yaml +19 -9
  8. data/data/config/item_modifiers.yaml +34 -26
  9. data/data/config/microservice.yaml +4 -1
  10. data/data/config/param_item_modifiers.yaml +17 -1
  11. data/data/config/parameter_modifiers.yaml +30 -13
  12. data/data/config/plugins.yaml +9 -5
  13. data/data/config/screen.yaml +9 -9
  14. data/data/config/table_manager.yaml +2 -2
  15. data/data/config/telemetry_modifiers.yaml +9 -4
  16. data/data/config/tool.yaml +4 -1
  17. data/data/config/widgets.yaml +44 -17
  18. data/ext/openc3/ext/config_parser/config_parser.c +1 -1
  19. data/ext/openc3/ext/packet/packet.c +7 -1
  20. data/ext/openc3/ext/platform/platform.c +3 -3
  21. data/ext/openc3/ext/structure/structure.c +56 -76
  22. data/lib/openc3/accessors/accessor.rb +1 -0
  23. data/lib/openc3/accessors/binary_accessor.rb +174 -15
  24. data/lib/openc3/accessors/form_accessor.rb +2 -2
  25. data/lib/openc3/accessors/http_accessor.rb +1 -1
  26. data/lib/openc3/accessors/json_accessor.rb +6 -4
  27. data/lib/openc3/accessors/template_accessor.rb +6 -9
  28. data/lib/openc3/accessors/xml_accessor.rb +1 -1
  29. data/lib/openc3/api/cmd_api.rb +72 -44
  30. data/lib/openc3/api/config_api.rb +10 -10
  31. data/lib/openc3/api/interface_api.rb +28 -21
  32. data/lib/openc3/api/limits_api.rb +30 -30
  33. data/lib/openc3/api/metrics_api.rb +3 -3
  34. data/lib/openc3/api/offline_access_api.rb +5 -5
  35. data/lib/openc3/api/router_api.rb +25 -19
  36. data/lib/openc3/api/settings_api.rb +10 -10
  37. data/lib/openc3/api/stash_api.rb +10 -10
  38. data/lib/openc3/api/target_api.rb +10 -10
  39. data/lib/openc3/api/tlm_api.rb +44 -44
  40. data/lib/openc3/config/config_parser.rb +1 -1
  41. data/lib/openc3/conversions/bit_reverse_conversion.rb +60 -0
  42. data/lib/openc3/conversions/ip_read_conversion.rb +59 -0
  43. data/lib/openc3/conversions/ip_write_conversion.rb +61 -0
  44. data/lib/openc3/conversions/object_read_conversion.rb +88 -0
  45. data/lib/openc3/conversions/object_write_conversion.rb +38 -0
  46. data/lib/openc3/conversions/segmented_polynomial_conversion.rb +7 -7
  47. data/lib/openc3/conversions.rb +6 -1
  48. data/lib/openc3/core_ext/array.rb +5 -5
  49. data/lib/openc3/core_ext/exception.rb +9 -2
  50. data/lib/openc3/core_ext/string.rb +2 -2
  51. data/lib/openc3/interfaces/http_server_interface.rb +1 -0
  52. data/lib/openc3/interfaces/interface.rb +1 -1
  53. data/lib/openc3/interfaces/linc_interface.rb +3 -3
  54. data/lib/openc3/io/json_api.rb +11 -6
  55. data/lib/openc3/io/json_drb.rb +19 -21
  56. data/lib/openc3/io/json_rpc.rb +15 -14
  57. data/lib/openc3/logs/buffered_packet_log_writer.rb +3 -3
  58. data/lib/openc3/logs/log_writer.rb +7 -8
  59. data/lib/openc3/logs/packet_log_writer.rb +7 -7
  60. data/lib/openc3/logs/text_log_writer.rb +4 -4
  61. data/lib/openc3/microservices/decom_microservice.rb +19 -4
  62. data/lib/openc3/microservices/interface_microservice.rb +41 -3
  63. data/lib/openc3/microservices/microservice.rb +11 -11
  64. data/lib/openc3/microservices/reaction_microservice.rb +2 -2
  65. data/lib/openc3/microservices/scope_cleanup_microservice.rb +1 -1
  66. data/lib/openc3/microservices/timeline_microservice.rb +70 -45
  67. data/lib/openc3/microservices/trigger_group_microservice.rb +3 -3
  68. data/lib/openc3/migrations/20240915000000_activity_uuid.rb +28 -0
  69. data/lib/openc3/models/activity_model.rb +124 -92
  70. data/lib/openc3/models/auth_model.rb +31 -2
  71. data/lib/openc3/models/cvt_model.rb +11 -5
  72. data/lib/openc3/models/gem_model.rb +8 -8
  73. data/lib/openc3/models/plugin_model.rb +3 -3
  74. data/lib/openc3/models/reducer_model.rb +2 -2
  75. data/lib/openc3/models/scope_model.rb +45 -14
  76. data/lib/openc3/models/sorted_model.rb +5 -5
  77. data/lib/openc3/models/target_model.rb +7 -4
  78. data/lib/openc3/models/tool_config_model.rb +1 -1
  79. data/lib/openc3/models/tool_model.rb +4 -4
  80. data/lib/openc3/models/widget_model.rb +11 -5
  81. data/lib/openc3/operators/microservice_operator.rb +2 -2
  82. data/lib/openc3/operators/operator.rb +14 -12
  83. data/lib/openc3/packets/command_validator.rb +48 -0
  84. data/lib/openc3/packets/commands.rb +6 -14
  85. data/lib/openc3/packets/packet.rb +49 -16
  86. data/lib/openc3/packets/packet_config.rb +47 -25
  87. data/lib/openc3/packets/packet_item.rb +5 -0
  88. data/lib/openc3/packets/parsers/packet_parser.rb +3 -3
  89. data/lib/openc3/packets/structure.rb +87 -15
  90. data/lib/openc3/packets/structure_item.rb +76 -53
  91. data/lib/openc3/packets/telemetry.rb +6 -27
  92. data/lib/openc3/script/api_shared.rb +7 -5
  93. data/lib/openc3/script/calendar.rb +2 -2
  94. data/lib/openc3/script/commands.rb +6 -4
  95. data/lib/openc3/script/extract.rb +5 -3
  96. data/lib/openc3/script/metadata.rb +2 -2
  97. data/lib/openc3/script/suite.rb +17 -17
  98. data/lib/openc3/script/web_socket_api.rb +11 -0
  99. data/lib/openc3/streams/serial_stream.rb +2 -3
  100. data/lib/openc3/streams/stream.rb +2 -2
  101. data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +10 -10
  102. data/lib/openc3/tools/table_manager/table_manager_core.rb +11 -11
  103. data/lib/openc3/tools/table_manager/table_parser.rb +2 -3
  104. data/lib/openc3/topics/command_decom_topic.rb +2 -1
  105. data/lib/openc3/topics/command_topic.rb +3 -3
  106. data/lib/openc3/topics/decom_interface_topic.rb +4 -3
  107. data/lib/openc3/topics/system_events_topic.rb +40 -0
  108. data/lib/openc3/topics/telemetry_decom_topic.rb +1 -1
  109. data/lib/openc3/utilities/authentication.rb +2 -1
  110. data/lib/openc3/utilities/authorization.rb +4 -3
  111. data/lib/openc3/utilities/cli_generator.rb +15 -8
  112. data/lib/openc3/utilities/cosmos_rails_formatter.rb +60 -0
  113. data/lib/openc3/utilities/crc.rb +6 -6
  114. data/lib/openc3/utilities/local_mode.rb +2 -1
  115. data/lib/openc3/utilities/logger.rb +44 -34
  116. data/lib/openc3/utilities/metric.rb +1 -2
  117. data/lib/openc3/utilities/quaternion.rb +18 -18
  118. data/lib/openc3/utilities/target_file.rb +4 -4
  119. data/lib/openc3/version.rb +6 -6
  120. data/lib/openc3/win32/win32_main.rb +2 -2
  121. data/templates/tool_angular/package.json +22 -22
  122. data/templates/tool_react/package.json +13 -13
  123. data/templates/tool_svelte/package.json +14 -14
  124. data/templates/tool_svelte/src/services/openc3-api.js +17 -17
  125. data/templates/tool_vue/package.json +13 -13
  126. data/templates/widget/package.json +11 -12
  127. data/templates/widget/src/Widget.vue +0 -1
  128. metadata +25 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c70541c17780bda9cd1adb897963eb1daedfc459bc2637a511745180430e162
4
- data.tar.gz: a6eb2e273a1bb9dd5d849b68e8910b7c8eaaca65cee41eed8bdb155891eec1db
3
+ metadata.gz: 65a5c8a4ee0645f9d129c7c6140b9e8863e61e035f5e0269a977e8cd7d722c15
4
+ data.tar.gz: a023d452a32050ab5d81a3edcdc8fccaf10eed1a81b481ef11d89c633d3f6cba
5
5
  SHA512:
6
- metadata.gz: 60e677a6a9b13c7d94d4f32b70b824509e2c87cf691bebb9285b96c544318a8016f2f98b119c657de1cee77b0ec197301bd040962ec9055afe0cd0ae3228ad74
7
- data.tar.gz: a8a9f67755a4768806f92134a42c50a44cd686a95ad55877f66fcc735ceb75741845ffe1e71aaa66f5ac5181cb929f999f808c86abc6a1b9408f44f122933892
6
+ metadata.gz: 3ecd41dc83eb1d9af0e847fcb61d8784bfce374c33681008030423f2d1b04c09b62200ec5ca7f878273e6aed704982aeb55fddad1b1ae5490367e5fe48ea844f
7
+ data.tar.gz: f121f92ac5c6101eb9e3d9ebe52b525235090dc964a7bf409ebf7a47b1bc1c52c7f5a97a7194f5238ade4cee31078cb5822b486954d87983c8fdf9294f557257
data/Gemfile CHANGED
@@ -8,10 +8,13 @@ gemspec :name => 'openc3'
8
8
 
9
9
  # Include the rails gems for the convenience of custom microservice plugins
10
10
  gem 'bootsnap', '>= 1.9.3', require: false
11
- gem 'mock_redis', '0.44'
12
11
  gem 'rack-cors', '~> 2.0'
13
12
  gem 'rails', '~> 7.1.0'
14
- gem 'rspec-rails', '~> 6.0'
15
- gem 'simplecov', '~> 0.20'
16
- gem 'simplecov-cobertura', '~> 2.1'
17
13
  gem 'tzinfo-data'
14
+
15
+ group :test, :development do
16
+ gem 'mock_redis', '0.45'
17
+ gem 'rspec-rails', '~> 7.0'
18
+ gem 'simplecov', '~> 0.20'
19
+ gem 'simplecov-cobertura', '~> 2.1'
20
+ end
data/bin/cstol_converter CHANGED
@@ -18,7 +18,7 @@
18
18
  # All changes Copyright 2022, OpenC3, Inc.
19
19
  # All Rights Reserved
20
20
  #
21
- # This file may also be used under the terms of a commercial license
21
+ # This file may also be used under the terms of a commercial license
22
22
  # if purchased from OpenC3, Inc.
23
23
 
24
24
  # This file converts OASIS CSTOL files to OpenC3 scripts
@@ -163,7 +163,7 @@ def parse_expression(vars, quoted = false, in_eval = false)
163
163
  when /(\+|-|\*|\/)/ # Arithmetic operator
164
164
  str = str + " " + vars[i].tr('[]"', '') + " "
165
165
 
166
- # Verfies Number followed immediately by units
166
+ # Verifies Number followed immediately by units
167
167
  when /\dDN\z|\ddn\z|\dDEG\z|\ddeg\z|\dRAD\z|\drad\z|\dV\z|\dA\z|\dv\z|\da\z|\dc\z|\dC\z|\df\z|\dF\z|\dDPS\z|\dM\z|\dMPS\z|\dm\z|\dmps\z/ # Checks for decimal/degrees/volts and amps
168
168
  temp = vars[i].tr('[]"', '')
169
169
  temp = temp.gsub(/DN\z|dn\z|DEG\z|deg\z|RAD\z|rad\z|V\z|A\z|v\z|a\z|c\z|C\z|f\z|F\z|DPS\z|M\z|MPS\z|m\z|mps\z/, '')
@@ -190,7 +190,7 @@ def parse_expression(vars, quoted = false, in_eval = false)
190
190
  str = str + parse_time(vars[i].tr('[]"', ''))
191
191
 
192
192
  when /\dE/ # Floating point
193
- temp_number = vars[i].tr('E', '').to_f * 10**vars[i + 2].to_f
193
+ temp_number = vars[i].tr('E', '').to_f * (10**vars[i + 2].to_f)
194
194
  str = str + "#{temp_number}"
195
195
  finished = true
196
196
  when /\d/ # Decimal number
@@ -279,7 +279,7 @@ def parse_id(id, quoted = false, in_eval = false, in_command = false, in_single_
279
279
  elsif id.match?(/\dE\+\d/) # Floating point
280
280
  id = id.gsub('+', ' + ')
281
281
  temp_num_arr = id.split(' ')
282
- temp_number = temp_num_arr[0].tr('E', '').to_f * 10**temp_num_arr[2].to_f
282
+ temp_number = temp_num_arr[0].tr('E', '').to_f * (10**temp_num_arr[2].to_f)
283
283
  str = str + "#{temp_number}"
284
284
  elsif id.match(/\A\d/) or id.match(/\A-\d/) # starts with Decimal number
285
285
  str = id
@@ -332,7 +332,7 @@ def parse_time(time)
332
332
  end
333
333
 
334
334
  # Compute the number of seconds
335
- secs = (3600 * tok[0].to_i + 60 * tok[1].to_i + tok[2].to_i).to_s
335
+ secs = ((3600 * tok[0].to_i) + (60 * tok[1].to_i) + tok[2].to_i).to_s
336
336
 
337
337
  # Other cases not handled
338
338
  else
@@ -341,7 +341,7 @@ def parse_time(time)
341
341
  end
342
342
 
343
343
  def parse_tlm_item(tlm)
344
- # Remove preceeding parentheses
344
+ # Remove preceding parentheses
345
345
  if tlm[0].index("(") == 0
346
346
  tlm[0] = tlm[0][1..-1]
347
347
  end
@@ -371,7 +371,7 @@ def parse_line(full_line, loc_out_file, wait_check_flag = false)
371
371
  return
372
372
  end
373
373
 
374
- # Detemine the location of any comments in this line
374
+ # Determine the location of any comments in this line
375
375
  commentIdx = full_line.index(";")
376
376
 
377
377
  # If we found a comment operator
@@ -653,7 +653,7 @@ def parse_line(full_line, loc_out_file, wait_check_flag = false)
653
653
  # TODO not sure if this is wise, for some files the loop is infinite and
654
654
  # there's no exist case
655
655
  if words[1] == nil
656
- str = str + "# TODO Possible inifinite loop case check script file" +
656
+ str = str + "# TODO Possible infinite loop case check script file" +
657
657
  "\n" + str + "while(true)"
658
658
  else
659
659
  str = str + words[1] + ".times do |i|"
@@ -694,7 +694,7 @@ def parse_line(full_line, loc_out_file, wait_check_flag = false)
694
694
  # Print the function name and the first argument
695
695
  str = str + "def " + words[1].downcase + "(" + parse_id(listWords[0])
696
696
 
697
- # Print the remaining arguments preceeded by a separator
697
+ # Print the remaining arguments preceded by a separator
698
698
  (listWords.length - 1).times do |i|
699
699
  str = str + ", " + parse_id(listWords[i + 1])
700
700
  end
@@ -759,7 +759,7 @@ def parse_line(full_line, loc_out_file, wait_check_flag = false)
759
759
  # Print the function name and the first argument
760
760
  str = str + words[1].downcase + "(" + parse_id(listWords[0])
761
761
 
762
- # Print the remaining arguments preceeded by a separator
762
+ # Print the remaining arguments preceded by a separator
763
763
  (listWords.length - 1).times do |i|
764
764
  str = str + ", " + parse_id(listWords[i + 1])
765
765
  end
@@ -783,7 +783,7 @@ def parse_line(full_line, loc_out_file, wait_check_flag = false)
783
783
  str = str + "# SCL Ignored: " + line
784
784
 
785
785
  when "wait"
786
- # add a space incase there's no space between or and parantheses
786
+ # add a space in case there's no space between or and parentheses
787
787
  line = line.gsub(')or', ') or')
788
788
  words = line.split(' ')
789
789
  # Only a wait
@@ -1055,8 +1055,8 @@ end
1055
1055
 
1056
1056
  begin
1057
1057
  opts.parse!(ARGV)
1058
- rescue => err
1059
- puts err
1058
+ rescue => e
1059
+ puts e
1060
1060
  puts opts
1061
1061
  exit
1062
1062
  end
@@ -1164,7 +1164,7 @@ else
1164
1164
  @universal_index = 0
1165
1165
  @data_by_lines = @data.lines.to_a
1166
1166
  # Parse each line in the file
1167
- @data_by_lines.each do |line|
1167
+ @data_by_lines.each do |_line|
1168
1168
  if @verify_wait_check == true
1169
1169
  # Skip line
1170
1170
  @verify_wait_check = false
data/bin/openc3cli CHANGED
@@ -58,17 +58,30 @@ MIGRATE_PARSER = OptionParser.new do |opts|
58
58
  end
59
59
  ERROR_CODE = 1
60
60
 
61
+ CLI_SCRIPT_ACTIONS = %w(help list run spawn)
62
+ $script_interrupt_text = ''
63
+ trap('INT') do
64
+ abort("Interrupted at console; exiting.#{$script_interrupt_text}")
65
+ end
66
+
61
67
  # Prints the usage text for the openc3cli executable
62
68
  def print_usage
63
69
  puts "Usage:"
64
70
  puts " cli help # Displays this information"
65
71
  puts " cli rake # Runs rake in the local directory"
66
72
  puts " cli irb # Runs irb in the local directory"
73
+ puts " cli script list /PATH SCOPE # lists script names filtered by path within scope, 'DEFAULT' if not given"
74
+ puts " cli script spawn NAME SCOPE variable1=value1 variable2=value2 # Starts named script remotely"
75
+ puts " cli script run NAME SCOPE variable1=value1 variable2=value2 # Starts named script, monitoring status on console,\
76
+ by default until error or exit"
77
+ puts " PARAMETERS name-value pairs to form the script's runtime environment"
78
+ puts " OPTIONS: --wait 0 seconds to monitor status before detaching from the running script; ie --wait 100"
79
+ puts " --disconnect run the script in disconnect mode"
67
80
  puts " cli validate /PATH/FILENAME.gem SCOPE variables.txt # Validate a COSMOS plugin gem file"
68
81
  puts " cli load /PATH/FILENAME.gem SCOPE variables.txt # Loads a COSMOS plugin gem file"
69
82
  puts " cli list <SCOPE> # Lists installed plugins, SCOPE is DEFAULT if not given"
70
83
  puts " cli generate TYPE OPTIONS # Generate various COSMOS entities"
71
- puts " OPTIONS: --ruby or --python to specify the language in the generated code"
84
+ puts " OPTIONS: --ruby or --python is required to specify the language in the generated code unless OPENC3_LANGUAGE is set"
72
85
  puts " #{MIGRATE_PARSER}"
73
86
  puts " cli bridge CONFIG_FILENAME # Run COSMOS host bridge"
74
87
  puts " cli bridgegem gem_name variable1=value1 variable2=value2 # Runs bridge using gem bridge.txt"
@@ -160,8 +173,8 @@ def migrate(args)
160
173
  end
161
174
 
162
175
  # Migrate cmd_tlm_server.txt info to plugin.txt
163
- Dir.glob('targets/**/cmd_tlm_server*.txt') do |file|
164
- File.open(file) do |file|
176
+ Dir.glob('targets/**/cmd_tlm_server*.txt') do |cmd_tlm_server_file|
177
+ File.open(cmd_tlm_server_file) do |file|
165
178
  file.each do |line|
166
179
  next if line =~ /^\s*#/ # Ignore comments
167
180
  next if line.strip.empty? # Ignore empty lines
@@ -193,9 +206,9 @@ def migrate(args)
193
206
  lines = screen.split("\n")
194
207
  lines.map! do |line|
195
208
  if line.include?('Qt.')
196
- line = "# FIXME (no Qt): #{line.sub("<%", "< %").sub("%>", "% >")}"
209
+ "# FIXME (no Qt): #{line.sub("<%", "< %").sub("%>", "% >")}"
197
210
  elsif line.include?('Cosmos::')
198
- line = "# FIXME (no Cosmos::): #{line.sub("<%", "< %").sub("%>", "% >")}"
211
+ "# FIXME (no Cosmos::): #{line.sub("<%", "< %").sub("%>", "% >")}"
199
212
  else
200
213
  line
201
214
  end
@@ -425,7 +438,7 @@ def load_plugin(plugin_file_path, scope:, plugin_hash_file: nil, force: false)
425
438
  unless OpenC3::ScopeModel.names.include?(scope)
426
439
  begin
427
440
  puts "Creating scope: #{scope}"
428
- scope_model = OpenC3::ScopeModel.new(name: scope, scope: scope)
441
+ scope_model = OpenC3::ScopeModel.new(name: scope)
429
442
  scope_model.create
430
443
  scope_model.deploy(".", {})
431
444
  rescue => e
@@ -493,7 +506,6 @@ def load_plugin(plugin_file_path, scope:, plugin_hash_file: nil, force: false)
493
506
  else
494
507
  # Outside Cluster
495
508
  require 'openc3/script'
496
-
497
509
  if plugin_hash_file
498
510
  plugin_hash = JSON.parse(File.read(plugin_hash_file), :allow_nan => true, :create_additions => true)
499
511
  else
@@ -648,6 +660,167 @@ def run_bridge(filename, params)
648
660
  end
649
661
  end
650
662
 
663
+ def cli_script_monitor(script_id)
664
+ ret_code = ERROR_CODE
665
+ require 'openc3/script'
666
+ OpenC3::RunningScriptWebSocketApi.new(id: script_id) do |api|
667
+ while (resp = api.read) do
668
+ # see ScriptRunner.vue for types and states
669
+ case resp['type']
670
+ when 'error', 'fatal'
671
+ $script_interrupt_text = ''
672
+ puts 'script failed'
673
+ break
674
+ when 'file'
675
+ puts "Filename #{resp['filename']} scope #{resp['scope']}"
676
+ when 'line'
677
+ fn = resp['filename'].nil? ? '<no file>' : resp['filename']
678
+ puts "At [#{fn}:#{resp['line_no']}] state [#{resp['state']}]"
679
+ if resp['state'] == 'error'
680
+ $script_interrupt_text = ''
681
+ puts 'script failed'
682
+ break
683
+ end
684
+ when 'output'
685
+ puts resp['line']
686
+ when 'paused'
687
+ if resp['state'] == 'fatal'
688
+ $script_interrupt_text = ''
689
+ puts 'script failed'
690
+ break
691
+ end
692
+ when 'complete'
693
+ $script_interrupt_text = ''
694
+ puts 'script complete'
695
+ ret_code = 0
696
+ break
697
+ # These conditions are all handled by the else
698
+ # when 'running', 'breakpoint', 'waiting', 'time'
699
+ else
700
+ puts resp.pretty_inspect
701
+ end
702
+ end
703
+ end
704
+ return ret_code
705
+ end
706
+
707
+ def cli_script_list(args=[])
708
+ path = ''
709
+ if (args[0] && args[0][0] == '/')
710
+ path = (args.shift)[1..-1]+'/'
711
+ end
712
+ scope = args[1]
713
+ scope ||= 'DEFAULT'
714
+ require 'openc3/script'
715
+ script_list(scope: scope).each do |script_name|
716
+ puts(script_name) if script_name.start_with?(path)
717
+ end
718
+ return 0
719
+ end
720
+
721
+ def cli_script_run(disconnect=false, environment={}, args=[])
722
+ # we are limiting the wait for status, not the script run time
723
+ # we make 0 mean 'forever'
724
+ ret_code = ERROR_CODE
725
+ wait_limit = 0
726
+ if (i = args.index('--wait'))
727
+ begin
728
+ args.delete('--wait')
729
+ # pull out the flag
730
+ seconds = args[i]
731
+ wait_limit = Integer(seconds, 10) # only decimal, ignore leading 0
732
+ args.delete_at(i)
733
+ # and its value
734
+ rescue ArgumentError
735
+ abort(" --wait requires a number of seconds to wait, not [#{seconds}]")
736
+ end
737
+ end
738
+ abort("No script file provided") if args[0].nil?
739
+ scope = args[1]
740
+ scope ||= 'DEFAULT'
741
+ require 'openc3/script'
742
+ id = script_run(args[0], disconnect: disconnect, environment: environment, scope: scope) # could raise
743
+ $script_interrupt_text = " Script #{args[0]} still running remotely.\n" # for Ctrl-C
744
+ if (wait_limit < 1) then
745
+ ret_code = cli_script_monitor(id)
746
+ else
747
+ Timeout::timeout(wait_limit, nil, "--wait #{wait_limit} exceeded") do
748
+ ret_code = cli_script_monitor(id)
749
+ rescue Timeout::ExitException, Timeout::Error => e
750
+ # Timeout exceptions are also raised by the Websocket API, so we check
751
+ if e.message =~ /^--wait /
752
+ puts e.message + ", detaching from running script #{args[0]}"
753
+ else
754
+ raise
755
+ end
756
+ end
757
+ end
758
+ return ret_code
759
+ end
760
+
761
+ def cli_script_spawn(disconnect=false, environment={}, args=[])
762
+ ret_code = ERROR_CODE
763
+ if (args.index('--wait'))
764
+ abort("Did you mean \"script run --wait <seconds> [...]\"?")
765
+ end
766
+ abort("No script file provided") if args[0].nil?
767
+ # heaven help you if you left out the script name
768
+ scope = args[1]
769
+ scope ||= 'DEFAULT'
770
+ require 'openc3/script'
771
+ if (id = script_run(args[0], disconnect: disconnect, environment: environment, scope: scope))
772
+ puts id
773
+ ret_code = 0
774
+ end
775
+ return ret_code
776
+ end
777
+
778
+ ## cli_script(args) turns an ARGV of [spawn|run] <--wait 123...> <--disconnect> SCRIPT <scope> <ENV_A=1 ENV_B=2 ...>
779
+ # into function calls and tidied parameters to remote-control a script via RunningScriptWebSocketApi
780
+ def cli_script(args=[])
781
+ ret_code = ERROR_CODE
782
+ check_environment()
783
+ # Double check for the OPENC3_API_PASSWORD because it is absolutely required
784
+ # We always pass it via openc3.sh even if it's not defined so check for empty
785
+ if ENV['OPENC3_API_PASSWORD'].nil? or ENV['OPENC3_API_PASSWORD'].empty?
786
+ abort "OPENC3_API_PASSWORD environment variable is required for cli script"
787
+ end
788
+ command = args.shift
789
+ # pull out the disconnect flag
790
+ discon = args.delete('--disconnect')
791
+ discon = (discon.is_a? String) ? true : false
792
+ environ = {}
793
+ args.each do |arg|
794
+ name, value = arg.split('=')
795
+ if name and value
796
+ # add env[k]=v ; pull out "k=v"
797
+ environ[name] = value
798
+ args.delete(arg)
799
+ end
800
+ end
801
+ case command
802
+ # for list
803
+ # args[] should now be ["/<path>", "<scope>"]
804
+ # or ["/<path>"]
805
+ # or ["<scope>"]
806
+ # or []
807
+ when 'list'
808
+ ret_code = cli_script_list(args)
809
+ # for spawn or run
810
+ # args[] should now be ["--wait 100", "script_name", "<scope>"]
811
+ # or ["--wait 100", "script_name"]
812
+ # or ["script_name", "<scope>"]
813
+ # or ["script_name"]
814
+ when 'spawn'
815
+ ret_code = cli_script_spawn(discon, environ, args)
816
+ when 'run'
817
+ ret_code = cli_script_run(discon, environ, args)
818
+ else
819
+ abort 'openc3cli internal error: parsing arguments'
820
+ end
821
+ exit(ret_code)
822
+ end
823
+
651
824
  if not ARGV[0].nil? # argument(s) given
652
825
 
653
826
  # Handle each task
@@ -657,11 +830,20 @@ if not ARGV[0].nil? # argument(s) given
657
830
  ARGV.clear
658
831
  IRB.start
659
832
 
833
+ when 'script'
834
+ case ARGV[1]
835
+ when 'list', 'run', 'spawn'
836
+ cli_script(ARGV[1..-1])
837
+ else
838
+ # invalid actions, misplaced and malformed leading options and 'help' come here
839
+ abort("cli script <action> must be one of #{CLI_SCRIPT_ACTIONS}, not [#{ARGV[1]}]")
840
+ end
841
+
660
842
  when 'rake'
661
843
  if File.exist?('Rakefile')
662
844
  puts `rake #{ARGV[1..-1].join(' ')}`
663
845
  else
664
- puts "No Rakefile found! Only run 'rake' in the presense of a Rakefile which is typically at the root of your COSMOS project."
846
+ puts "No Rakefile found! Only run 'rake' in the presence of a Rakefile which is typically at the root of your COSMOS project."
665
847
  end
666
848
 
667
849
  when 'validate'
@@ -1,5 +1,5 @@
1
1
  ---
2
- tcpip_client_interface.rb:
2
+ tcpip_client_interface:
3
3
  parameters:
4
4
  - name: Host
5
5
  required: true
@@ -29,7 +29,7 @@ tcpip_client_interface.rb:
29
29
  # prettier-ignore
30
30
  values:
31
31
  <%= MetaConfigParser.load('protocols.yaml').to_meta_config_yaml(8) %>
32
- tcpip_server_interface.rb:
32
+ tcpip_server_interface:
33
33
  parameters:
34
34
  - name: Write Port
35
35
  required: true
@@ -54,7 +54,7 @@ tcpip_server_interface.rb:
54
54
  # prettier-ignore
55
55
  values:
56
56
  <%= MetaConfigParser.load('protocols.yaml').to_meta_config_yaml(8) %>
57
- udp_interface.rb:
57
+ udp_interface:
58
58
  description: The UDP interface uses UDP packets to send and receive telemetry
59
59
  from the target
60
60
  parameters:
@@ -76,7 +76,7 @@ udp_interface.rb:
76
76
  description: Port on the local machine to send commands from. Default is
77
77
  'nil' (socket is not bound to an outgoing port).
78
78
  values: \d{2,5}
79
- - name: Inteface Address
79
+ - name: Interface Address
80
80
  required: false
81
81
  description:
82
82
  If the remote machine supports multicast the interface address
@@ -100,7 +100,7 @@ udp_interface.rb:
100
100
  required: false
101
101
  description: Address to bind UDP ports to
102
102
  values: .+
103
- serial_interface.rb:
103
+ serial_interface:
104
104
  description: Connects to a target over a serial port. OpenC3 provides drivers
105
105
  for both Windows and POSIX drivers for UNIX based systems.
106
106
  parameters:
@@ -171,6 +171,10 @@ ACCESSOR:
171
171
  required: true
172
172
  description: The name of the accessor class
173
173
  values: .+
174
+ - name: Argument
175
+ required: false
176
+ description: Additional argument passed to the accessor class constructor
177
+ values: .+
174
178
  since: 5.0.10
175
179
  TEMPLATE:
176
180
  summary: Defines a template string used to initialize the command before default values are filled in
@@ -244,3 +248,58 @@ SCREEN:
244
248
  description: Screen Name of related telemetry screen
245
249
  values: .+
246
250
  since: 5.14.0
251
+ VIRTUAL:
252
+ summary: Marks this packet as virtual and not participating in identification
253
+ description: Used for packet definitions that can be used as structures for items with a given packet.
254
+ since: 5.18.0
255
+ VALIDATOR:
256
+ summary: Defines a validator class for a command
257
+ description: Validator class is used to validate the command success or failure with both a pre_check and post_check method.
258
+ parameters:
259
+ - name: Class Filename
260
+ required: true
261
+ description: The filename which contains the Ruby or Python class. The filename must
262
+ be named after the class such that the class is a CamelCase version of the
263
+ underscored filename. For example, 'command_validator.rb' should contain
264
+ 'class CommandValidator'.
265
+ values: .*
266
+ - name: Argument
267
+ required: false
268
+ description: Additional argument passed to the validator class constructor
269
+ values: .*
270
+ ruby_example: |
271
+ VALIDATOR custom_validator.rb
272
+
273
+ Defined in custom_validator.rb:
274
+
275
+ require 'openc3/packets/command_validator'
276
+ class CustomValidator < OpenC3::CommandValidator
277
+ def pre_check(packet)
278
+ if tlm("TGT PKT ITEM") == 0
279
+ return [false, "TGT PKT ITEM is 0"]
280
+ end
281
+ @cmd_acpt_cnt = tlm("TGT PKT CMD_ACPT_CNT")
282
+ return [true, nil]
283
+ end
284
+ def post_check(packet)
285
+ wait_check("TGT PKT CMD_ACPT_CNT > #{@cmd_acpt_cnt}", 10)
286
+ return [true, nil]
287
+ end
288
+ end
289
+
290
+ python_example: |
291
+ VALIDATOR custom_validator.rb
292
+
293
+ Defined in custom_validator.py:
294
+
295
+ class CustomValidator(CommandValidator):
296
+ def pre_check(self, command):
297
+ if tlm("TGT PKT ITEM") == 0:
298
+ return [False, "TGT PKT ITEM is 0"]
299
+ self.cmd_acpt_cnt = tlm("INST HEALTH_STATUS CMD_ACPT_CNT")
300
+ return [True, None]
301
+
302
+ def post_check(self, command):
303
+ wait_check(f"INST HEALTH_STATUS CMD_ACPT_CNT > {self.cmd_acpt_cnt}", 10)
304
+ return [True, None]
305
+ since: 5.19.0
@@ -6,9 +6,12 @@ MAP_TARGET:
6
6
  required: true
7
7
  description: Target name to map to this interface
8
8
  values: .+
9
- example: |
9
+ ruby_example: |
10
10
  INTERFACE DATA_INT tcpip_client_interface.rb host.docker.internal 8080 8081 10.0 nil BURST
11
11
  MAP_TARGET DATA
12
+ python_example: |
13
+ INTERFACE DATA_INT openc3/interfaces/tcpip_client_interface.py host.docker.internal 8080 8081 10.0 nil BURST
14
+ MAP_TARGET DATA
12
15
  MAP_CMD_TARGET:
13
16
  summary: Maps a target name to an interface for commands only
14
17
  since: 5.2.0
@@ -17,9 +20,12 @@ MAP_CMD_TARGET:
17
20
  required: true
18
21
  description: Command target name to map to this interface
19
22
  values: .+
20
- example: |
23
+ ruby_example: |
21
24
  INTERFACE CMD_INT tcpip_client_interface.rb host.docker.internal 8080 8081 10.0 nil BURST
22
25
  MAP_CMD_TARGET DATA # Only DATA commands go on the CMD_INT interface
26
+ python_example: |
27
+ INTERFACE CMD_INT openc3/interfaces/tcpip_client_interface.py host.docker.internal 8080 8081 10.0 nil BURST
28
+ MAP_CMD_TARGET DATA # Only DATA commands go on the CMD_INT interface
23
29
  MAP_TLM_TARGET:
24
30
  summary: Maps a target name to an interface for telemetry only
25
31
  since: 5.2.0
@@ -28,9 +34,12 @@ MAP_TLM_TARGET:
28
34
  required: true
29
35
  description: Telemetry target name to map to this interface
30
36
  values: .+
31
- example: |
37
+ ruby_example: |
32
38
  INTERFACE TLM_INT tcpip_client_interface.rb host.docker.internal 8080 8081 10.0 nil BURST
33
39
  MAP_TLM_TARGET DATA # Only DATA telemetry received on TLM_INT interface
40
+ python_example: |
41
+ INTERFACE TLM_INT openc3/interfaces/tcpip_client_interface.py host.docker.internal 8080 8081 10.0 nil BURST
42
+ MAP_TLM_TARGET DATA # Only DATA telemetry received on TLM_INT interface
34
43
  DONT_CONNECT:
35
44
  summary: Server will not automatically try to connect to the interface at startup
36
45
  DONT_RECONNECT:
@@ -51,7 +60,7 @@ DISABLE_DISCONNECT:
51
60
  description:
52
61
  Use this keyword to prevent the user from disconnecting from the interface.
53
62
  This is typically used in a 'production' environment where you would not want
54
- the user to inadvertantly disconnect from a target.
63
+ the user to inadvertently disconnect from a target.
55
64
  LOG_RAW:
56
65
  summary: Deprecated, use LOG_STREAM
57
66
  LOG_STREAM:
@@ -102,17 +111,18 @@ PROTOCOL:
102
111
  values: ["READ", "WRITE", "READ_WRITE"]
103
112
  - name: Protocol Filename or Classname
104
113
  required: true
105
- description: Ruby filename or class name which implements the protocol
114
+ description: Ruby or Python filename or class name which implements the protocol
106
115
  values: .*
107
116
  - name: Protocol specific parameters
108
117
  required: false
109
118
  description: Additional parameters used by the protocol
110
- example: |
119
+ ruby_example: |
111
120
  INTERFACE DATA_INT tcpip_client_interface.rb host.docker.internal 8080 8081 10.0 nil nil
112
121
  MAP_TARGET DATA
113
122
  # Rather than defining the LENGTH protocol on the INTERFACE line we define it here
114
123
  PROTOCOL READ LengthProtocol 0 16 0 1 BIG_ENDIAN 4 0xBA5EBA11
115
- INTERFACE DATA_INT tcpip_client_interface.rb host.docker.internal 8080 8081 10.0 nil BURST
124
+ python_example: |
125
+ INTERFACE DATA_INT openc3/interfaces/tcpip_client_interface.py host.docker.internal 8080 8081 10.0 nil BURST
116
126
  MAP_TARGET DATA
117
127
  PROTOCOL READ IgnorePacketProtocol INST IMAGE # Drop all INST IMAGE packets
118
128
  OPTION:
@@ -220,8 +230,8 @@ CMD:
220
230
  required: true
221
231
  description: One or more arguments to exec to run the microservice.
222
232
  values: .+
223
- example: |
224
- CMD ruby interface_microservice.rb DEFAULT__INTERFACE__INT1
233
+ ruby_example: CMD ruby interface_microservice.rb DEFAULT__INTERFACE__INT1
234
+ python_example: CMD python interface_microservice.py DEFAULT__INTERFACE__INT1
225
235
  CONTAINER:
226
236
  summary: Docker Container
227
237
  description: Container to execute and run the microservice in. Only used in COSMOS Enterprise Edition.