openc3 5.20.0 → 6.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/bin/openc3cli +12 -120
  3. data/data/config/command_modifiers.yaml +13 -1
  4. data/data/config/interface_modifiers.yaml +21 -4
  5. data/data/config/item_modifiers.yaml +1 -1
  6. data/data/config/microservice.yaml +15 -2
  7. data/data/config/param_item_modifiers.yaml +1 -1
  8. data/data/config/parameter_modifiers.yaml +1 -1
  9. data/data/config/table_manager.yaml +2 -2
  10. data/data/config/target.yaml +11 -0
  11. data/data/config/telemetry_modifiers.yaml +17 -1
  12. data/data/config/tool.yaml +12 -0
  13. data/data/config/widgets.yaml +13 -17
  14. data/lib/openc3/accessors/form_accessor.rb +4 -3
  15. data/lib/openc3/accessors/html_accessor.rb +3 -3
  16. data/lib/openc3/accessors/http_accessor.rb +13 -13
  17. data/lib/openc3/accessors/xml_accessor.rb +16 -4
  18. data/lib/openc3/api/target_api.rb +0 -30
  19. data/lib/openc3/config/config_parser.rb +6 -3
  20. data/lib/openc3/core_ext/array.rb +0 -16
  21. data/lib/openc3/core_ext.rb +0 -1
  22. data/lib/openc3/interfaces/file_interface.rb +198 -0
  23. data/lib/openc3/interfaces/http_client_interface.rb +71 -39
  24. data/lib/openc3/interfaces/http_server_interface.rb +0 -7
  25. data/lib/openc3/interfaces/interface.rb +2 -0
  26. data/lib/openc3/interfaces/mqtt_interface.rb +32 -15
  27. data/lib/openc3/interfaces/mqtt_stream_interface.rb +19 -4
  28. data/lib/openc3/interfaces/protocols/crc_protocol.rb +7 -0
  29. data/lib/openc3/interfaces/serial_interface.rb +1 -0
  30. data/lib/openc3/interfaces.rb +2 -4
  31. data/lib/openc3/microservices/multi_microservice.rb +3 -3
  32. data/lib/openc3/migrations/20241208080000_no_critical_cmd.rb +31 -0
  33. data/lib/openc3/migrations/20241208080001_no_trigger_group.rb +46 -0
  34. data/lib/openc3/models/interface_model.rb +9 -3
  35. data/lib/openc3/models/microservice_model.rb +8 -1
  36. data/lib/openc3/models/plugin_model.rb +6 -1
  37. data/lib/openc3/models/python_package_model.rb +6 -1
  38. data/lib/openc3/models/reaction_model.rb +14 -10
  39. data/lib/openc3/models/scope_model.rb +60 -42
  40. data/lib/openc3/models/target_model.rb +17 -1
  41. data/lib/openc3/models/timeline_model.rb +17 -5
  42. data/lib/openc3/models/tool_model.rb +15 -3
  43. data/lib/openc3/models/trigger_group_model.rb +6 -3
  44. data/lib/openc3/operators/microservice_operator.rb +8 -0
  45. data/lib/openc3/packets/commands.rb +17 -6
  46. data/lib/openc3/packets/limits.rb +0 -12
  47. data/lib/openc3/packets/packet.rb +1 -1
  48. data/lib/openc3/packets/packet_item.rb +30 -36
  49. data/lib/openc3/packets/structure_item.rb +2 -2
  50. data/lib/openc3/script/script.rb +0 -10
  51. data/lib/openc3/script/web_socket_api.rb +2 -2
  52. data/lib/openc3/streams/mqtt_stream.rb +41 -33
  53. data/lib/openc3/streams/serial_stream.rb +27 -27
  54. data/lib/openc3/streams/stream.rb +17 -17
  55. data/lib/openc3/streams/tcpip_client_stream.rb +1 -1
  56. data/lib/openc3/streams/tcpip_socket_stream.rb +19 -19
  57. data/lib/openc3/system/system.rb +1 -1
  58. data/lib/openc3/system.rb +2 -3
  59. data/lib/openc3/tools/table_manager/table.rb +2 -2
  60. data/lib/openc3/tools/table_manager/table_parser.rb +1 -1
  61. data/lib/openc3/top_level.rb +0 -5
  62. data/lib/openc3/topics/command_decom_topic.rb +0 -7
  63. data/lib/openc3/utilities/bucket_utilities.rb +1 -1
  64. data/lib/openc3/utilities/cli_generator.rb +0 -1
  65. data/lib/openc3/version.rb +7 -7
  66. data/templates/plugin/README.md +1 -1
  67. data/templates/target/targets/TARGET/lib/target.rb +1 -1
  68. data/templates/tool_angular/package.json +8 -8
  69. data/templates/tool_angular/src/app/app.component.html +4 -13
  70. data/templates/tool_angular/src/app/app.component.scss +5 -13
  71. data/templates/tool_angular/src/app/app.component.ts +5 -4
  72. data/templates/tool_angular/src/app/custom-overlay-container.ts +2 -2
  73. data/templates/tool_angular/src/app/openc3-api.d.ts +1 -1
  74. data/templates/tool_angular/src/main.single-spa.ts +1 -1
  75. data/templates/tool_react/package.json +1 -0
  76. data/templates/tool_react/src/root.component.js +1 -1
  77. data/templates/tool_svelte/package.json +11 -9
  78. data/templates/tool_svelte/rollup.config.js +2 -0
  79. data/templates/tool_svelte/src/App.svelte +2 -2
  80. data/templates/tool_vue/eslint.config.mjs +68 -0
  81. data/templates/tool_vue/jsconfig.json +1 -1
  82. data/templates/tool_vue/package.json +26 -43
  83. data/templates/tool_vue/src/App.vue +3 -5
  84. data/templates/tool_vue/src/main.js +12 -23
  85. data/templates/tool_vue/src/router.js +19 -18
  86. data/templates/tool_vue/src/tools/tool_name/tool_name.vue +2 -2
  87. data/templates/tool_vue/vite.config.js +52 -0
  88. data/templates/widget/package.json +19 -26
  89. data/templates/widget/src/Widget.vue +13 -15
  90. data/templates/widget/vite.config.js +26 -0
  91. metadata +10 -41
  92. data/lib/openc3/core_ext/hash.rb +0 -40
  93. data/lib/openc3/core_ext/httpclient.rb +0 -11
  94. data/lib/openc3/interfaces/linc_interface.rb +0 -480
  95. data/lib/openc3/interfaces/protocols/override_protocol.rb +0 -4
  96. data/lib/openc3/microservices/critical_cmd_microservice.rb +0 -74
  97. data/lib/openc3/microservices/reaction_microservice.rb +0 -607
  98. data/lib/openc3/microservices/timeline_microservice.rb +0 -398
  99. data/lib/openc3/microservices/trigger_group_microservice.rb +0 -698
  100. data/lib/openc3/migrations/20230615000000_autonomic.rb +0 -86
  101. data/lib/openc3/migrations/20240915000000_activity_uuid.rb +0 -28
  102. data/lib/openc3/migrations/20241016000000_scope_critical_cmd.rb +0 -24
  103. data/lib/openc3/system/system_config.rb +0 -413
  104. data/templates/tool_svelte/src/services/api.js +0 -92
  105. data/templates/tool_svelte/src/services/axios.js +0 -85
  106. data/templates/tool_svelte/src/services/cable.js +0 -65
  107. data/templates/tool_svelte/src/services/config-parser.js +0 -198
  108. data/templates/tool_svelte/src/services/openc3-api.js +0 -606
  109. data/templates/tool_vue/.eslintrc.js +0 -43
  110. data/templates/tool_vue/babel.config.json +0 -11
  111. data/templates/tool_vue/vue.config.js +0 -38
  112. data/templates/widget/.eslintrc.js +0 -43
  113. data/templates/widget/babel.config.json +0 -11
  114. data/templates/widget/vue.config.js +0 -28
  115. /data/templates/tool_vue/{.prettierrc.js → .prettierrc.cjs} +0 -0
  116. /data/templates/widget/{.prettierrc.js → .prettierrc.cjs} +0 -0
@@ -30,7 +30,6 @@ module OpenC3
30
30
  'get_target_list', # DEPRECATED
31
31
  'get_target',
32
32
  'get_target_interfaces',
33
- 'get_all_target_info', # DEPRECATED
34
33
  ])
35
34
 
36
35
  # Returns the list of all target names
@@ -71,34 +70,5 @@ module OpenC3
71
70
  end
72
71
  info
73
72
  end
74
-
75
- # DEPRECATED: Get information about all targets
76
- # Warning this call can take a long time with many defined packets
77
- #
78
- # @return [Array<Array<String, String, Numeric, Numeric>] Array of Arrays \[name, interface, cmd_cnt, tlm_cnt]
79
- def get_all_target_info(manual: false, scope: $openc3_scope, token: $openc3_token)
80
- authorize(permission: 'system', manual: manual, scope: scope, token: token)
81
- info = []
82
- get_target_names(scope: scope, token: token).each do |target_name|
83
- cmd_cnt = 0
84
- packets = TargetModel.packets(target_name, type: :CMD, scope: scope)
85
- packets.each do |packet|
86
- cmd_cnt += Topic.get_cnt("#{scope}__COMMAND__{#{target_name}}__#{packet['packet_name']}")
87
- end
88
- tlm_cnt = 0
89
- packets = TargetModel.packets(target_name, type: :TLM, scope: scope)
90
- packets.each do |packet|
91
- tlm_cnt += Topic.get_cnt("#{scope}__TELEMETRY__{#{target_name}}__#{packet['packet_name']}")
92
- end
93
- interface_names = []
94
- InterfaceModel.all(scope: scope).each do |_name, interface|
95
- if interface['target_names'].include? target_name
96
- interface_names << interface['name']
97
- end
98
- end
99
- info << [target_name, interface_names.join(","), cmd_cnt, tlm_cnt]
100
- end
101
- info
102
- end
103
73
  end
104
74
  end
@@ -248,7 +248,7 @@ module OpenC3
248
248
 
249
249
  # Verifies the indicated parameter in the config doesn't start or end
250
250
  # with an underscore, doesn't contain a double underscore or double bracket,
251
- # doesn't contain spaces and doesn't start with a close bracket.
251
+ # doesn't contain spaces, quotes or brackets.
252
252
  #
253
253
  # @param [Integer] index The index of the parameter to check
254
254
  def verify_parameter_naming(index, usage = "")
@@ -265,8 +265,11 @@ module OpenC3
265
265
  if param.include? ' '
266
266
  raise Error.new(self, "Parameter #{index} (#{param}) for #{@keyword} cannot contain a space (' ').", usage, @url)
267
267
  end
268
- if param.start_with?('}')
269
- raise Error.new(self, "Parameter #{index} (#{param}) for #{@keyword} cannot start with a close bracket ('}').", usage, @url)
268
+ if param.include?('"') or param.include?("'")
269
+ raise Error.new(self, "Parameter #{index} (#{param}) for #{@keyword} cannot contain a quote (' or \").", usage, @url)
270
+ end
271
+ if param.include?('{') or param.include?('}')
272
+ raise Error.new(self, "Parameter #{index} (#{param}) for #{@keyword} cannot contain a curly bracket ('{' or '}').", usage, @url)
270
273
  end
271
274
  end
272
275
 
@@ -24,22 +24,6 @@ require 'openc3/ext/array' if RUBY_ENGINE == 'ruby' and !ENV['OPENC3_NO_EXT']
24
24
 
25
25
  # OpenC3 specific additions to the Ruby Array class
26
26
  class Array
27
- # Redefine inspect to only print for small numbers of
28
- # items. Prevents exceptions taking forever to be raise with
29
- # large objects. See http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/105145
30
- alias old_inspect inspect
31
-
32
- # @param max_elements [Integer] The maximum number of elements in the array to
33
- # print out before simply displaying the array class and object id
34
- # @return [String] String representation of the array
35
- def inspect(max_elements = 10)
36
- if self.length <= max_elements
37
- old_inspect()
38
- else
39
- '#<' + self.class.to_s + ':' + self.object_id.to_s + '>'
40
- end
41
- end
42
-
43
27
  # @return [Array] Cloned array after all elements called to_f
44
28
  def clone_to_f
45
29
  new_array = self.class.new(0)
@@ -27,7 +27,6 @@ require 'openc3/core_ext/openc3_io'
27
27
  require 'openc3/core_ext/exception'
28
28
  require 'openc3/core_ext/faraday'
29
29
  require 'openc3/core_ext/file'
30
- require 'openc3/core_ext/hash'
31
30
  require 'openc3/core_ext/io'
32
31
  require 'openc3/core_ext/kernel'
33
32
  require 'openc3/core_ext/math'
@@ -0,0 +1,198 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2024 OpenC3, Inc.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is free software; you can modify and/or redistribute it
7
+ # under the terms of the GNU Affero General Public License
8
+ # as published by the Free Software Foundation; version 3 with
9
+ # attribution addendums as found in the LICENSE.txt
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # This file may also be used under the terms of a commercial license
17
+ # if purchased from OpenC3, Inc.
18
+
19
+ require 'openc3/interfaces/interface'
20
+ require 'openc3/config/config_parser'
21
+ require 'thread'
22
+ require 'listen'
23
+ require 'fileutils'
24
+
25
+ module OpenC3
26
+ class FileInterface < Interface
27
+ # @param command_write_folder [String] Folder to write command files to - Set to nil to disallow writes
28
+ # @param telemetry_read_folder [String] Folder to read telemetry files from - Set to nil to disallow reads
29
+ # @param telemetry_archive_folder [String] Folder to move read telemetry files to - Set to DELETE to delete files
30
+ # @param file_read_size [Integer] Number of bytes to read from the file at a time
31
+ # @param stored [Boolean] Whether to set stored flag on read telemetry
32
+ # @param protocol_type [String] Name of the protocol to use
33
+ # with this interface
34
+ # @param protocol_args [Array<String>] Arguments to pass to the protocol
35
+ def initialize(
36
+ command_write_folder,
37
+ telemetry_read_folder,
38
+ telemetry_archive_folder,
39
+ file_read_size = 65536,
40
+ stored = true,
41
+ protocol_type = nil,
42
+ *protocol_args
43
+ )
44
+ super()
45
+
46
+ @protocol_type = ConfigParser.handle_nil(protocol_type)
47
+ @protocol_args = protocol_args
48
+ if @protocol_type
49
+ protocol_class_name = protocol_type.to_s.capitalize << 'Protocol'
50
+ klass = OpenC3.require_class(protocol_class_name.class_name_to_filename)
51
+ add_protocol(klass, protocol_args, :PARAMS)
52
+ end
53
+
54
+ @command_write_folder = ConfigParser.handle_nil(command_write_folder)
55
+ @telemetry_read_folder = ConfigParser.handle_nil(telemetry_read_folder)
56
+ @telemetry_archive_folder = ConfigParser.handle_nil(telemetry_archive_folder)
57
+ @file_read_size = Integer(file_read_size)
58
+ @stored = ConfigParser.handle_true_false(stored)
59
+
60
+ @read_allowed = false unless @telemetry_read_folder
61
+ @write_allowed = false unless @command_write_folder
62
+ @write_raw_allowed = false unless @command_write_folder
63
+
64
+ @file = nil
65
+ @listener = nil
66
+ @connected = false
67
+ @extension = ".bin"
68
+ @label = "command"
69
+ @queue = Queue.new
70
+ @polling = false
71
+ @recursive = false
72
+ end
73
+
74
+ def connect
75
+ super()
76
+
77
+ if @telemetry_read_folder
78
+ @listener = Listen.to(@telemetry_read_folder, force_polling: @polling) do |modified, added, removed|
79
+ @queue << added if added
80
+ end
81
+ @listener.start # starts a listener thread--does not block
82
+ end
83
+
84
+ @connected = true
85
+ end
86
+
87
+ def connected?
88
+ return @connected
89
+ end
90
+
91
+ def disconnect
92
+ @file.close if @file and not @file.closed?
93
+ @file = nil
94
+ @listener.stop if @listener
95
+ @listener = nil
96
+ @queue << nil
97
+ super()
98
+ @connected = false
99
+ end
100
+
101
+ def read_interface
102
+ while true
103
+ if @file
104
+ # Read more data from existing file
105
+ data = @file.read(@file_read_size)
106
+ if data and data.length > 0
107
+ read_interface_base(data, nil)
108
+ return data, nil
109
+ else
110
+ finish_file()
111
+ end
112
+ end
113
+
114
+ # Find the next file to read
115
+ file = get_next_telemetry_file()
116
+ if file
117
+ @file = File.open(file, 'rb')
118
+ next
119
+ end
120
+
121
+ # Wait for a file to read
122
+ result = @queue.pop
123
+ return nil, nil unless result
124
+ end
125
+ end
126
+
127
+ def write_interface(data, extra = nil)
128
+ # Write this data into its own file
129
+ File.open(create_unique_filename(), 'wb') do |file|
130
+ file.write(data)
131
+ end
132
+
133
+ write_interface_base(data, extra)
134
+ return data, extra
135
+ end
136
+
137
+ def convert_data_to_packet(data, extra = nil)
138
+ packet = super(data, extra)
139
+ if packet and @stored
140
+ packet.stored = true
141
+ end
142
+ return packet
143
+ end
144
+
145
+ # Supported Options
146
+ # LABEL - Label to add to written files
147
+ # EXTENSION - Extension to add to written files
148
+ # (see Interface#set_option)
149
+ def set_option(option_name, option_values)
150
+ super(option_name, option_values)
151
+ case option_name.upcase
152
+ when 'LABEL'
153
+ @label = option_values[0]
154
+ when 'EXTENSION'
155
+ @extension = option_values[0]
156
+ when 'POLLING'
157
+ @polling = ConfigParser.handle_true_false(option_values[0])
158
+ when 'RECURSIVE'
159
+ @recursive = ConfigParser.handle_true_false(option_values[0])
160
+ end
161
+ end
162
+
163
+ def finish_file
164
+ path = @file.path
165
+ @file.close
166
+ @file = nil
167
+
168
+ # Archive (or DELETE) complete file
169
+ if @telemetry_archive_folder == "DELETE"
170
+ FileUtils.rm(path)
171
+ else
172
+ FileUtils.mv(path, @telemetry_archive_folder)
173
+ end
174
+ end
175
+
176
+ def get_next_telemetry_file
177
+ if @recursive
178
+ return Dir.glob("#{@telemetry_read_folder}/**/*").sort[0]
179
+ else
180
+ return Dir.glob("#{@telemetry_read_folder}/*").sort[0]
181
+ end
182
+ end
183
+
184
+ def create_unique_filename
185
+ # Create a filename that doesn't exist
186
+ attempt = nil
187
+ while true
188
+ filename = File.join(@command_write_folder, File.build_timestamped_filename([@label, attempt], @extension))
189
+ if File.exist?(filename)
190
+ attempt ||= 0
191
+ attempt += 1
192
+ else
193
+ return filename
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
@@ -29,7 +29,8 @@ module OpenC3
29
29
  # @param hostname [String] HTTP/HTTPS server to connect to
30
30
  # @param port [Integer] HTTP/HTTPS port
31
31
  # @param protocol [String] http or https
32
- def initialize(hostname, port = 80, protocol = 'http', write_timeout = 5, read_timeout = nil, connect_timeout = 5, include_request_in_response = false)
32
+ def initialize(hostname, port = 80, protocol = 'http', write_timeout = 5, read_timeout = nil,
33
+ connect_timeout = 5, include_request_in_response = false)
33
34
  super()
34
35
  @hostname = hostname
35
36
  @port = Integer(port)
@@ -46,7 +47,6 @@ module OpenC3
46
47
  @connect_timeout = ConfigParser.handle_nil(connect_timeout)
47
48
  @connect_timeout = Float(@connect_timeout) if @connect_timeout
48
49
  @include_request_in_response = ConfigParser.handle_true_false(include_request_in_response)
49
-
50
50
  @response_queue = Queue.new
51
51
  end
52
52
 
@@ -72,6 +72,8 @@ module OpenC3
72
72
  super()
73
73
  end
74
74
 
75
+ # Whether the interface is connected to its target(s)
76
+ # But in this case it is basically whether connect was called
75
77
  def connected?
76
78
  if @http
77
79
  return true
@@ -84,25 +86,32 @@ module OpenC3
84
86
  def disconnect
85
87
  @http.close if @http
86
88
  @http = nil
89
+ # Clear the response queue
87
90
  while @response_queue.length > 0
88
91
  @response_queue.pop
89
92
  end
90
93
  super()
94
+ # Push nil to cause read_interface to return nil
91
95
  @response_queue.push(nil)
92
96
  end
93
97
 
94
- # Reads from the socket if the read_port is defined
95
- def read_interface
96
- # Get the Faraday Response
97
- data, extra = @response_queue.pop
98
- return nil if data.nil?
99
-
100
- read_interface_base(data, extra)
98
+ # Called to convert a packet into a data buffer. Write protocols then
99
+ # potentially modify the data in their write_data methods. Finally
100
+ # write_interface is called to send the data to the target.
101
+ #
102
+ # @param packet [Packet] Packet to extract data from
103
+ # @return data, extra
104
+ def convert_packet_to_data(packet)
105
+ extra = packet.extra
106
+ extra ||= {}
107
+ data = packet.buffer(true) # Copy buffer so logged command isn't modified
108
+ extra['HTTP_URI'] = URI("#{@url}#{packet.read('HTTP_PATH')}").to_s
109
+ # Store the target name for use in identifying the response
110
+ extra['HTTP_REQUEST_TARGET_NAME'] = packet.target_name
101
111
  return data, extra
102
112
  end
103
113
 
104
- # Writes to the socket
105
- # @param data [Hash] For the HTTP Interface, data is a hash with the needed request info
114
+ # Calls the appropriate HTTP method using Faraday
106
115
  def write_interface(data, extra = nil)
107
116
  extra ||= {}
108
117
  queries = extra['HTTP_QUERIES']
@@ -115,7 +124,10 @@ module OpenC3
115
124
  resp = nil
116
125
  case method
117
126
  when 'get'
118
- resp = @http.get(uri, queries, headers)
127
+ resp = @http.get(uri) do |req|
128
+ req.params = queries
129
+ req.headers = headers
130
+ end
119
131
  when 'put'
120
132
  resp = @http.put(uri) do |req|
121
133
  req.params = queries
@@ -123,19 +135,27 @@ module OpenC3
123
135
  req.body = data
124
136
  end
125
137
  when 'delete'
126
- resp = @http.delete(uri, queries, headers)
127
- else # 'post'
138
+ resp = @http.delete(uri) do |req|
139
+ req.params = queries
140
+ req.headers = headers
141
+ end
142
+ when 'post'
128
143
  resp = @http.post(uri) do |req|
129
144
  req.params = queries
130
145
  req.headers = headers
131
146
  req.body = data
132
147
  end
148
+ else
149
+ raise "Unsupported HTTP Method: #{method}"
133
150
  end
134
151
 
135
152
  # Normalize Response into simple hash
136
153
  response_data = nil
137
154
  response_extra = {}
138
155
  if resp
156
+ # We store the request data and extra under HTTP_REQUEST
157
+ # This can optionally be returned in the response
158
+ # but is also used to identify the response target
139
159
  response_extra['HTTP_REQUEST'] = [data, extra]
140
160
  if resp.headers and resp.headers.length > 0
141
161
  response_extra['HTTP_HEADERS'] = resp.headers
@@ -151,20 +171,50 @@ module OpenC3
151
171
  return data, extra
152
172
  end
153
173
 
174
+ # Returns the response data and extra from the interface
175
+ # which was queued up by the write_interface method.
176
+ # Read protocols can then potentially modify the data in their read_data methods.
177
+ # Then convert_data_to_packet is called to convert the data into a Packet object.
178
+ # Finally the read protocols read_packet methods are called.
179
+ def read_interface
180
+ # This blocks until a response is available
181
+ data, extra = @response_queue.pop
182
+ return nil if data.nil?
183
+
184
+ read_interface_base(data, extra)
185
+ return data, extra
186
+ end
187
+
154
188
  # Called to convert the read data into a OpenC3 Packet object
155
189
  #
156
190
  # @param data [String] Raw packet data
191
+ # @param extra [Hash] Contains the following keys:
192
+ # HTTP_HEADERS - Hash of response headers
193
+ # HTTP_STATUS - Integer response status code
194
+ # HTTP_REQUEST - [data, extra]
195
+ # where data is the request data and extra contains:
196
+ # HTTP_REQUEST_TARGET_NAME - String request target name
197
+ # HTTP_URI - String request URI based on HTTP_PATH
198
+ # HTTP_PATH - String request path
199
+ # HTTP_METHOD - String request method
200
+ # HTTP_PACKET - String response packet name
201
+ # HTTP_ERROR_PACKET - Optional string error packet name
202
+ # HTTP_QUERIES - Optional hash of request queries
203
+ # HTTP_HEADERS - Optional hash of request headers
157
204
  # @return [Packet] OpenC3 Packet with buffer filled with data
158
205
  def convert_data_to_packet(data, extra = nil)
159
206
  packet = Packet.new(nil, nil, :BIG_ENDIAN, nil, data.to_s)
160
207
  packet.accessor = HttpAccessor.new(packet)
161
- if extra
162
- # Identify the response
163
- request_target_name = extra['HTTP_REQUEST_TARGET_NAME']
208
+ # Grab the request extra set in the write_interface method
209
+ request_extra = extra['HTTP_REQUEST'][1] if extra and extra['HTTP_REQUEST']
210
+ if request_extra
211
+ # Identify the response target
212
+ request_target_name = request_extra['HTTP_REQUEST_TARGET_NAME']
164
213
  if request_target_name
165
214
  request_target_name = request_target_name.to_s.upcase
166
- response_packet_name = extra['HTTP_PACKET']
167
- error_packet_name = extra['HTTP_ERROR_PACKET']
215
+ response_packet_name = request_extra['HTTP_PACKET']
216
+ error_packet_name = request_extra['HTTP_ERROR_PACKET']
217
+ # HTTP_STATUS was set in the base extra
168
218
  status = extra['HTTP_STATUS'].to_i
169
219
  if status >= 300 and error_packet_name
170
220
  # Handle error special case response packet
@@ -177,30 +227,12 @@ module OpenC3
177
227
  end
178
228
  end
179
229
  end
180
-
181
- if not @include_request_in_response
230
+ unless @include_request_in_response
182
231
  extra.delete("HTTP_REQUEST")
183
232
  end
184
- extra.delete("HTTP_REQUEST_TARGET_NAME")
185
- extra.delete("HTTP_REQUEST_PACKET_NAME")
186
- packet.extra = extra
187
233
  end
188
-
234
+ packet.extra = extra
189
235
  return packet
190
236
  end
191
-
192
- # Called to convert a packet into the data to send
193
- #
194
- # @param packet [Packet] Packet to extract data from
195
- # @return data
196
- def convert_packet_to_data(packet)
197
- extra = packet.extra
198
- extra ||= {}
199
- data = packet.buffer(true) # Copy buffer so logged command isn't modified
200
- extra['HTTP_URI'] = URI("#{@url}#{packet.read('HTTP_PATH')}").to_s
201
- extra['HTTP_REQUEST_TARGET_NAME'] = packet.target_name
202
- extra['HTTP_REQUEST_PACKET_NAME'] = packet.packet_name
203
- return data, extra
204
- end
205
237
  end
206
238
  end
@@ -149,7 +149,6 @@ module OpenC3
149
149
  @request_queue.push(nil)
150
150
  end
151
151
 
152
- # Reads from the socket if the read_port is defined
153
152
  def read_interface
154
153
  # Get the Faraday Response
155
154
  data, extra = @request_queue.pop
@@ -159,8 +158,6 @@ module OpenC3
159
158
  return data, extra
160
159
  end
161
160
 
162
- # Writes to the socket
163
- # @param data [Hash] For the HTTP Interface, data is a hash with the needed request info
164
161
  def write_interface(_data, _extra = nil)
165
162
  raise "Commands cannot be sent to HttpServerInterface"
166
163
  end
@@ -188,10 +185,6 @@ module OpenC3
188
185
  return packet
189
186
  end
190
187
 
191
- # Called to convert a packet into the data to send
192
- #
193
- # @param packet [Packet] Packet to extract data from
194
- # @return data
195
188
  def convert_packet_to_data(_packet)
196
189
  raise "Commands cannot be sent to HttpServerInterface"
197
190
  end
@@ -499,6 +499,8 @@ module OpenC3
499
499
  def set_option(option_name, option_values)
500
500
  option_name_upcase = option_name.upcase
501
501
 
502
+ # PERIODIC_CMD is special because there could be more than 1 periodic command
503
+ # so we store them in an array for processing during connect()
502
504
  if option_name_upcase == 'PERIODIC_CMD'
503
505
  # OPTION PERIODIC_CMD LOG/DONT_LOG 1.0 "INST COLLECT with TYPE NORMAL"
504
506
  @options[option_name_upcase] ||= []
@@ -18,6 +18,7 @@
18
18
 
19
19
  # You can quickly setup an unauthenticated MQTT server in Docker with
20
20
  # docker run -it -p 1883:1883 eclipse-mosquitto:2.0.15 mosquitto -c /mosquitto-no-auth.conf
21
+ # You can also test against encrypted and authenticated servers at https://test.mosquitto.org/
21
22
 
22
23
  require 'openc3/interfaces/interface'
23
24
  require 'openc3/config/config_parser'
@@ -91,12 +92,13 @@ module OpenC3
91
92
  class MqttInterface < Interface
92
93
  # @param hostname [String] MQTT server to connect to
93
94
  # @param port [Integer] MQTT port
94
- # @param ssl [Boolean] Use SSL true/false
95
+ # @param ssl [Boolean] Whether to use SSL
95
96
  def initialize(hostname, port = 1883, ssl = false)
96
97
  super()
97
98
  @hostname = hostname
98
99
  @port = Integer(port)
99
100
  @ssl = ConfigParser.handle_true_false(ssl)
101
+ @ack_timeout = 5.0
100
102
  @username = nil
101
103
  @password = nil
102
104
  @cert = nil
@@ -130,14 +132,21 @@ module OpenC3
130
132
  @write_topics = []
131
133
  @read_topics = []
132
134
  @client = MQTT::Client.new
135
+ @client.ack_timeout = @ack_timeout
133
136
  @client.host = @hostname
134
137
  @client.port = @port
135
- @client.ssl = @ssl
136
138
  @client.username = @username if @username
137
139
  @client.password = @password if @password
138
- @client.cert = @cert if @cert
139
- @client.key = @key if @key
140
- @client.ca_file = @ca_file.path if @ca_file
140
+ @client.ssl = @ssl
141
+ if @cert and @key
142
+ @client.ssl = true
143
+ @client.cert_file = @cert.path
144
+ @client.key_file = @key.path
145
+ end
146
+ if @ca_file
147
+ @client.ssl = true
148
+ @client.ca_file = @ca_file.path
149
+ end
141
150
  @client.connect
142
151
  @read_packets_by_topic.each do |topic, _|
143
152
  Logger.info "#{@name}: Subscribing to #{topic}"
@@ -146,9 +155,7 @@ module OpenC3
146
155
  super()
147
156
  end
148
157
 
149
- # @return [Boolean] Whether the active ports (read and/or write) have
150
- # created sockets. Since UDP is connectionless, creation of the sockets
151
- # is used to determine connection.
158
+ # @return [Boolean] Whether the MQTT client is connected
152
159
  def connected?
153
160
  if @client
154
161
  return @client.connected?
@@ -159,8 +166,10 @@ module OpenC3
159
166
 
160
167
  # Disconnects the interface from its target(s)
161
168
  def disconnect
162
- @client.disconnect
163
- @client = nil
169
+ if @client
170
+ @client.disconnect
171
+ @client = nil
172
+ end
164
173
  super()
165
174
  end
166
175
 
@@ -188,12 +197,12 @@ module OpenC3
188
197
  super(packet)
189
198
  end
190
199
  else
191
- raise "Command packet #{packet.target_name} #{packet.packet_name} requires a META TOPIC or TOPICS"
200
+ raise "Command packet '#{packet.target_name} #{packet.packet_name}' requires a META TOPIC or TOPICS"
192
201
  end
193
202
  end
194
203
  end
195
204
 
196
- # Reads from the socket if the read_port is defined
205
+ # Reads from the client
197
206
  def read_interface
198
207
  topic, data = @client.get
199
208
  if data.nil? or data.length <= 0
@@ -209,7 +218,7 @@ module OpenC3
209
218
  return nil
210
219
  end
211
220
 
212
- # Writes to the socket
221
+ # Writes to the client
213
222
  # @param data [String] Raw packet data
214
223
  def write_interface(data, extra = nil)
215
224
  write_interface_base(data, extra)
@@ -228,14 +237,22 @@ module OpenC3
228
237
  def set_option(option_name, option_values)
229
238
  super(option_name, option_values)
230
239
  case option_name.upcase
240
+ when 'ACK_TIMEOUT'
241
+ @ack_timeout = Float(option_values[0])
231
242
  when 'USERNAME'
232
243
  @username = option_values[0]
233
244
  when 'PASSWORD'
234
245
  @password = option_values[0]
235
246
  when 'CERT'
236
- @cert = option_values[0]
247
+ # CERT must be given as a file
248
+ @cert = Tempfile.new('cert')
249
+ @cert.write(option_values[0])
250
+ @cert.close
237
251
  when 'KEY'
238
- @key = option_values[0]
252
+ # KEY must be given as a file
253
+ @key = Tempfile.new('key')
254
+ @key.write(option_values[0])
255
+ @key.close
239
256
  when 'CA_FILE'
240
257
  # CA_FILE must be given as a file
241
258
  @ca_file = Tempfile.new('ca_file')