openc3 5.4.3.pre.beta0 → 5.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of openc3 might be problematic. Click here for more details.

Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/bin/openc3cli +58 -74
  3. data/data/config/item_modifiers.yaml +1 -1
  4. data/data/config/plugins.yaml +5 -0
  5. data/data/config/widgets.yaml +13 -11
  6. data/lib/openc3/api/cmd_api.rb +43 -17
  7. data/lib/openc3/api/tlm_api.rb +37 -4
  8. data/lib/openc3/bridge/bridge.rb +3 -3
  9. data/lib/openc3/bridge/bridge_config.rb +68 -20
  10. data/lib/openc3/interfaces/interface.rb +8 -0
  11. data/lib/openc3/microservices/decom_microservice.rb +0 -3
  12. data/lib/openc3/microservices/interface_microservice.rb +2 -0
  13. data/lib/openc3/microservices/reaction_microservice.rb +0 -1
  14. data/lib/openc3/models/cvt_model.rb +1 -2
  15. data/lib/openc3/models/metadata_model.rb +1 -1
  16. data/lib/openc3/models/microservice_model.rb +1 -1
  17. data/lib/openc3/models/note_model.rb +1 -1
  18. data/lib/openc3/models/plugin_model.rb +14 -7
  19. data/lib/openc3/models/timeline_model.rb +1 -1
  20. data/lib/openc3/models/trigger_group_model.rb +1 -1
  21. data/lib/openc3/packets/packet_item.rb +1 -1
  22. data/lib/openc3/script/script.rb +10 -0
  23. data/lib/openc3/script/storage.rb +5 -5
  24. data/lib/openc3/script/web_socket_api.rb +423 -0
  25. data/lib/openc3/streams/tcpip_client_stream.rb +1 -1
  26. data/lib/openc3/streams/web_socket_client_stream.rb +98 -0
  27. data/lib/openc3/tools/table_manager/table_manager_core.rb +1 -2
  28. data/lib/openc3/utilities/aws_bucket.rb +22 -4
  29. data/lib/openc3/utilities/bucket_file_cache.rb +3 -2
  30. data/lib/openc3/utilities/bucket_utilities.rb +1 -1
  31. data/lib/openc3/utilities/cli_generator.rb +210 -0
  32. data/lib/openc3/utilities/local_mode.rb +2 -2
  33. data/lib/openc3/utilities/target_file.rb +43 -32
  34. data/lib/openc3/version.rb +7 -7
  35. data/templates/conversion/conversion.rb +43 -0
  36. data/templates/limits_response/response.rb +51 -0
  37. data/templates/microservice/microservices/TEMPLATE/microservice.rb +62 -0
  38. data/templates/plugin/plugin.txt +1 -0
  39. metadata +49 -15
  40. data/templates/plugin-template/plugin.txt +0 -9
  41. /data/templates/{plugin-template → plugin}/LICENSE.txt +0 -0
  42. /data/templates/{plugin-template → plugin}/README.md +0 -0
  43. /data/templates/{plugin-template → plugin}/Rakefile +0 -0
  44. /data/templates/{plugin-template → plugin}/plugin.gemspec +0 -0
  45. /data/templates/{plugin-template → target}/targets/TARGET/cmd_tlm/cmd.txt +0 -0
  46. /data/templates/{plugin-template → target}/targets/TARGET/cmd_tlm/tlm.txt +0 -0
  47. /data/templates/{plugin-template → target}/targets/TARGET/lib/target.rb +0 -0
  48. /data/templates/{plugin-template → target}/targets/TARGET/procedures/procedure.rb +0 -0
  49. /data/templates/{plugin-template → target}/targets/TARGET/screens/status.txt +0 -0
  50. /data/templates/{plugin-template → target}/targets/TARGET/target.txt +0 -0
@@ -17,7 +17,7 @@
17
17
  # All changes Copyright 2022, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
- # This file may also be used under the terms of a commercial license
20
+ # This file may also be used under the terms of a commercial license
21
21
  # if purchased from OpenC3, Inc.
22
22
 
23
23
  require 'openc3/utilities/logger'
@@ -30,28 +30,55 @@ module OpenC3
30
30
  # @return [Hash<String, Interface>] Routers hash
31
31
  attr_accessor :routers
32
32
 
33
- def initialize(filename)
33
+ def initialize(filename, existing_variables = {})
34
34
  @interfaces = {}
35
35
  @routers = {}
36
- process_file(filename)
36
+ process_file(filename, existing_variables)
37
37
  end
38
38
 
39
39
  def self.generate_default(filename)
40
40
  default_config = <<~EOF
41
- # Example Host Bridge Configuration for a Serial Port
42
- #
43
- # INTERFACE <Interface Name> <Interface File> <Interface Params...>
44
- # INTERFACE <Interface Name> serial_interface.rb <Write Port> <Read Port> <Baud Rate> <Parity ODD/EVEN/NONE> <Stop Bits> <Write Timeout> <Read Timeout> <Protocol Name> <Protocol Params>
45
- # INTERFACE <Interface Name> serial_interface.rb <Write Port> <Read Port> <Baud Rate> <Parity ODD/EVEN/NONE> <Stop Bits> <Write Timeout> <Read Timeout> BURST <Discard Leading Bytes> <Sync Pattern> <Add Sync On Write>
46
- # INTERFACE SERIAL_INT serial_interface.rb /dev/ttyS1 /dev/ttyS1 38400 ODD 1 10.0 nil BURST 4 0xDEADBEEF
47
- INTERFACE SERIAL_INT serial_interface.rb COM1 COM1 9600 NONE 1 10.0 nil BURST
41
+ # Write serial port name
42
+ VARIABLE write_port_name COM1
48
43
  #{' '}
49
- # ROUTER <Router Name> <Interface File> <Interface Params...>
50
- # ROUTER SERIAL_ROUTER tcpip_server_interface.rb <Write Port> <Read Port> <Write Timeout> <Read Timeout> <Protocol Name> <Protocol Params>
51
- # ROUTER SERIAL_ROUTER tcpip_server_interface.rb <Write Port> <Read Port> <Write Timeout> <Read Timeout> BURST <Discard Leading Bytes> <Sync Pattern> <Add Sync On Write>
52
- ROUTER SERIAL_ROUTER tcpip_server_interface.rb 2950 2950 10.0 nil BURST
53
- # ROUTE <Interface Name>
54
- ROUTE SERIAL_INT
44
+ # Read serial port name
45
+ VARIABLE read_port_name COM1
46
+ #{' '}
47
+ # Baud Rate
48
+ VARIABLE baud_rate 115200
49
+ #{' '}
50
+ # Parity - NONE, ODD, or EVEN
51
+ VARIABLE parity NONE
52
+ #{' '}
53
+ # Stop bits - 0, 1, or 2
54
+ VARIABLE stop_bits 1
55
+ #{' '}
56
+ # Write Timeout
57
+ VARIABLE write_timeout 10.0
58
+ #{' '}
59
+ # Read Timeout
60
+ VARIABLE read_timeout nil
61
+ #{' '}
62
+ # Flow Control - NONE, or RTSCTS
63
+ VARIABLE flow_control NONE
64
+ #{' '}
65
+ # Data bits per word - Typically 8
66
+ VARIABLE data_bits 8
67
+ #{' '}
68
+ # Port to listen for connections from COSMOS - Plugin must match
69
+ VARIABLE router_port 2950
70
+ #{' '}
71
+ # Port to listen on for connections from COSMOS. Defaults to localhost for security. Will need to be opened
72
+ # if COSMOS is on another machine.
73
+ VARIABLE router_listen_address 127.0.0.1
74
+ #{' '}
75
+ INTERFACE SERIAL_INT serial_interface.rb <%= write_port_name %> <%= read_port_name %> <%= baud_rate %> <%= parity %> <%= stop_bits %> <%= write_timeout %> <%= read_timeout %>
76
+ OPTION FLOW_CONTROL <%= flow_control %>
77
+ OPTION DATA_BITS <%= data_bits %>
78
+ #{' '}
79
+ ROUTER SERIAL_ROUTER tcpip_server_interface.rb <%= router_port %> <%= router_port %> 10.0 nil BURST
80
+ ROUTE SERIAL_INT
81
+ OPTION LISTEN_ADDRESS <%= router_listen_address %>
55
82
  #{' '}
56
83
  EOF
57
84
 
@@ -66,18 +93,39 @@ module OpenC3
66
93
  # Processes a file and adds in the configuration defined in the file
67
94
  #
68
95
  # @param filename [String] The name of the configuration file to parse
69
- # @param recursive [Boolean] Whether process_file is being called
70
- # recursively
71
- def process_file(filename, recursive = false)
96
+ def process_file(filename, existing_variables = {})
72
97
  current_interface_or_router = nil
73
98
  current_type = nil
74
99
  current_interface_log_added = false
75
100
 
76
101
  Logger.info "Processing Bridge configuration in file: #{File.expand_path(filename)}"
77
102
 
103
+ variables = {}
78
104
  parser = ConfigParser.new
79
- parser.parse_file(filename) do |keyword, params|
105
+ parser.parse_file(filename,
106
+ false,
107
+ true,
108
+ false) do |keyword, params|
80
109
  case keyword
110
+ when 'VARIABLE'
111
+ usage = "#{keyword} <Variable Name> <Default Value>"
112
+ parser.verify_num_parameters(2, nil, usage)
113
+ variable_name = params[0]
114
+ value = params[1..-1].join(" ")
115
+ variables[variable_name] = value
116
+ if existing_variables && existing_variables.key?(variable_name)
117
+ variables[variable_name] = existing_variables[variable_name]
118
+ end
119
+ # Ignore everything else during phase 1
120
+ end
121
+ end
122
+
123
+ parser = ConfigParser.new
124
+ parser.parse_file(filename, false, true, true, variables) do |keyword, params|
125
+ case keyword
126
+
127
+ when 'VARIABLE'
128
+ # Ignore during this pass
81
129
 
82
130
  when 'INTERFACE'
83
131
  usage = "INTERFACE <Name> <Filename> <Specific Parameters>"
@@ -25,6 +25,11 @@ require 'openc3/io/raw_logger_pair'
25
25
  require 'openc3/utilities/secrets'
26
26
 
27
27
  module OpenC3
28
+ # Define a class to allow interfaces and protocols to reject commands without
29
+ # disconnecting the interface
30
+ class WriteRejectError < StandardError
31
+ end
32
+
28
33
  # Defines all the attributes and methods common to all interface classes
29
34
  # used by OpenC3.
30
35
  class Interface
@@ -330,6 +335,9 @@ module OpenC3
330
335
  else
331
336
  @write_mutex.synchronize { yield }
332
337
  end
338
+ rescue WriteRejectError => err
339
+ Logger.error("#{@name}: Write rejected by interface: #{err.message}")
340
+ raise err
333
341
  rescue Exception => err
334
342
  Logger.error("#{@name}: Error writing to interface")
335
343
  disconnect()
@@ -95,7 +95,6 @@ module OpenC3
95
95
  # @param log_change [Boolean] Whether to log this limits change event
96
96
  def limits_change_callback(packet, item, old_limits_state, value, log_change)
97
97
  return if @cancel_thread
98
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
99
98
  packet_time = packet.packet_time
100
99
  message = "#{packet.target_name} #{packet.packet_name} #{item.name} = #{value} is #{item.limits.state}"
101
100
  message << " (#{packet.packet_time.sys.formatted})" if packet_time
@@ -136,8 +135,6 @@ module OpenC3
136
135
  @logger.error e.formatted
137
136
  end
138
137
  end
139
-
140
- diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
141
138
  end
142
139
  end
143
140
  end
@@ -202,6 +202,8 @@ module OpenC3
202
202
  else
203
203
  next "Interface not connected: #{@interface.name}"
204
204
  end
205
+ rescue WriteRejectError => e
206
+ next e.message
205
207
  rescue => e
206
208
  @logger.error "#{@interface.name}: #{e.formatted}"
207
209
  next e.message
@@ -264,7 +264,6 @@ module OpenC3
264
264
  when 'trigger'
265
265
  process_enabled_trigger(data: data)
266
266
  end
267
- current_time = Time.now.to_i
268
267
  rescue StandardError => e
269
268
  @logger.error "ReactionWorker-#{@ident} failed to evaluate kind: #{kind} data: #{data}\n#{e.formatted}"
270
269
  end
@@ -240,11 +240,10 @@ module OpenC3
240
240
  # return an ordered array of hash with keys
241
241
  def self._parse_item(lookups, overrides, item, scope:)
242
242
  target_name, packet_name, item_name, value_type = item.split('__')
243
- raise ArgumentError, "items must be formatted as TGT__PKT__ITEM__TYPE" if target_name.nil? || packet_name.nil? || item_name.nil? || value_type.nil?
244
243
 
245
244
  # We build lookup keys by including all the less formatted types to gracefully degrade lookups
246
245
  # This allows the user to specify WITH_UNITS and if there is no conversions it will simply return the RAW value
247
- case value_type.upcase
246
+ case value_type
248
247
  when 'RAW'
249
248
  keys = [item_name]
250
249
  when 'CONVERTED'
@@ -69,7 +69,7 @@ module OpenC3
69
69
  if @color.nil?
70
70
  @color = '#%06x' % (rand * 0xffffff)
71
71
  end
72
- unless @color =~ /(#*)([0-9,a-f,A-F]{6})/
72
+ unless @color =~ /(#*)([0-9a-fA-F]{6})/
73
73
  raise SortedInputError.new "invalid color, must be in hex format, e.g. #FF0000"
74
74
  end
75
75
  @color = "##{@color}" unless @color.start_with?('#')
@@ -163,7 +163,7 @@ module OpenC3
163
163
  parser.verify_num_parameters(1, 2, usage)
164
164
  begin
165
165
  @ports << [Integer(parameters[0])]
166
- rescue => err # In case Integer fails
166
+ rescue # In case Integer fails
167
167
  raise ConfigParser::Error.new(parser, "Port must be an integer: #{parameters[0]}", usage)
168
168
  end
169
169
  protocol = ConfigParser.handle_nil(parameters[1])
@@ -78,7 +78,7 @@ module OpenC3
78
78
  if @color.nil?
79
79
  @color = '#%06x' % (rand * 0xffffff)
80
80
  end
81
- unless @color =~ /(#*)([0-9,a-f,A-F]{6})/
81
+ unless @color =~ /(#*)([0-9a-fA-F]{6})/
82
82
  raise SortedInputError.new "invalid color, must be in hex format, e.g. #FF0000"
83
83
  end
84
84
  @color = "##{@color}" unless @color.start_with?('#')
@@ -72,7 +72,7 @@ module OpenC3
72
72
  temp_dir = Dir.mktmpdir
73
73
  tf = nil
74
74
  begin
75
- if File.exists?(gem_file_path)
75
+ if File.exist?(gem_file_path)
76
76
  # Load gem to internal gem server
77
77
  OpenC3::GemModel.put(gem_file_path, gem_install: false, scope: scope) unless validate_only
78
78
  else
@@ -122,7 +122,6 @@ module OpenC3
122
122
  if existing_variables && existing_variables.key?(variable_name)
123
123
  variables[variable_name] = existing_variables[variable_name]
124
124
  end
125
- # Ignore everything else during phase 1
126
125
  end
127
126
  end
128
127
 
@@ -162,14 +161,21 @@ module OpenC3
162
161
  gem_path = File.join(temp_dir, "gem")
163
162
  FileUtils.mkdir_p(gem_path)
164
163
  pkg = Gem::Package.new(gem_file_path)
165
- needs_dependencies = pkg.spec.runtime_dependencies.length > 0
166
164
  pkg.extract_files(gem_path)
167
165
  Dir[File.join(gem_path, '**/screens/*.txt')].each do |filename|
168
166
  if File.basename(filename) != File.basename(filename).downcase
169
- raise "Invalid screen name: #{filename}. Screen names must be lowercase."
167
+ raise "Invalid screen filename: #{filename}. Screen filenames must be lowercase."
170
168
  end
171
169
  end
170
+ needs_dependencies = pkg.spec.runtime_dependencies.length > 0
172
171
  needs_dependencies = true if Dir.exist?(File.join(gem_path, 'lib'))
172
+ # If needs_dependencies hasn't already been set we need to scan the plugin.txt
173
+ # to see if they've explicitly set the NEEDS_DEPENDENCIES keyword
174
+ unless needs_dependencies
175
+ if plugin_hash['plugin_txt_lines'].join("\n").include?('NEEDS_DEPENDENCIES')
176
+ needs_dependencies = true
177
+ end
178
+ end
173
179
  if needs_dependencies
174
180
  plugin_model.needs_dependencies = true
175
181
  plugin_model.update unless validate_only
@@ -198,7 +204,7 @@ module OpenC3
198
204
  current_model = nil
199
205
  parser.parse_file(plugin_txt_path, false, true, true, variables) do |keyword, params|
200
206
  case keyword
201
- when 'VARIABLE'
207
+ when 'VARIABLE', 'NEEDS_DEPENDENCIES'
202
208
  # Ignore during phase 2
203
209
  when 'TARGET', 'INTERFACE', 'ROUTER', 'MICROSERVICE', 'TOOL', 'WIDGET'
204
210
  if current_model
@@ -206,12 +212,13 @@ module OpenC3
206
212
  current_model.deploy(gem_path, variables, validate_only: validate_only)
207
213
  current_model = nil
208
214
  end
209
- current_model = OpenC3.const_get((keyword.capitalize + 'Model').intern).handle_config(parser, keyword, params, plugin: plugin_model.name, needs_dependencies: needs_dependencies, scope: scope)
215
+ current_model = OpenC3.const_get((keyword.capitalize + 'Model').intern).handle_config(parser,
216
+ keyword, params, plugin: plugin_model.name, needs_dependencies: needs_dependencies, scope: scope)
210
217
  else
211
218
  if current_model
212
219
  current_model.handle_config(parser, keyword, params)
213
220
  else
214
- raise "Invalid keyword #{keyword} in plugin.txt"
221
+ raise "Invalid keyword '#{keyword}' in plugin.txt"
215
222
  end
216
223
  end
217
224
  end
@@ -91,7 +91,7 @@ module OpenC3
91
91
  if color.nil?
92
92
  color = '#%06x' % (rand * 0xffffff)
93
93
  end
94
- valid_color = color =~ /[0-9,a-f,A-F]{6}/
94
+ valid_color = color =~ /[0-9a-fA-F]{6}/
95
95
  if valid_color.nil?
96
96
  raise RuntimeError.new "invalid color but in hex format. #FF0000"
97
97
  end
@@ -92,7 +92,7 @@ module OpenC3
92
92
  if color.nil?
93
93
  color = '#%06x' % (rand * 0xffffff)
94
94
  end
95
- valid_color = color =~ /[0-9,a-f,A-F]{6}/
95
+ valid_color = color =~ /[0-9a-fA-F]{6}/
96
96
  if valid_color.nil?
97
97
  raise TriggerGroupInputError.new "invalid color must be in hex format. #FF0000"
98
98
  end
@@ -80,7 +80,7 @@ module OpenC3
80
80
 
81
81
  # @return [Hash] Whether or not messages should be printed for this state.
82
82
  # Given as STATE_NAME => true / false.
83
- attr_accessor :messages_disabled
83
+ attr_reader :messages_disabled
84
84
 
85
85
  # Colors associated with states
86
86
  # @return [Hash] State colors given as STATE_NAME => COLOR
@@ -31,6 +31,7 @@ require 'openc3/script/limits'
31
31
  require 'openc3/script/exceptions'
32
32
  require 'openc3/script/script_runner'
33
33
  require 'openc3/script/storage'
34
+ require 'openc3/script/web_socket_api'
34
35
  require 'openc3/utilities/authentication'
35
36
 
36
37
  $api_server = nil
@@ -99,6 +100,15 @@ module OpenC3
99
100
  # NOOP
100
101
  end
101
102
 
103
+ def openc3_script_sleep(sleep_time = nil)
104
+ if sleep_time
105
+ sleep(sleep_time)
106
+ else
107
+ prompt("Press any key to continue...")
108
+ end
109
+ return false
110
+ end
111
+
102
112
  def ask_string(question, blank_or_default = false, password = false)
103
113
  answer = ''
104
114
  default = ''
@@ -77,7 +77,7 @@ module OpenC3
77
77
  else
78
78
  request.body_stream = io_or_string
79
79
  end
80
- response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
80
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
81
81
  http.request(request) do |response|
82
82
  response.value() # Raises an HTTP error if the response is not 2xx (success)
83
83
  return response
@@ -157,17 +157,17 @@ module OpenC3
157
157
  if $openc3_in_cluster
158
158
  case ENV['OPENC3_CLOUD']
159
159
  when 'local'
160
- uri = URI.parse("http://openc3-minio:9000" + url)
160
+ URI.parse("http://openc3-minio:9000" + url)
161
161
  when 'aws'
162
- uri = URI.parse("https://s3.#{ENV['AWS_REGION']}.amazonaws.com" + url)
162
+ URI.parse("https://s3.#{ENV['AWS_REGION']}.amazonaws.com" + url)
163
163
  when 'gcp'
164
- uri = URI.parse("https://storage.googleapis.com" + url)
164
+ URI.parse("https://storage.googleapis.com" + url)
165
165
  # when 'azure'
166
166
  else
167
167
  raise "Unknown cloud #{ENV['OPENC3_CLOUD']}"
168
168
  end
169
169
  else
170
- uri = URI.parse($api_server.generate_url + url)
170
+ URI.parse($api_server.generate_url + url)
171
171
  end
172
172
  end
173
173