openc3 5.20.0 → 6.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/openc3cli +12 -120
- data/data/config/command_modifiers.yaml +13 -1
- data/data/config/interface_modifiers.yaml +21 -4
- data/data/config/item_modifiers.yaml +1 -1
- data/data/config/microservice.yaml +15 -2
- data/data/config/param_item_modifiers.yaml +1 -1
- data/data/config/parameter_modifiers.yaml +1 -1
- data/data/config/table_manager.yaml +2 -2
- data/data/config/target.yaml +11 -0
- data/data/config/telemetry_modifiers.yaml +17 -1
- data/data/config/tool.yaml +12 -0
- data/data/config/widgets.yaml +13 -17
- data/lib/openc3/accessors/form_accessor.rb +4 -3
- data/lib/openc3/accessors/html_accessor.rb +3 -3
- data/lib/openc3/accessors/http_accessor.rb +13 -13
- data/lib/openc3/accessors/xml_accessor.rb +16 -4
- data/lib/openc3/api/target_api.rb +0 -30
- data/lib/openc3/config/config_parser.rb +6 -3
- data/lib/openc3/core_ext/array.rb +0 -16
- data/lib/openc3/core_ext.rb +0 -1
- data/lib/openc3/interfaces/file_interface.rb +198 -0
- data/lib/openc3/interfaces/http_client_interface.rb +71 -39
- data/lib/openc3/interfaces/http_server_interface.rb +0 -7
- data/lib/openc3/interfaces/interface.rb +2 -0
- data/lib/openc3/interfaces/mqtt_interface.rb +32 -15
- data/lib/openc3/interfaces/mqtt_stream_interface.rb +19 -4
- data/lib/openc3/interfaces/protocols/crc_protocol.rb +7 -0
- data/lib/openc3/interfaces/serial_interface.rb +1 -0
- data/lib/openc3/interfaces.rb +2 -4
- data/lib/openc3/microservices/multi_microservice.rb +3 -3
- data/lib/openc3/migrations/20241208080000_no_critical_cmd.rb +31 -0
- data/lib/openc3/migrations/20241208080001_no_trigger_group.rb +46 -0
- data/lib/openc3/models/interface_model.rb +9 -3
- data/lib/openc3/models/microservice_model.rb +8 -1
- data/lib/openc3/models/plugin_model.rb +6 -1
- data/lib/openc3/models/python_package_model.rb +6 -1
- data/lib/openc3/models/reaction_model.rb +14 -10
- data/lib/openc3/models/scope_model.rb +60 -42
- data/lib/openc3/models/target_model.rb +17 -1
- data/lib/openc3/models/timeline_model.rb +17 -5
- data/lib/openc3/models/tool_model.rb +15 -3
- data/lib/openc3/models/trigger_group_model.rb +6 -3
- data/lib/openc3/operators/microservice_operator.rb +8 -0
- data/lib/openc3/packets/commands.rb +17 -6
- data/lib/openc3/packets/limits.rb +0 -12
- data/lib/openc3/packets/packet.rb +1 -1
- data/lib/openc3/packets/packet_item.rb +30 -36
- data/lib/openc3/packets/structure_item.rb +2 -2
- data/lib/openc3/script/script.rb +0 -10
- data/lib/openc3/script/web_socket_api.rb +2 -2
- data/lib/openc3/streams/mqtt_stream.rb +41 -33
- data/lib/openc3/streams/serial_stream.rb +27 -27
- data/lib/openc3/streams/stream.rb +17 -17
- data/lib/openc3/streams/tcpip_client_stream.rb +1 -1
- data/lib/openc3/streams/tcpip_socket_stream.rb +19 -19
- data/lib/openc3/system/system.rb +1 -1
- data/lib/openc3/system.rb +2 -3
- data/lib/openc3/tools/table_manager/table.rb +2 -2
- data/lib/openc3/tools/table_manager/table_parser.rb +1 -1
- data/lib/openc3/top_level.rb +0 -5
- data/lib/openc3/topics/command_decom_topic.rb +0 -7
- data/lib/openc3/utilities/bucket_utilities.rb +1 -1
- data/lib/openc3/utilities/cli_generator.rb +0 -1
- data/lib/openc3/version.rb +7 -7
- data/templates/plugin/README.md +1 -1
- data/templates/target/targets/TARGET/lib/target.rb +1 -1
- data/templates/tool_angular/package.json +8 -8
- data/templates/tool_angular/src/app/app.component.html +4 -13
- data/templates/tool_angular/src/app/app.component.scss +5 -13
- data/templates/tool_angular/src/app/app.component.ts +5 -4
- data/templates/tool_angular/src/app/custom-overlay-container.ts +2 -2
- data/templates/tool_angular/src/app/openc3-api.d.ts +1 -1
- data/templates/tool_angular/src/main.single-spa.ts +1 -1
- data/templates/tool_react/package.json +1 -0
- data/templates/tool_react/src/root.component.js +1 -1
- data/templates/tool_svelte/package.json +11 -9
- data/templates/tool_svelte/rollup.config.js +2 -0
- data/templates/tool_svelte/src/App.svelte +2 -2
- data/templates/tool_vue/eslint.config.mjs +68 -0
- data/templates/tool_vue/jsconfig.json +1 -1
- data/templates/tool_vue/package.json +26 -43
- data/templates/tool_vue/src/App.vue +3 -5
- data/templates/tool_vue/src/main.js +12 -23
- data/templates/tool_vue/src/router.js +19 -18
- data/templates/tool_vue/src/tools/tool_name/tool_name.vue +2 -2
- data/templates/tool_vue/vite.config.js +52 -0
- data/templates/widget/package.json +19 -26
- data/templates/widget/src/Widget.vue +13 -15
- data/templates/widget/vite.config.js +26 -0
- metadata +10 -41
- data/lib/openc3/core_ext/hash.rb +0 -40
- data/lib/openc3/core_ext/httpclient.rb +0 -11
- data/lib/openc3/interfaces/linc_interface.rb +0 -480
- data/lib/openc3/interfaces/protocols/override_protocol.rb +0 -4
- data/lib/openc3/microservices/critical_cmd_microservice.rb +0 -74
- data/lib/openc3/microservices/reaction_microservice.rb +0 -607
- data/lib/openc3/microservices/timeline_microservice.rb +0 -398
- data/lib/openc3/microservices/trigger_group_microservice.rb +0 -698
- data/lib/openc3/migrations/20230615000000_autonomic.rb +0 -86
- data/lib/openc3/migrations/20240915000000_activity_uuid.rb +0 -28
- data/lib/openc3/migrations/20241016000000_scope_critical_cmd.rb +0 -24
- data/lib/openc3/system/system_config.rb +0 -413
- data/templates/tool_svelte/src/services/api.js +0 -92
- data/templates/tool_svelte/src/services/axios.js +0 -85
- data/templates/tool_svelte/src/services/cable.js +0 -65
- data/templates/tool_svelte/src/services/config-parser.js +0 -198
- data/templates/tool_svelte/src/services/openc3-api.js +0 -606
- data/templates/tool_vue/.eslintrc.js +0 -43
- data/templates/tool_vue/babel.config.json +0 -11
- data/templates/tool_vue/vue.config.js +0 -38
- data/templates/widget/.eslintrc.js +0 -43
- data/templates/widget/babel.config.json +0 -11
- data/templates/widget/vue.config.js +0 -28
- /data/templates/tool_vue/{.prettierrc.js → .prettierrc.cjs} +0 -0
- /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
|
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.
|
269
|
-
raise Error.new(self, "Parameter #{index} (#{param}) for #{@keyword} cannot
|
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)
|
data/lib/openc3/core_ext.rb
CHANGED
@@ -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,
|
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
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
#
|
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
|
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
|
127
|
-
|
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
|
-
|
162
|
-
|
163
|
-
|
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 =
|
167
|
-
error_packet_name =
|
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]
|
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.
|
139
|
-
|
140
|
-
|
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
|
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
|
163
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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')
|