openc3 5.19.0 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -7
- data/bin/openc3cli +12 -120
- data/bin/pipinstall +5 -3
- data/data/config/command_modifiers.yaml +17 -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 +41 -17
- data/ext/openc3/ext/packet/packet.c +3 -0
- 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/cmd_api.rb +4 -5
- data/lib/openc3/api/limits_api.rb +3 -3
- data/lib/openc3/api/target_api.rb +0 -30
- data/lib/openc3/api/tlm_api.rb +2 -1
- data/lib/openc3/bridge/bridge_config.rb +1 -2
- data/lib/openc3/ccsds/ccsds_parser.rb +12 -8
- data/lib/openc3/config/config_parser.rb +10 -3
- data/lib/openc3/conversions/bit_reverse_conversion.rb +1 -0
- data/lib/openc3/conversions/conversion.rb +5 -1
- data/lib/openc3/conversions/generic_conversion.rb +3 -8
- data/lib/openc3/conversions/object_read_conversion.rb +1 -8
- data/lib/openc3/conversions/polynomial_conversion.rb +3 -8
- data/lib/openc3/conversions/processor_conversion.rb +13 -11
- data/lib/openc3/conversions/segmented_polynomial_conversion.rb +3 -11
- data/lib/openc3/conversions/unix_time_conversion.rb +4 -7
- data/lib/openc3/conversions/unix_time_formatted_conversion.rb +4 -3
- data/lib/openc3/conversions/unix_time_seconds_conversion.rb +4 -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 +1 -9
- data/lib/openc3/interfaces/interface.rb +3 -2
- 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/tcpip_server_interface.rb +1 -2
- data/lib/openc3/interfaces/udp_interface.rb +5 -3
- data/lib/openc3/interfaces.rb +2 -4
- data/lib/openc3/io/json_drb.rb +5 -0
- data/lib/openc3/io/json_rpc.rb +10 -9
- data/lib/openc3/io/udp_sockets.rb +7 -5
- data/lib/openc3/microservices/decom_microservice.rb +24 -7
- data/lib/openc3/microservices/interface_microservice.rb +65 -7
- data/lib/openc3/microservices/microservice.rb +1 -2
- 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/activity_model.rb +7 -3
- data/lib/openc3/models/cvt_model.rb +7 -1
- data/lib/openc3/models/interface_model.rb +9 -3
- data/lib/openc3/models/microservice_model.rb +8 -1
- data/lib/openc3/models/model.rb +1 -0
- data/lib/openc3/models/plugin_model.rb +11 -6
- data/lib/openc3/models/python_package_model.rb +10 -3
- data/lib/openc3/models/reaction_model.rb +14 -10
- data/lib/openc3/models/scope_model.rb +87 -25
- 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 +10 -3
- data/lib/openc3/packets/commands.rb +17 -6
- data/lib/openc3/packets/limits.rb +0 -12
- data/lib/openc3/packets/packet.rb +10 -1
- data/lib/openc3/packets/packet_config.rb +34 -1
- data/lib/openc3/packets/packet_item.rb +30 -32
- data/lib/openc3/packets/structure_item.rb +2 -2
- data/lib/openc3/script/calendar.rb +1 -6
- data/lib/openc3/script/commands.rb +19 -13
- data/lib/openc3/script/critical_cmd.rb +91 -0
- data/lib/openc3/script/screen.rb +2 -2
- data/lib/openc3/script/script.rb +17 -10
- data/lib/openc3/script/web_socket_api.rb +5 -5
- 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 +9 -5
- data/lib/openc3/topics/command_decom_topic.rb +0 -7
- data/lib/openc3/topics/command_topic.rb +16 -0
- data/lib/openc3/topics/interface_topic.rb +2 -0
- data/lib/openc3/utilities/authentication.rb +7 -3
- data/lib/openc3/utilities/bucket_utilities.rb +1 -1
- data/lib/openc3/utilities/cli_generator.rb +0 -1
- data/lib/openc3/utilities/logger.rb +1 -0
- data/lib/openc3/utilities/store_queued.rb +1 -0
- data/lib/openc3/version.rb +6 -6
- data/templates/conversion/conversion.rb +2 -0
- data/templates/plugin/README.md +1 -1
- data/templates/target/targets/TARGET/lib/target.rb +1 -1
- data/templates/tool_angular/package.json +9 -9
- 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/build/smui.css +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 +25 -39
- 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/reaction_microservice.rb +0 -607
- data/lib/openc3/microservices/timeline_microservice.rb +0 -400
- 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/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
@@ -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
|
@@ -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
|
-
|
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
|
-
|
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]
|
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')
|
@@ -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
|
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
|
-
|
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
|
-
|
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
|
-
|
296
|
-
when 'LISTEN_ADDRESS'
|
295
|
+
if option_name.upcase == 'LISTEN_ADDRESS'
|
297
296
|
@listen_address = option_values[0]
|
298
297
|
end
|
299
298
|
end
|