openc3 5.4.3.pre.beta0 → 5.5.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of openc3 might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/bin/openc3cli +58 -74
- data/data/config/item_modifiers.yaml +1 -1
- data/data/config/plugins.yaml +5 -0
- data/data/config/widgets.yaml +13 -11
- data/lib/openc3/api/cmd_api.rb +43 -17
- data/lib/openc3/api/tlm_api.rb +37 -4
- data/lib/openc3/bridge/bridge.rb +3 -3
- data/lib/openc3/bridge/bridge_config.rb +68 -20
- data/lib/openc3/interfaces/interface.rb +8 -0
- data/lib/openc3/microservices/decom_microservice.rb +0 -3
- data/lib/openc3/microservices/interface_microservice.rb +2 -0
- data/lib/openc3/microservices/reaction_microservice.rb +0 -1
- data/lib/openc3/models/cvt_model.rb +1 -2
- data/lib/openc3/models/metadata_model.rb +1 -1
- data/lib/openc3/models/microservice_model.rb +1 -1
- data/lib/openc3/models/note_model.rb +1 -1
- data/lib/openc3/models/plugin_model.rb +14 -7
- data/lib/openc3/models/timeline_model.rb +1 -1
- data/lib/openc3/models/trigger_group_model.rb +1 -1
- data/lib/openc3/packets/packet_item.rb +1 -1
- data/lib/openc3/script/script.rb +10 -0
- data/lib/openc3/script/storage.rb +5 -5
- data/lib/openc3/script/web_socket_api.rb +423 -0
- data/lib/openc3/streams/tcpip_client_stream.rb +1 -1
- data/lib/openc3/streams/web_socket_client_stream.rb +98 -0
- data/lib/openc3/tools/table_manager/table_manager_core.rb +1 -2
- data/lib/openc3/utilities/aws_bucket.rb +22 -4
- data/lib/openc3/utilities/bucket_file_cache.rb +3 -2
- data/lib/openc3/utilities/bucket_utilities.rb +1 -1
- data/lib/openc3/utilities/cli_generator.rb +210 -0
- data/lib/openc3/utilities/local_mode.rb +2 -2
- data/lib/openc3/utilities/target_file.rb +43 -32
- data/lib/openc3/version.rb +7 -7
- data/templates/conversion/conversion.rb +43 -0
- data/templates/limits_response/response.rb +51 -0
- data/templates/microservice/microservices/TEMPLATE/microservice.rb +62 -0
- data/templates/plugin/plugin.txt +1 -0
- metadata +49 -15
- data/templates/plugin-template/plugin.txt +0 -9
- /data/templates/{plugin-template → plugin}/LICENSE.txt +0 -0
- /data/templates/{plugin-template → plugin}/README.md +0 -0
- /data/templates/{plugin-template → plugin}/Rakefile +0 -0
- /data/templates/{plugin-template → plugin}/plugin.gemspec +0 -0
- /data/templates/{plugin-template → target}/targets/TARGET/cmd_tlm/cmd.txt +0 -0
- /data/templates/{plugin-template → target}/targets/TARGET/cmd_tlm/tlm.txt +0 -0
- /data/templates/{plugin-template → target}/targets/TARGET/lib/target.rb +0 -0
- /data/templates/{plugin-template → target}/targets/TARGET/procedures/procedure.rb +0 -0
- /data/templates/{plugin-template → target}/targets/TARGET/screens/status.txt +0 -0
- /data/templates/{plugin-template → target}/targets/TARGET/target.txt +0 -0
@@ -0,0 +1,423 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
# Copyright 2023 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/streams/web_socket_client_stream'
|
20
|
+
require 'openc3/utilities/authentication'
|
21
|
+
require 'openc3/io/json_rpc'
|
22
|
+
|
23
|
+
module OpenC3
|
24
|
+
# Base class - Do not use directly
|
25
|
+
class WebSocketApi
|
26
|
+
USER_AGENT = 'OpenC3 / v5 (ruby/openc3/lib/io/web_socket_api)'.freeze
|
27
|
+
|
28
|
+
# Create the WebsocketApi object. If a block is given will automatically connect/disconnect
|
29
|
+
def initialize(url:, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_scope, &block)
|
30
|
+
@scope = scope
|
31
|
+
@authentication = authentication.nil? ? generate_auth() : authentication
|
32
|
+
@url = url
|
33
|
+
@write_timeout = write_timeout
|
34
|
+
@read_timeout = read_timeout
|
35
|
+
@connect_timeout = connect_timeout
|
36
|
+
@subscribed = false
|
37
|
+
if block_given?
|
38
|
+
begin
|
39
|
+
connect()
|
40
|
+
yield self
|
41
|
+
ensure
|
42
|
+
disconnect()
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Read the next message without filtering / parsing
|
48
|
+
def read_message
|
49
|
+
subscribe()
|
50
|
+
return @stream.read
|
51
|
+
end
|
52
|
+
|
53
|
+
# Read the next message with json parsing, filtering, and timeout support
|
54
|
+
def read(ignore_protocol_messages: true, timeout: nil)
|
55
|
+
start_time = Time.now
|
56
|
+
while true
|
57
|
+
message = read_message()
|
58
|
+
if message
|
59
|
+
json_hash = JSON.parse(message, allow_nan: true, create_additions: true)
|
60
|
+
if ignore_protocol_messages
|
61
|
+
type = json_hash['type']
|
62
|
+
if type # ping, welcome, confirm_subscription, reject_subscription, disconnect
|
63
|
+
if type == 'disconnect'
|
64
|
+
if json_hash['reason'] == 'unauthorized'
|
65
|
+
raise "Unauthorized"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
if type == 'reject_subscription'
|
69
|
+
raise "Subscription Rejected"
|
70
|
+
end
|
71
|
+
if timeout
|
72
|
+
end_time = Time.now
|
73
|
+
if (start_time - end_time) > timeout
|
74
|
+
raise Timeout::Error, "No Data Timeout"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
if defined? RunningScript and RunningScript.instance
|
78
|
+
raise StopScript if RunningScript.instance.stop?
|
79
|
+
end
|
80
|
+
next
|
81
|
+
end
|
82
|
+
end
|
83
|
+
return json_hash['message']
|
84
|
+
end
|
85
|
+
return message
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Will subscribe to the channel based on @identifier
|
90
|
+
def subscribe
|
91
|
+
unless @subscribed
|
92
|
+
json_hash = {}
|
93
|
+
json_hash['command'] = 'subscribe'
|
94
|
+
json_hash['identifier'] = JSON.generate(@identifier)
|
95
|
+
@stream.write(JSON.generate(json_hash))
|
96
|
+
@subscribed = true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Will unsubscribe to the channel based on @identifier
|
101
|
+
def unsubscribe
|
102
|
+
if @subscribed
|
103
|
+
json_hash = {}
|
104
|
+
json_hash['command'] = 'unsubscribe'
|
105
|
+
json_hash['identifier'] = JSON.generate(@identifier)
|
106
|
+
@stream.write(JSON.generate(json_hash))
|
107
|
+
@subscribed = false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Send an ActionCable command
|
112
|
+
def write_action(data_hash)
|
113
|
+
json_hash = {}
|
114
|
+
json_hash['command'] = 'message'
|
115
|
+
json_hash['identifier'] = JSON.generate(@identifier)
|
116
|
+
json_hash['data'] = JSON.generate(data_hash)
|
117
|
+
write(JSON.generate(json_hash))
|
118
|
+
end
|
119
|
+
|
120
|
+
# General write to the websocket
|
121
|
+
def write(data)
|
122
|
+
subscribe()
|
123
|
+
@stream.write(data)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Connect to the websocket with authorization in query params
|
127
|
+
def connect
|
128
|
+
disconnect()
|
129
|
+
final_url = @url + "?scope=#{@scope}&authorization=#{@authentication.token}"
|
130
|
+
@stream = WebSocketClientStream.new(final_url, @write_timeout, @read_timeout, @connect_timeout)
|
131
|
+
@stream.headers = {
|
132
|
+
'Sec-WebSocket-Protocol' => 'actioncable-v1-json, actioncable-unsupported',
|
133
|
+
'User-Agent' => USER_AGENT
|
134
|
+
}
|
135
|
+
@stream.connect
|
136
|
+
end
|
137
|
+
|
138
|
+
# Are we connected?
|
139
|
+
def connected?
|
140
|
+
if @stream
|
141
|
+
@stream.connected?
|
142
|
+
else
|
143
|
+
false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Disconnect from the websocket and attempt to send unsubscribe message
|
148
|
+
def disconnect
|
149
|
+
if connected?()
|
150
|
+
begin
|
151
|
+
unsubscribe()
|
152
|
+
rescue
|
153
|
+
# Oh well, we tried
|
154
|
+
end
|
155
|
+
@stream.disconnect
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# private
|
160
|
+
|
161
|
+
# Generate the appropriate token for OpenC3
|
162
|
+
def generate_auth
|
163
|
+
if ENV['OPENC3_API_TOKEN'].nil? and ENV['OPENC3_API_USER'].nil?
|
164
|
+
if ENV['OPENC3_API_PASSWORD'] || ENV['OPENC3_SERVICE_PASSWORD']
|
165
|
+
return OpenC3Authentication.new()
|
166
|
+
else
|
167
|
+
raise "Environment Variables Not Set for Authentication"
|
168
|
+
end
|
169
|
+
else
|
170
|
+
return OpenC3KeycloakAuthentication.new(ENV['OPENC3_KEYCLOAK_URL'])
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Base class for cmd-tlm-api websockets - Do not use directly
|
176
|
+
class CmdTlmWebSocketApi < WebSocketApi
|
177
|
+
def initialize(url: nil, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_scope)
|
178
|
+
url = generate_url() unless url
|
179
|
+
super(url: url, write_timeout: write_timeout, read_timeout: read_timeout, connect_timeout: connect_timeout, authentication: authentication, scope: scope)
|
180
|
+
end
|
181
|
+
|
182
|
+
def generate_url
|
183
|
+
schema = ENV['OPENC3_API_SCHEMA'] || 'http'
|
184
|
+
hostname = ENV['OPENC3_API_HOSTNAME'] || (ENV['OPENC3_DEVEL'] ? '127.0.0.1' : 'openc3-cosmos-cmd-tlm-api')
|
185
|
+
port = ENV['OPENC3_API_PORT'] || '2901'
|
186
|
+
port = port.to_i
|
187
|
+
return "#{schema}://#{hostname}:#{port}/openc3-api/cable"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Base class for script-runner-api websockets - Do not use directly
|
192
|
+
class ScriptWebSocketApi < WebSocketApi
|
193
|
+
def initialize(url: nil, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_token)
|
194
|
+
url = generate_url() unless url
|
195
|
+
super(url: url, write_timeout: write_timeout, read_timeout: read_timeout, connect_timeout: connect_timeout, authentication: authentication, scope: scope)
|
196
|
+
end
|
197
|
+
|
198
|
+
def generate_url
|
199
|
+
schema = ENV['OPENC3_SCRIPT_API_SCHEMA'] || 'http'
|
200
|
+
hostname = ENV['OPENC3_SCRIPT_API_HOSTNAME'] || (ENV['OPENC3_DEVEL'] ? '127.0.0.1' : 'openc3-cosmos-script-runner-api')
|
201
|
+
port = ENV['OPENC3_SCRIPT_API_PORT'] || '2902'
|
202
|
+
port = port.to_i
|
203
|
+
return "#{schema}://#{hostname}:#{port}/script-api/cable"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Running Script WebSocket
|
208
|
+
class RunningScriptWebSocketApi < ScriptWebSocketApi
|
209
|
+
def initialize(id:, url: nil, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_scope)
|
210
|
+
@identifier = {
|
211
|
+
channel: "RunningScriptChannel",
|
212
|
+
id: id
|
213
|
+
}
|
214
|
+
super(url: url, write_timeout: write_timeout, read_timeout: read_timeout, connect_timeout: connect_timeout, authentication: authentication, scope: scope)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Log Messages WebSocket
|
219
|
+
class MessagesWebSocketApi < CmdTlmWebSocketApi
|
220
|
+
def initialize(history_count: 0, url: nil, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_scope)
|
221
|
+
@identifier = {
|
222
|
+
channel: "MessagesChannel",
|
223
|
+
history_count: history_count
|
224
|
+
}
|
225
|
+
super(url: url, write_timeout: write_timeout, read_timeout: read_timeout, connect_timeout: connect_timeout, authentication: authentication, scope: scope)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Notifications WebSocket
|
230
|
+
class NotificationsWebSocketApi < CmdTlmWebSocketApi
|
231
|
+
def initialize(history_count: 0, start_offset: nil, url: nil, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_scope)
|
232
|
+
@identifier = {
|
233
|
+
channel: "NotificationsChannel",
|
234
|
+
history_count: history_count,
|
235
|
+
start_offset: start_offset
|
236
|
+
}
|
237
|
+
super(url: url, write_timeout: write_timeout, read_timeout: read_timeout, connect_timeout: connect_timeout, authentication: authentication, scope: scope)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Autonomic Events WebSocket
|
242
|
+
class AutonomicEventsWebSocketApi < CmdTlmWebSocketApi
|
243
|
+
def initialize(history_count: 0, url: nil, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_scope)
|
244
|
+
@identifier = {
|
245
|
+
channel: "AutonomicEventsChannel",
|
246
|
+
history_count: history_count
|
247
|
+
}
|
248
|
+
super(url: url, write_timeout: write_timeout, read_timeout: read_timeout, connect_timeout: connect_timeout, authentication: authentication, scope: scope)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Calendar Events WebSocket
|
253
|
+
class CalendarEventsWebSocketApi < CmdTlmWebSocketApi
|
254
|
+
def initialize(history_count: 0, url: nil, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_scope)
|
255
|
+
@identifier = {
|
256
|
+
channel: "CalendarEventsChannel",
|
257
|
+
history_count: history_count
|
258
|
+
}
|
259
|
+
super(url: url, write_timeout: write_timeout, read_timeout: read_timeout, connect_timeout: connect_timeout, authentication: authentication, scope: scope)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# Config Events WebSocket
|
264
|
+
class ConfigEventsWebSocketApi < CmdTlmWebSocketApi
|
265
|
+
def initialize(history_count: 0, url: nil, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_scope)
|
266
|
+
@identifier = {
|
267
|
+
channel: "ConfigEventsChannel",
|
268
|
+
history_count: history_count
|
269
|
+
}
|
270
|
+
super(url: url, write_timeout: write_timeout, read_timeout: read_timeout, connect_timeout: connect_timeout, authentication: authentication, scope: scope)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Limits Events WebSocket
|
275
|
+
class LimitsEventsWebSocketApi < CmdTlmWebSocketApi
|
276
|
+
def initialize(history_count: 0, url: nil, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_scope)
|
277
|
+
@identifier = {
|
278
|
+
channel: "LimitsEventsChannel",
|
279
|
+
history_count: history_count
|
280
|
+
}
|
281
|
+
super(url: url, write_timeout: write_timeout, read_timeout: read_timeout, connect_timeout: connect_timeout, authentication: authentication, scope: scope)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Timeline WebSocket
|
286
|
+
class TimelineEventsWebSocketApi < CmdTlmWebSocketApi
|
287
|
+
def initialize(history_count: 0, url: nil, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_scope)
|
288
|
+
@identifier = {
|
289
|
+
channel: "TimelineEventsChannel",
|
290
|
+
history_count: history_count
|
291
|
+
}
|
292
|
+
super(url: url, write_timeout: write_timeout, read_timeout: read_timeout, connect_timeout: connect_timeout, authentication: authentication, scope: scope)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# Streaming API WebSocket
|
297
|
+
class StreamingWebSocketApi < CmdTlmWebSocketApi
|
298
|
+
def initialize(url: nil, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_scope)
|
299
|
+
@identifier = {
|
300
|
+
channel: "StreamingChannel"
|
301
|
+
}
|
302
|
+
super(url: url, write_timeout: write_timeout, read_timeout: read_timeout, connect_timeout: connect_timeout, authentication: authentication, scope: scope)
|
303
|
+
end
|
304
|
+
|
305
|
+
# Request to add data to the stream
|
306
|
+
#
|
307
|
+
# arguments:
|
308
|
+
# scope: scope name
|
309
|
+
# start_time: 64-bit nanoseconds from unix epoch - If not present then realtime
|
310
|
+
# end_time: 64-bit nanoseconds from unix epoch - If not present stream forever
|
311
|
+
# items: [ [ MODE__CMDORTLM__TARGET__PACKET__ITEM__VALUETYPE__REDUCEDTYPE, item_key] ]
|
312
|
+
# MODE - RAW, DECOM, REDUCED_MINUTE, REDUCED_HOUR, or REDUCED_DAY
|
313
|
+
# CMDORTLM - CMD or TLM
|
314
|
+
# TARGET - Target name
|
315
|
+
# PACKET - Packet name
|
316
|
+
# ITEM - Item Name
|
317
|
+
# VALUETYPE - RAW, CONVERTED, FORMATTED, or WITH_UNITS
|
318
|
+
# REDUCEDTYPE - MIN, MAX, AVG, STDDEV (only for reduced modes)
|
319
|
+
# item_key is an optional shortened name to return the data as
|
320
|
+
# packets: [ MODE__CMDORTLM__TARGET__PACKET__VALUETYPE ]
|
321
|
+
# MODE - RAW, DECOM, REDUCED_MINUTE, REDUCED_HOUR, or REDUCED_DAY
|
322
|
+
# CMDORTLM - CMD or TLM
|
323
|
+
# TARGET - Target name
|
324
|
+
# PACKET - Packet name
|
325
|
+
# VALUETYPE - RAW, CONVERTED, FORMATTED, WITH_UNITS, or PURE (pure means all types as stored in log)
|
326
|
+
#
|
327
|
+
def add(items: nil, packets: nil, start_time: nil, end_time: nil, scope: $openc3_scope)
|
328
|
+
data_hash = {}
|
329
|
+
data_hash['action'] = 'add'
|
330
|
+
if start_time
|
331
|
+
if Time === start_time
|
332
|
+
start_time = start_time.to_nsec_from_epoch
|
333
|
+
end
|
334
|
+
data_hash['start_time'] = start_time
|
335
|
+
end
|
336
|
+
if end_time
|
337
|
+
if Time === end_time
|
338
|
+
end_time = end_time.to_nsec_from_epoch
|
339
|
+
end
|
340
|
+
data_hash['end_time'] = end_time
|
341
|
+
end
|
342
|
+
data_hash['items'] = items if items
|
343
|
+
data_hash['packets'] = packets if packets
|
344
|
+
data_hash['scope'] = scope
|
345
|
+
data_hash['token'] = @authentication.token
|
346
|
+
write_action(data_hash)
|
347
|
+
end
|
348
|
+
|
349
|
+
# Request to remove data from the stream
|
350
|
+
#
|
351
|
+
# arguments:
|
352
|
+
# scope: scope name
|
353
|
+
# items: [ [ MODE__CMDORTLM__TARGET__PACKET__ITEM__VALUETYPE__REDUCEDTYPE] ]
|
354
|
+
# MODE - RAW, DECOM, REDUCED_MINUTE, REDUCED_HOUR, or REDUCED_DAY
|
355
|
+
# CMDORTLM - CMD or TLM
|
356
|
+
# TARGET - Target name
|
357
|
+
# PACKET - Packet name
|
358
|
+
# ITEM - Item Name
|
359
|
+
# VALUETYPE - RAW, CONVERTED, FORMATTED, or WITH_UNITS
|
360
|
+
# REDUCEDTYPE - MIN, MAX, AVG, STDDEV (only for reduced modes)
|
361
|
+
# packets: [ MODE__CMDORTLM__TARGET__PACKET__VALUETYPE ]
|
362
|
+
# MODE - RAW, DECOM, REDUCED_MINUTE, REDUCED_HOUR, or REDUCED_DAY
|
363
|
+
# CMDORTLM - CMD or TLM
|
364
|
+
# TARGET - Target name
|
365
|
+
# PACKET - Packet name
|
366
|
+
# VALUETYPE - RAW, CONVERTED, FORMATTED, WITH_UNITS, or PURE (pure means all types as stored in log)
|
367
|
+
#
|
368
|
+
def remove(items: nil, packets: nil, scope: $openc3_scope)
|
369
|
+
data_hash = {}
|
370
|
+
data_hash['action'] = 'remove'
|
371
|
+
data_hash['items'] = items if items
|
372
|
+
data_hash['packets'] = packets if packets
|
373
|
+
data_hash['scope'] = scope
|
374
|
+
data_hash['token'] = @authentication.token
|
375
|
+
write_action(data_hash)
|
376
|
+
end
|
377
|
+
|
378
|
+
# Convenience method to read all data until end marker is received.
|
379
|
+
# Warning: DATA IS STORED IN RAM. Do not use this with large queries
|
380
|
+
def self.read_all(items: nil, packets: nil, start_time: nil, end_time:, scope: $openc3_scope, timeout: nil)
|
381
|
+
read_all_start_time = Time.now
|
382
|
+
data = []
|
383
|
+
self.new do |api|
|
384
|
+
api.add(items: items, packets: packets, start_time: start_time, end_time: end_time, scope: scope)
|
385
|
+
while true
|
386
|
+
batch = api.read
|
387
|
+
if batch.length == 0
|
388
|
+
return data
|
389
|
+
else
|
390
|
+
data.concat(batch)
|
391
|
+
end
|
392
|
+
if timeout
|
393
|
+
if (Time.now - read_all_start_time) > timeout
|
394
|
+
return data
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
# # Example Use
|
404
|
+
# $openc3_scope = 'DEFAULT'
|
405
|
+
# ENV['OPENC3_API_HOSTNAME'] = 'localhost'
|
406
|
+
# ENV['OPENC3_API_PORT'] = '2900'
|
407
|
+
# ENV['OPENC3_SCRIPT_API_HOSTNAME'] = 'localhost'
|
408
|
+
# ENV['OPENC3_SCRIPT_API_PORT'] = '2900'
|
409
|
+
# ENV['OPENC3_API_PASSWORD'] = 'password'
|
410
|
+
#
|
411
|
+
# OpenC3::StreamingWebSocketApi.new do |api|
|
412
|
+
# api.add(items: ['DECOM__TLM__INST__HEALTH_STATUS__TEMP1__CONVERTED', 'DECOM__TLM__INST__HEALTH_STATUS__TEMP2__CONVERTED'])
|
413
|
+
# 5.times do
|
414
|
+
# puts api.read
|
415
|
+
# end
|
416
|
+
# api.remove(items: ['DECOM__TLM__INST__HEALTH_STATUS__TEMP1__CONVERTED'])
|
417
|
+
# 5.times do
|
418
|
+
# puts api.read
|
419
|
+
# end
|
420
|
+
# end
|
421
|
+
#
|
422
|
+
# # Warning this saves all data to RAM. Do not use for large queries
|
423
|
+
# data = OpenC3::StreamingWebSocketApi.read_all(items: ['DECOM__TLM__INST__HEALTH_STATUS__TEMP1__CONVERTED', 'DECOM__TLM__INST__HEALTH_STATUS__TEMP2__CONVERTED'], end_time: Time.now + 30)
|
@@ -17,7 +17,7 @@
|
|
17
17
|
# All changes Copyright 2022, OpenC3, Inc.
|
18
18
|
# All Rights Reserved
|
19
19
|
#
|
20
|
-
# This file may also be used under the terms of a commercial license
|
20
|
+
# This file may also be used under the terms of a commercial license
|
21
21
|
# if purchased from OpenC3, Inc.
|
22
22
|
|
23
23
|
require 'socket'
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
# Copyright 2023 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 'openssl'
|
20
|
+
require 'openc3/streams/tcpip_client_stream'
|
21
|
+
require 'websocket'
|
22
|
+
require 'uri'
|
23
|
+
|
24
|
+
module OpenC3
|
25
|
+
class WebSocketClientStream < TcpipClientStream
|
26
|
+
attr_accessor :headers
|
27
|
+
|
28
|
+
# @param url [String] The host to connect to
|
29
|
+
# @param write_timeout (see TcpipSocketStream#initialize)
|
30
|
+
# @param read_timeout (see TcpipSocketStream#initialize)
|
31
|
+
# @param connect_timeout (see TcpipClientStream#initialize)
|
32
|
+
def initialize(url, write_timeout, read_timeout, connect_timeout = 5.0)
|
33
|
+
@url = url
|
34
|
+
@uri = URI.parse @url
|
35
|
+
port = ((@uri.scheme == 'wss' or @uri.scheme == 'https') ? 443 : 80)
|
36
|
+
port = @uri.port if @uri.port
|
37
|
+
super(@uri.host, port, port, write_timeout, read_timeout, connect_timeout)
|
38
|
+
if ['https', 'wss'].include? @uri.scheme
|
39
|
+
socket = ::OpenSSL::SSL::SSLSocket.new(@write_socket)
|
40
|
+
socket.sync_close = true
|
41
|
+
socket.hostname = @uri.host
|
42
|
+
@write_socket = socket
|
43
|
+
@read_socket = socket
|
44
|
+
end
|
45
|
+
@headers = {}
|
46
|
+
end
|
47
|
+
|
48
|
+
def connect
|
49
|
+
super()
|
50
|
+
@handshake = ::WebSocket::Handshake::Client.new(:url => @url, :headers => @headers)
|
51
|
+
@frame = ::WebSocket::Frame::Incoming::Client.new
|
52
|
+
@handshaked = false
|
53
|
+
@write_socket.write(@handshake.to_s)
|
54
|
+
read() # This should wait for the handshake
|
55
|
+
return true
|
56
|
+
end
|
57
|
+
|
58
|
+
def read
|
59
|
+
while true
|
60
|
+
if @handshaked
|
61
|
+
msg = @frame.next
|
62
|
+
return msg.data if msg
|
63
|
+
end
|
64
|
+
|
65
|
+
data = super()
|
66
|
+
return data if data.length <= 0
|
67
|
+
|
68
|
+
if @handshaked
|
69
|
+
@frame << data
|
70
|
+
msg = @frame.next
|
71
|
+
return msg.data if msg
|
72
|
+
else
|
73
|
+
index = 0
|
74
|
+
chars = ""
|
75
|
+
data.each_char do |char|
|
76
|
+
@handshake << char
|
77
|
+
chars << char
|
78
|
+
index += 1
|
79
|
+
if @handshake.finished?
|
80
|
+
@handshaked = true
|
81
|
+
break
|
82
|
+
end
|
83
|
+
end
|
84
|
+
if @handshaked
|
85
|
+
data = data[index..-1]
|
86
|
+
@frame << data
|
87
|
+
return
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def write(data, type: :text)
|
94
|
+
frame = ::WebSocket::Frame::Outgoing::Client.new(:data => data, :type => type, :version => @handshake.version)
|
95
|
+
super(frame.to_s)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -17,7 +17,7 @@
|
|
17
17
|
# All changes Copyright 2022, OpenC3, Inc.
|
18
18
|
# All Rights Reserved
|
19
19
|
#
|
20
|
-
# This file may also be used under the terms of a commercial license
|
20
|
+
# This file may also be used under the terms of a commercial license
|
21
21
|
# if purchased from OpenC3, Inc.
|
22
22
|
|
23
23
|
require 'openc3'
|
@@ -148,7 +148,6 @@ module OpenC3
|
|
148
148
|
}
|
149
149
|
col = 0
|
150
150
|
row = 0
|
151
|
-
num_cols = table.num_columns
|
152
151
|
table.sorted_items.each_with_index do |item, index|
|
153
152
|
next if item.hidden
|
154
153
|
if table.num_columns == 1
|
@@ -111,7 +111,12 @@ module OpenC3
|
|
111
111
|
token = nil
|
112
112
|
result = []
|
113
113
|
while true
|
114
|
-
resp = @client.list_objects_v2(
|
114
|
+
resp = @client.list_objects_v2({
|
115
|
+
bucket: bucket,
|
116
|
+
max_keys: max_request,
|
117
|
+
prefix: prefix,
|
118
|
+
continuation_token: token
|
119
|
+
})
|
115
120
|
result.concat(resp.contents)
|
116
121
|
break if result.length >= max_total
|
117
122
|
break unless resp.is_truncated
|
@@ -119,12 +124,12 @@ module OpenC3
|
|
119
124
|
end
|
120
125
|
# Array of objects with key and size methods
|
121
126
|
result
|
122
|
-
rescue Aws::S3::Errors::NoSuchBucket
|
127
|
+
rescue Aws::S3::Errors::NoSuchBucket
|
123
128
|
raise NotFound, "Bucket '#{bucket}' does not exist."
|
124
129
|
end
|
125
130
|
|
126
131
|
# Lists the files under a specified path
|
127
|
-
def list_files(bucket:, path:, only_directories: false)
|
132
|
+
def list_files(bucket:, path:, only_directories: false, include_head: false)
|
128
133
|
# Trailing slash is important in AWS S3 when listing files
|
129
134
|
# See https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Types/ListObjectsV2Output.html#common_prefixes-instance_method
|
130
135
|
if path[-1] != '/'
|
@@ -158,6 +163,9 @@ module OpenC3
|
|
158
163
|
item['name'] = aws_item.key.split('/')[-1]
|
159
164
|
item['modified'] = aws_item.last_modified
|
160
165
|
item['size'] = aws_item.size
|
166
|
+
if include_head
|
167
|
+
item['head'] = head_object(bucket: bucket, key: aws_item.key)
|
168
|
+
end
|
161
169
|
files << item
|
162
170
|
end
|
163
171
|
result = [dirs, files]
|
@@ -166,10 +174,20 @@ module OpenC3
|
|
166
174
|
token = resp.next_continuation_token
|
167
175
|
end
|
168
176
|
result
|
169
|
-
rescue Aws::S3::Errors::NoSuchBucket
|
177
|
+
rescue Aws::S3::Errors::NoSuchBucket
|
170
178
|
raise NotFound, "Bucket '#{bucket}' does not exist."
|
171
179
|
end
|
172
180
|
|
181
|
+
# get metadata for a specific object
|
182
|
+
def head_object(bucket:, key:)
|
183
|
+
head = @client.head_object({
|
184
|
+
bucket: bucket,
|
185
|
+
key: key
|
186
|
+
})
|
187
|
+
rescue Aws::S3::Errors::NotFound => error
|
188
|
+
raise NotFound, "Object: #{bucket}/#{key}"
|
189
|
+
end
|
190
|
+
|
173
191
|
# put_object fires off the request to store but does not confirm
|
174
192
|
def put_object(bucket:, key:, body:, content_type: nil, cache_control: nil, metadata: nil)
|
175
193
|
@client.put_object(bucket: bucket, key: key, body: body,
|
@@ -57,10 +57,10 @@ class BucketFile
|
|
57
57
|
cmd_or_tlm = path_split[2].to_s.upcase
|
58
58
|
target_name = path_split[3].to_s.upcase
|
59
59
|
if stream_mode == 'RAW'
|
60
|
-
type = (
|
60
|
+
type = (cmd_or_tlm == 'CMD') ? 'COMMAND' : 'TELEMETRY'
|
61
61
|
else
|
62
62
|
if stream_mode == 'DECOM'
|
63
|
-
type = (
|
63
|
+
type = (cmd_or_tlm == 'CMD') ? 'DECOMCMD' : 'DECOM'
|
64
64
|
else
|
65
65
|
type = stream_mode # REDUCED_MINUTE, REDUCED_HOUR, or REDUCED_DAY
|
66
66
|
end
|
@@ -156,6 +156,7 @@ class BucketFileCache
|
|
156
156
|
@current_disk_usage = 0
|
157
157
|
@queued_bucket_files = []
|
158
158
|
@bucket_file_hash = {}
|
159
|
+
bucket_file = nil
|
159
160
|
|
160
161
|
@thread = Thread.new do
|
161
162
|
client = OpenC3::Bucket.getClient()
|
@@ -162,7 +162,7 @@ module OpenC3
|
|
162
162
|
|
163
163
|
def self.get_file_times(bucket_path)
|
164
164
|
basename = File.basename(bucket_path)
|
165
|
-
file_start_timestamp, file_end_timestamp,
|
165
|
+
file_start_timestamp, file_end_timestamp, _ = basename.split("__")
|
166
166
|
file_start_time = DateTime.strptime(file_start_timestamp, FILE_TIMESTAMP_FORMAT).to_time
|
167
167
|
file_end_time = DateTime.strptime(file_end_timestamp, FILE_TIMESTAMP_FORMAT).to_time
|
168
168
|
return file_start_time, file_end_time
|