openc3 5.4.3.pre.beta0 → 5.5.0.pre.beta0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 859879129f5646a4e8fd10a916e1e88859a7b5ed178fe2983f42df4e7e8c1a52
4
- data.tar.gz: 745a188c5e9277e0bb793207f383f85d6ec6d571d41d6115530f6f78bd671817
3
+ metadata.gz: 47eef747a6f82da0e4cdf93863f00357e07be1002d58762c4345c611e53da9a5
4
+ data.tar.gz: 6b6b804cac37eab03765a619aedd6ec4e6d4e8a9dfe5048507d23bd9c0987542
5
5
  SHA512:
6
- metadata.gz: '0582ba269983955ae39c8d0c941666d2fcc65811416e3c1ae7026811fa00d9699ecffc09e195909f30cac3c8571f6b83889349fed9c96ec89c95091aff088a61'
7
- data.tar.gz: a9c9c554d6595cd7718dc58b1e2e07e0ff9a440cd6545674b47250a3542164a41d27bad1bf237c8c121f170dd04380eeaa125769abc9423c0de2e610578f9799
6
+ metadata.gz: 9f862cfdc31ded71fe8eaa59f8771ccb88d45b0363fa17636c6ed8a0c1cfcb30343f4850b57fba89dc9fd221e9a70cb7af7524081bb4d7f24e8d263c816b3879
7
+ data.tar.gz: 3604545c30b970d8ed6e6c0bf69c8d6ba0eeffefd3cb0c043d65ea140dfb15936bcd3d9ffb94bdb21493d40c05213838bdf7df215ee12597e41275cc903d16ee
@@ -31,6 +31,7 @@ require 'openc3/script/limits'
31
31
  require 'openc3/script/exceptions'
32
32
  require 'openc3/script/script_runner'
33
33
  require 'openc3/script/storage'
34
+ require 'openc3/script/web_socket_api'
34
35
  require 'openc3/utilities/authentication'
35
36
 
36
37
  $api_server = nil
@@ -99,6 +100,15 @@ module OpenC3
99
100
  # NOOP
100
101
  end
101
102
 
103
+ def openc3_script_sleep(sleep_time = nil)
104
+ if sleep_time
105
+ sleep(sleep_time)
106
+ else
107
+ prompt("Press any key to continue...")
108
+ end
109
+ return false
110
+ end
111
+
102
112
  def ask_string(question, blank_or_default = false, password = false)
103
113
  answer = ''
104
114
  default = ''
@@ -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,99 @@
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
+ start_time = Time.now
55
+ read() # This should wait for the handshake
56
+ return true
57
+ end
58
+
59
+ def read
60
+ while true
61
+ if @handshaked
62
+ msg = @frame.next
63
+ return msg.data if msg
64
+ end
65
+
66
+ data = super()
67
+ return data if data.length <= 0
68
+
69
+ if @handshaked
70
+ @frame << data
71
+ msg = @frame.next
72
+ return msg.data if msg
73
+ else
74
+ index = 0
75
+ chars = ""
76
+ data.each_char do |char|
77
+ @handshake << char
78
+ chars << char
79
+ index += 1
80
+ if @handshake.finished?
81
+ @handshaked = true
82
+ break
83
+ end
84
+ end
85
+ if @handshaked
86
+ data = data[index..-1]
87
+ @frame << data
88
+ return
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ def write(data, type: :text)
95
+ frame = ::WebSocket::Frame::Outgoing::Client.new(:data => data, :type => type, :version => @handshake.version)
96
+ super(frame.to_s)
97
+ end
98
+ end
99
+ end
@@ -1,14 +1,14 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- OPENC3_VERSION = '5.4.3-beta0'
3
+ OPENC3_VERSION = '5.5.0-beta0'
4
4
  module OpenC3
5
5
  module Version
6
6
  MAJOR = '5'
7
- MINOR = '4'
8
- PATCH = '3'
7
+ MINOR = '5'
8
+ PATCH = '0'
9
9
  OTHER = 'pre.beta0'
10
- BUILD = '3eeed1efa129ce27f14bffc2b6bab4428a20c500'
10
+ BUILD = 'abab088dbe97761fca351752b3396f1c05dd852b'
11
11
  end
12
- VERSION = '5.4.3-beta0'
13
- GEM_VERSION = '5.4.3.pre.beta0'
12
+ VERSION = '5.5.0-beta0'
13
+ GEM_VERSION = '5.5.0.pre.beta0'
14
14
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openc3
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.4.3.pre.beta0
4
+ version: 5.5.0.pre.beta0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Melton
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-02-14 00:00:00.000000000 Z
12
+ date: 2023-02-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -445,6 +445,34 @@ dependencies:
445
445
  - - "~>"
446
446
  - !ruby/object:Gem::Version
447
447
  version: '0.3'
448
+ - !ruby/object:Gem::Dependency
449
+ name: websocket
450
+ requirement: !ruby/object:Gem::Requirement
451
+ requirements:
452
+ - - ">="
453
+ - !ruby/object:Gem::Version
454
+ version: '0'
455
+ type: :runtime
456
+ prerelease: false
457
+ version_requirements: !ruby/object:Gem::Requirement
458
+ requirements:
459
+ - - ">="
460
+ - !ruby/object:Gem::Version
461
+ version: '0'
462
+ - !ruby/object:Gem::Dependency
463
+ name: websocket-native
464
+ requirement: !ruby/object:Gem::Requirement
465
+ requirements:
466
+ - - ">="
467
+ - !ruby/object:Gem::Version
468
+ version: '0'
469
+ type: :runtime
470
+ prerelease: false
471
+ version_requirements: !ruby/object:Gem::Requirement
472
+ requirements:
473
+ - - ">="
474
+ - !ruby/object:Gem::Version
475
+ version: '0'
448
476
  - !ruby/object:Gem::Dependency
449
477
  name: dead_end
450
478
  requirement: !ruby/object:Gem::Requirement
@@ -916,10 +944,12 @@ files:
916
944
  - lib/openc3/script/suite_results.rb
917
945
  - lib/openc3/script/suite_runner.rb
918
946
  - lib/openc3/script/telemetry.rb
947
+ - lib/openc3/script/web_socket_api.rb
919
948
  - lib/openc3/streams/serial_stream.rb
920
949
  - lib/openc3/streams/stream.rb
921
950
  - lib/openc3/streams/tcpip_client_stream.rb
922
951
  - lib/openc3/streams/tcpip_socket_stream.rb
952
+ - lib/openc3/streams/web_socket_client_stream.rb
923
953
  - lib/openc3/system.rb
924
954
  - lib/openc3/system/system.rb
925
955
  - lib/openc3/system/system_config.rb