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.

Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/bin/openc3cli +58 -74
  3. data/data/config/item_modifiers.yaml +1 -1
  4. data/data/config/plugins.yaml +5 -0
  5. data/data/config/widgets.yaml +13 -11
  6. data/lib/openc3/api/cmd_api.rb +43 -17
  7. data/lib/openc3/api/tlm_api.rb +37 -4
  8. data/lib/openc3/bridge/bridge.rb +3 -3
  9. data/lib/openc3/bridge/bridge_config.rb +68 -20
  10. data/lib/openc3/interfaces/interface.rb +8 -0
  11. data/lib/openc3/microservices/decom_microservice.rb +0 -3
  12. data/lib/openc3/microservices/interface_microservice.rb +2 -0
  13. data/lib/openc3/microservices/reaction_microservice.rb +0 -1
  14. data/lib/openc3/models/cvt_model.rb +1 -2
  15. data/lib/openc3/models/metadata_model.rb +1 -1
  16. data/lib/openc3/models/microservice_model.rb +1 -1
  17. data/lib/openc3/models/note_model.rb +1 -1
  18. data/lib/openc3/models/plugin_model.rb +14 -7
  19. data/lib/openc3/models/timeline_model.rb +1 -1
  20. data/lib/openc3/models/trigger_group_model.rb +1 -1
  21. data/lib/openc3/packets/packet_item.rb +1 -1
  22. data/lib/openc3/script/script.rb +10 -0
  23. data/lib/openc3/script/storage.rb +5 -5
  24. data/lib/openc3/script/web_socket_api.rb +423 -0
  25. data/lib/openc3/streams/tcpip_client_stream.rb +1 -1
  26. data/lib/openc3/streams/web_socket_client_stream.rb +98 -0
  27. data/lib/openc3/tools/table_manager/table_manager_core.rb +1 -2
  28. data/lib/openc3/utilities/aws_bucket.rb +22 -4
  29. data/lib/openc3/utilities/bucket_file_cache.rb +3 -2
  30. data/lib/openc3/utilities/bucket_utilities.rb +1 -1
  31. data/lib/openc3/utilities/cli_generator.rb +210 -0
  32. data/lib/openc3/utilities/local_mode.rb +2 -2
  33. data/lib/openc3/utilities/target_file.rb +43 -32
  34. data/lib/openc3/version.rb +7 -7
  35. data/templates/conversion/conversion.rb +43 -0
  36. data/templates/limits_response/response.rb +51 -0
  37. data/templates/microservice/microservices/TEMPLATE/microservice.rb +62 -0
  38. data/templates/plugin/plugin.txt +1 -0
  39. metadata +49 -15
  40. data/templates/plugin-template/plugin.txt +0 -9
  41. /data/templates/{plugin-template → plugin}/LICENSE.txt +0 -0
  42. /data/templates/{plugin-template → plugin}/README.md +0 -0
  43. /data/templates/{plugin-template → plugin}/Rakefile +0 -0
  44. /data/templates/{plugin-template → plugin}/plugin.gemspec +0 -0
  45. /data/templates/{plugin-template → target}/targets/TARGET/cmd_tlm/cmd.txt +0 -0
  46. /data/templates/{plugin-template → target}/targets/TARGET/cmd_tlm/tlm.txt +0 -0
  47. /data/templates/{plugin-template → target}/targets/TARGET/lib/target.rb +0 -0
  48. /data/templates/{plugin-template → target}/targets/TARGET/procedures/procedure.rb +0 -0
  49. /data/templates/{plugin-template → target}/targets/TARGET/screens/status.txt +0 -0
  50. /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(bucket: bucket, prefix: prefix, max_keys: max_request)
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 => error
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 => error
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 = (@cmd_or_tlm == 'CMD') ? 'COMMAND' : 'TELEMETRY'
60
+ type = (cmd_or_tlm == 'CMD') ? 'COMMAND' : 'TELEMETRY'
61
61
  else
62
62
  if stream_mode == 'DECOM'
63
- type = (@cmd_or_tlm == 'CMD') ? 'DECOMCMD' : 'DECOM'
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, other = basename.split("__")
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