openc3 5.20.0 → 6.0.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.
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 +6 -6
  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')