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.
- 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 +6 -6
- 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')
|