openc3 5.19.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -7
  3. data/bin/openc3cli +12 -120
  4. data/bin/pipinstall +5 -3
  5. data/data/config/command_modifiers.yaml +17 -1
  6. data/data/config/interface_modifiers.yaml +21 -4
  7. data/data/config/item_modifiers.yaml +1 -1
  8. data/data/config/microservice.yaml +15 -2
  9. data/data/config/param_item_modifiers.yaml +1 -1
  10. data/data/config/parameter_modifiers.yaml +1 -1
  11. data/data/config/table_manager.yaml +2 -2
  12. data/data/config/target.yaml +11 -0
  13. data/data/config/telemetry_modifiers.yaml +17 -1
  14. data/data/config/tool.yaml +12 -0
  15. data/data/config/widgets.yaml +41 -17
  16. data/ext/openc3/ext/packet/packet.c +3 -0
  17. data/lib/openc3/accessors/form_accessor.rb +4 -3
  18. data/lib/openc3/accessors/html_accessor.rb +3 -3
  19. data/lib/openc3/accessors/http_accessor.rb +13 -13
  20. data/lib/openc3/accessors/xml_accessor.rb +16 -4
  21. data/lib/openc3/api/cmd_api.rb +4 -5
  22. data/lib/openc3/api/limits_api.rb +3 -3
  23. data/lib/openc3/api/target_api.rb +0 -30
  24. data/lib/openc3/api/tlm_api.rb +2 -1
  25. data/lib/openc3/bridge/bridge_config.rb +1 -2
  26. data/lib/openc3/ccsds/ccsds_parser.rb +12 -8
  27. data/lib/openc3/config/config_parser.rb +10 -3
  28. data/lib/openc3/conversions/bit_reverse_conversion.rb +1 -0
  29. data/lib/openc3/conversions/conversion.rb +5 -1
  30. data/lib/openc3/conversions/generic_conversion.rb +3 -8
  31. data/lib/openc3/conversions/object_read_conversion.rb +1 -8
  32. data/lib/openc3/conversions/polynomial_conversion.rb +3 -8
  33. data/lib/openc3/conversions/processor_conversion.rb +13 -11
  34. data/lib/openc3/conversions/segmented_polynomial_conversion.rb +3 -11
  35. data/lib/openc3/conversions/unix_time_conversion.rb +4 -7
  36. data/lib/openc3/conversions/unix_time_formatted_conversion.rb +4 -3
  37. data/lib/openc3/conversions/unix_time_seconds_conversion.rb +4 -3
  38. data/lib/openc3/core_ext/array.rb +0 -16
  39. data/lib/openc3/core_ext.rb +0 -1
  40. data/lib/openc3/interfaces/file_interface.rb +198 -0
  41. data/lib/openc3/interfaces/http_client_interface.rb +71 -39
  42. data/lib/openc3/interfaces/http_server_interface.rb +1 -9
  43. data/lib/openc3/interfaces/interface.rb +3 -2
  44. data/lib/openc3/interfaces/mqtt_interface.rb +32 -15
  45. data/lib/openc3/interfaces/mqtt_stream_interface.rb +19 -4
  46. data/lib/openc3/interfaces/protocols/crc_protocol.rb +7 -0
  47. data/lib/openc3/interfaces/serial_interface.rb +1 -0
  48. data/lib/openc3/interfaces/tcpip_server_interface.rb +1 -2
  49. data/lib/openc3/interfaces/udp_interface.rb +5 -3
  50. data/lib/openc3/interfaces.rb +2 -4
  51. data/lib/openc3/io/json_drb.rb +5 -0
  52. data/lib/openc3/io/json_rpc.rb +10 -9
  53. data/lib/openc3/io/udp_sockets.rb +7 -5
  54. data/lib/openc3/microservices/decom_microservice.rb +24 -7
  55. data/lib/openc3/microservices/interface_microservice.rb +65 -7
  56. data/lib/openc3/microservices/microservice.rb +1 -2
  57. data/lib/openc3/microservices/multi_microservice.rb +3 -3
  58. data/lib/openc3/migrations/20241208080000_no_critical_cmd.rb +31 -0
  59. data/lib/openc3/migrations/20241208080001_no_trigger_group.rb +46 -0
  60. data/lib/openc3/models/activity_model.rb +7 -3
  61. data/lib/openc3/models/cvt_model.rb +7 -1
  62. data/lib/openc3/models/interface_model.rb +9 -3
  63. data/lib/openc3/models/microservice_model.rb +8 -1
  64. data/lib/openc3/models/model.rb +1 -0
  65. data/lib/openc3/models/plugin_model.rb +11 -6
  66. data/lib/openc3/models/python_package_model.rb +10 -3
  67. data/lib/openc3/models/reaction_model.rb +14 -10
  68. data/lib/openc3/models/scope_model.rb +87 -25
  69. data/lib/openc3/models/target_model.rb +17 -1
  70. data/lib/openc3/models/timeline_model.rb +17 -5
  71. data/lib/openc3/models/tool_model.rb +15 -3
  72. data/lib/openc3/models/trigger_group_model.rb +6 -3
  73. data/lib/openc3/operators/microservice_operator.rb +10 -3
  74. data/lib/openc3/packets/commands.rb +17 -6
  75. data/lib/openc3/packets/limits.rb +0 -12
  76. data/lib/openc3/packets/packet.rb +10 -1
  77. data/lib/openc3/packets/packet_config.rb +34 -1
  78. data/lib/openc3/packets/packet_item.rb +30 -32
  79. data/lib/openc3/packets/structure_item.rb +2 -2
  80. data/lib/openc3/script/calendar.rb +1 -6
  81. data/lib/openc3/script/commands.rb +19 -13
  82. data/lib/openc3/script/critical_cmd.rb +91 -0
  83. data/lib/openc3/script/screen.rb +2 -2
  84. data/lib/openc3/script/script.rb +17 -10
  85. data/lib/openc3/script/web_socket_api.rb +5 -5
  86. data/lib/openc3/streams/mqtt_stream.rb +41 -33
  87. data/lib/openc3/streams/serial_stream.rb +27 -27
  88. data/lib/openc3/streams/stream.rb +17 -17
  89. data/lib/openc3/streams/tcpip_client_stream.rb +1 -1
  90. data/lib/openc3/streams/tcpip_socket_stream.rb +19 -19
  91. data/lib/openc3/system/system.rb +1 -1
  92. data/lib/openc3/system.rb +2 -3
  93. data/lib/openc3/tools/table_manager/table.rb +2 -2
  94. data/lib/openc3/tools/table_manager/table_parser.rb +1 -1
  95. data/lib/openc3/top_level.rb +9 -5
  96. data/lib/openc3/topics/command_decom_topic.rb +0 -7
  97. data/lib/openc3/topics/command_topic.rb +16 -0
  98. data/lib/openc3/topics/interface_topic.rb +2 -0
  99. data/lib/openc3/utilities/authentication.rb +7 -3
  100. data/lib/openc3/utilities/bucket_utilities.rb +1 -1
  101. data/lib/openc3/utilities/cli_generator.rb +0 -1
  102. data/lib/openc3/utilities/logger.rb +1 -0
  103. data/lib/openc3/utilities/store_queued.rb +1 -0
  104. data/lib/openc3/version.rb +6 -6
  105. data/templates/conversion/conversion.rb +2 -0
  106. data/templates/plugin/README.md +1 -1
  107. data/templates/target/targets/TARGET/lib/target.rb +1 -1
  108. data/templates/tool_angular/package.json +9 -9
  109. data/templates/tool_angular/src/app/app.component.html +4 -13
  110. data/templates/tool_angular/src/app/app.component.scss +5 -13
  111. data/templates/tool_angular/src/app/app.component.ts +5 -4
  112. data/templates/tool_angular/src/app/custom-overlay-container.ts +2 -2
  113. data/templates/tool_angular/src/app/openc3-api.d.ts +1 -1
  114. data/templates/tool_angular/src/main.single-spa.ts +1 -1
  115. data/templates/tool_react/package.json +1 -0
  116. data/templates/tool_react/src/root.component.js +1 -1
  117. data/templates/tool_svelte/build/smui.css +1 -1
  118. data/templates/tool_svelte/package.json +11 -9
  119. data/templates/tool_svelte/rollup.config.js +2 -0
  120. data/templates/tool_svelte/src/App.svelte +2 -2
  121. data/templates/tool_vue/eslint.config.mjs +68 -0
  122. data/templates/tool_vue/jsconfig.json +1 -1
  123. data/templates/tool_vue/package.json +26 -43
  124. data/templates/tool_vue/src/App.vue +3 -5
  125. data/templates/tool_vue/src/main.js +12 -23
  126. data/templates/tool_vue/src/router.js +19 -18
  127. data/templates/tool_vue/src/tools/tool_name/tool_name.vue +2 -2
  128. data/templates/tool_vue/vite.config.js +52 -0
  129. data/templates/widget/package.json +19 -26
  130. data/templates/widget/src/Widget.vue +13 -15
  131. data/templates/widget/vite.config.js +26 -0
  132. metadata +25 -39
  133. data/lib/openc3/core_ext/hash.rb +0 -40
  134. data/lib/openc3/core_ext/httpclient.rb +0 -11
  135. data/lib/openc3/interfaces/linc_interface.rb +0 -480
  136. data/lib/openc3/interfaces/protocols/override_protocol.rb +0 -4
  137. data/lib/openc3/microservices/reaction_microservice.rb +0 -607
  138. data/lib/openc3/microservices/timeline_microservice.rb +0 -400
  139. data/lib/openc3/microservices/trigger_group_microservice.rb +0 -698
  140. data/lib/openc3/migrations/20230615000000_autonomic.rb +0 -86
  141. data/lib/openc3/migrations/20240915000000_activity_uuid.rb +0 -28
  142. data/lib/openc3/system/system_config.rb +0 -413
  143. data/templates/tool_svelte/src/services/api.js +0 -92
  144. data/templates/tool_svelte/src/services/axios.js +0 -85
  145. data/templates/tool_svelte/src/services/cable.js +0 -65
  146. data/templates/tool_svelte/src/services/config-parser.js +0 -198
  147. data/templates/tool_svelte/src/services/openc3-api.js +0 -606
  148. data/templates/tool_vue/.eslintrc.js +0 -43
  149. data/templates/tool_vue/babel.config.json +0 -11
  150. data/templates/tool_vue/vue.config.js +0 -38
  151. data/templates/widget/.eslintrc.js +0 -43
  152. data/templates/widget/babel.config.json +0 -11
  153. data/templates/widget/vue.config.js +0 -28
  154. /data/templates/tool_vue/{.prettierrc.js → .prettierrc.cjs} +0 -0
  155. /data/templates/widget/{.prettierrc.js → .prettierrc.cjs} +0 -0
@@ -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
@@ -37,8 +37,7 @@ module OpenC3
37
37
  # (see Interface#set_option)
38
38
  def set_option(option_name, option_values)
39
39
  super(option_name, option_values)
40
- case option_name.upcase
41
- when 'LISTEN_ADDRESS'
40
+ if option_name.upcase == 'LISTEN_ADDRESS'
42
41
  @listen_address = option_values[0]
43
42
  end
44
43
  end
@@ -150,7 +149,6 @@ module OpenC3
150
149
  @request_queue.push(nil)
151
150
  end
152
151
 
153
- # Reads from the socket if the read_port is defined
154
152
  def read_interface
155
153
  # Get the Faraday Response
156
154
  data, extra = @request_queue.pop
@@ -160,8 +158,6 @@ module OpenC3
160
158
  return data, extra
161
159
  end
162
160
 
163
- # Writes to the socket
164
- # @param data [Hash] For the HTTP Interface, data is a hash with the needed request info
165
161
  def write_interface(_data, _extra = nil)
166
162
  raise "Commands cannot be sent to HttpServerInterface"
167
163
  end
@@ -189,10 +185,6 @@ module OpenC3
189
185
  return packet
190
186
  end
191
187
 
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
188
  def convert_packet_to_data(_packet)
197
189
  raise "Commands cannot be sent to HttpServerInterface"
198
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] ||= []
@@ -574,8 +576,7 @@ module OpenC3
574
576
  end
575
577
 
576
578
  def interface_cmd(cmd_name, *_cmd_args)
577
- case cmd_name
578
- when 'clear_counters'
579
+ if cmd_name == 'clear_counters'
579
580
  @write_queue_size = 0
580
581
  @read_queue_size = 0
581
582
  @bytes_written = 0
@@ -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')
@@ -16,14 +16,20 @@
16
16
  # This file may also be used under the terms of a commercial license
17
17
  # if purchased from OpenC3, Inc.
18
18
 
19
+ # You can quickly setup an unauthenticated MQTT server in Docker with
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/
22
+
19
23
  require 'openc3/interfaces/stream_interface'
20
24
  require 'openc3/streams/mqtt_stream'
25
+ require 'openc3/config/config_parser'
21
26
 
22
27
  module OpenC3
23
28
  class MqttStreamInterface < StreamInterface
24
29
  # @param hostname [String] MQTT server to connect to
25
30
  # @param port [Integer] MQTT port
26
- # @param ssl [Boolean] Use SSL true/false
31
+ # @param write_topic [String] MQTT publish topic
32
+ # @param read_topic [String] MQTT receive topic
27
33
  def initialize(hostname, port = 1883, ssl = false, write_topic = nil, read_topic = nil, protocol_type = nil, *protocol_args)
28
34
  super(protocol_type, protocol_args)
29
35
  @hostname = hostname
@@ -31,6 +37,7 @@ module OpenC3
31
37
  @ssl = ConfigParser.handle_true_false(ssl)
32
38
  @write_topic = ConfigParser.handle_nil(write_topic)
33
39
  @read_topic = ConfigParser.handle_nil(read_topic)
40
+ @ack_timeout = 5.0
34
41
  @username = nil
35
42
  @password = nil
36
43
  @cert = nil
@@ -47,7 +54,7 @@ module OpenC3
47
54
 
48
55
  # Creates a new {SerialStream} using the parameters passed in the constructor
49
56
  def connect
50
- @stream = MqttStream.new(@hostname, @port, @ssl, @write_topic, @read_topic)
57
+ @stream = MqttStream.new(@hostname, @port, @ssl, @write_topic, @read_topic, @ack_timeout)
51
58
  @stream.username = @username if @username
52
59
  @stream.password = @password if @password
53
60
  @stream.cert = @cert if @cert
@@ -66,14 +73,22 @@ module OpenC3
66
73
  def set_option(option_name, option_values)
67
74
  super(option_name, option_values)
68
75
  case option_name.upcase
76
+ when 'ACK_TIMEOUT'
77
+ @ack_timeout = Float(option_values[0])
69
78
  when 'USERNAME'
70
79
  @username = option_values[0]
71
80
  when 'PASSWORD'
72
81
  @password = option_values[0]
73
82
  when 'CERT'
74
- @cert = option_values[0]
83
+ # CERT must be given as a file
84
+ @cert = Tempfile.new('cert')
85
+ @cert.write(option_values[0])
86
+ @cert.close
75
87
  when 'KEY'
76
- @key = option_values[0]
88
+ # KEY must be given as a file
89
+ @key = Tempfile.new('key')
90
+ @key.write(option_values[0])
91
+ @key.close
77
92
  when 'CA_FILE'
78
93
  # CA_FILE must be given as a file
79
94
  @ca_file = Tempfile.new('ca_file')
@@ -121,6 +121,13 @@ module OpenC3
121
121
 
122
122
  @bit_size = bit_size.to_i
123
123
  case @bit_size
124
+ when 8
125
+ @pack = (@endianness == :BIG_ENDIAN) ? 'n' : 'v'
126
+ if args.empty?
127
+ @crc = Crc8.new
128
+ else
129
+ @crc = Crc8.new(*args)
130
+ end
124
131
  when 16
125
132
  @pack = (@endianness == :BIG_ENDIAN) ? 'n' : 'v'
126
133
  if args.empty?
@@ -98,6 +98,7 @@ module OpenC3
98
98
 
99
99
  # Supported Options
100
100
  # FLOW_CONTROL - Flow control method NONE or RTSCTS. Defaults to NONE
101
+ # DATA_BITS - Number of data bits 5, 6, 7, or 8. Defaults to 8
101
102
  def set_option(option_name, option_values)
102
103
  super(option_name, option_values)
103
104
  case option_name.upcase
@@ -292,8 +292,7 @@ module OpenC3
292
292
  # (see Interface#set_option)
293
293
  def set_option(option_name, option_values)
294
294
  super(option_name, option_values)
295
- case option_name.upcase
296
- when 'LISTEN_ADDRESS'
295
+ if option_name.upcase == 'LISTEN_ADDRESS'
297
296
  @listen_address = option_values[0]
298
297
  end
299
298
  end