openc3 6.3.0 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 80ec07f72b5f3b98e988b0e8b4bee1b1ccf94e734f738ff33dc209249966a7e3
4
- data.tar.gz: 9367191aab4bae678ef63b0db5f22bfa8a91a5518ec9e056c49c76e099744fd0
3
+ metadata.gz: c408df49ec8cf1fbcf88e8c05207ac1c65274d56dbee94986c4f56308bc52b23
4
+ data.tar.gz: 8e8897d618e69898470fcd8a201ba968688fbe598c4ecb46d1a375da14282490
5
5
  SHA512:
6
- metadata.gz: 7a90b63db9e9e3e085249fb2bc8ae00e0733b26c040f30a9dd23b430255228a928ffe13b2b59e8219a7c908f5e94f1f6c44932569c3b1aadf12d297b2d920a7b
7
- data.tar.gz: 2b8e39ae3eee3028faf0a06719ae98c5a958c71afc989fb4da981e53dc8aa22cdcd4cb89f9d07cdbcc6e671360a9cfaab6f0b33854718aa21e731a8c01b80efc
6
+ metadata.gz: c37180a0814a4ceb79b6503d52a83e10cba964351f07841db692e44bdb5a619f95b7a955283982f73218dc5f6670bef113654b917fc92ed99a9c71ff8e000e36
7
+ data.tar.gz: fa11395f5c24e207e6e6eb6f74b2b8f53c882222572f39cf7d6a6e95668dd99c5ad24015e832df937fb5f12800c92e3c7f55319716a46fc6a07cdbb99abc2e52
data/bin/openc3cli CHANGED
@@ -592,28 +592,18 @@ def cli_script_monitor(script_id)
592
592
  while (resp = api.read) do
593
593
  # see ScriptRunner.vue for types and states
594
594
  case resp['type']
595
- when 'error', 'fatal'
596
- $script_interrupt_text = ''
597
- puts 'script failed'
598
- break
599
595
  when 'file'
600
596
  puts "Filename #{resp['filename']} scope #{resp['scope']}"
601
597
  when 'line'
602
598
  fn = resp['filename'].nil? ? '<no file>' : resp['filename']
603
599
  puts "At [#{fn}:#{resp['line_no']}] state [#{resp['state']}]"
604
- if resp['state'] == 'error'
600
+ if resp['state'] == 'error' or resp['state'] == 'crashed'
605
601
  $script_interrupt_text = ''
606
602
  puts 'script failed'
607
603
  break
608
604
  end
609
605
  when 'output'
610
606
  puts resp['line']
611
- when 'paused'
612
- if resp['state'] == 'fatal'
613
- $script_interrupt_text = ''
614
- puts 'script failed'
615
- break
616
- end
617
607
  when 'complete'
618
608
  $script_interrupt_text = ''
619
609
  puts 'script complete'
@@ -240,7 +240,7 @@ CMD:
240
240
  python_example: CMD python interface_microservice.py DEFAULT__INTERFACE__INT1
241
241
  CONTAINER:
242
242
  summary: Docker Container
243
- description: Container to execute and run the microservice in. Only used in COSMOS Enterprise Edition.
243
+ description: Container to execute and run the microservice in. Only used in COSMOS Enterprise.
244
244
  since: 5.7.0
245
245
  parameters:
246
246
  - name: Args
@@ -107,7 +107,7 @@ MICROSERVICE:
107
107
  values: .+
108
108
  CONTAINER:
109
109
  summary: Docker Container
110
- description: Container to execute and run the microservice in. Only used in COSMOS Enterprise Edition.
110
+ description: Container to execute and run the microservice in. Only used in COSMOS Enterprise.
111
111
  parameters:
112
112
  - name: Args
113
113
  required: false
@@ -16,7 +16,7 @@ VARIABLE:
16
16
  NEEDS_DEPENDENCIES:
17
17
  summary: Indicates the plugin needs dependencies and sets the GEM_HOME environment variable
18
18
  description: If the plugin has a top level lib folder or lists runtime dependencies in the gemspec,
19
- NEEDS_DEPENDENCIES is effectively already set. Note that in Enterprise Edition, having
19
+ NEEDS_DEPENDENCIES is effectively already set. Note that in Enterprise, having
20
20
  NEEDS_DEPENDENCIES adds the NFS volume mount to the Kubernetes pod.
21
21
  since: 5.5.0
22
22
  INTERFACE:
@@ -66,7 +66,7 @@ TOOL:
66
66
  description:
67
67
  Position of the tool starting at 2 (1 is reserved for Admin Console).
68
68
  Tools without a position are appended to the end as they are installed.
69
- All COSMOS open source tools have consecutive integer values for position.
69
+ All COSMOS Core tools have consecutive integer values for position.
70
70
  since: 5.0.8
71
71
  parameters:
72
72
  - name: Position
@@ -81,7 +81,11 @@ static VALUE burst_protocol_read_data(int argc, VALUE *argv, VALUE self)
81
81
  };
82
82
 
83
83
  rb_str_concat(rb_ivar_get(self, id_ivar_data), data);
84
- rb_ivar_set(self, id_ivar_extra, extra);
84
+
85
+ /* Maintain extra from last read read_data */
86
+ if (!((RSTRING_LEN(data) == 0) && (!(RTEST(extra))))) {
87
+ rb_ivar_set(self, id_ivar_extra, extra);
88
+ }
85
89
 
86
90
  while (1)
87
91
  {
@@ -14,7 +14,7 @@
14
14
  # GNU Affero General Public License for more details.
15
15
 
16
16
  # Modified by OpenC3, Inc.
17
- # All changes Copyright 2023, OpenC3, Inc.
17
+ # All changes Copyright 2025, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
20
  # This file may also be used under the terms of a commercial license
@@ -35,6 +35,12 @@ require 'openc3/api/target_api'
35
35
  require 'openc3/api/tlm_api'
36
36
  require 'openc3/utilities/authorization'
37
37
  require 'openc3/topics/topic'
38
+ begin
39
+ require 'openc3-enterprise/api/cmd_authority_api'
40
+ rescue LoadError
41
+ # LoadError expected for Open Source Edition
42
+ end
43
+
38
44
 
39
45
  module OpenC3
40
46
  module Api
@@ -18,12 +18,16 @@
18
18
 
19
19
  require 'openc3/interfaces/interface'
20
20
  require 'openc3/config/config_parser'
21
+ require 'openc3/utilities/logger'
21
22
  require 'thread'
22
23
  require 'listen'
23
24
  require 'fileutils'
25
+ require 'zlib'
24
26
 
25
27
  module OpenC3
26
28
  class FileInterface < Interface
29
+ attr_reader :filename
30
+
27
31
  # @param command_write_folder [String] Folder to write command files to - Set to nil to disallow writes
28
32
  # @param telemetry_read_folder [String] Folder to read telemetry files from - Set to nil to disallow reads
29
33
  # @param telemetry_archive_folder [String] Folder to move read telemetry files to - Set to DELETE to delete files
@@ -62,6 +66,7 @@ module OpenC3
62
66
  @write_raw_allowed = false unless @command_write_folder
63
67
 
64
68
  @file = nil
69
+ @filename = ''
65
70
  @listener = nil
66
71
  @connected = false
67
72
  @extension = ".bin"
@@ -69,6 +74,9 @@ module OpenC3
69
74
  @queue = Queue.new
70
75
  @polling = false
71
76
  @recursive = false
77
+ @throttle = nil
78
+ @discard_file_header_bytes = nil
79
+ @sleeper = nil
72
80
  end
73
81
 
74
82
  def connect
@@ -90,6 +98,7 @@ module OpenC3
90
98
  def disconnect
91
99
  @file.close if @file and not @file.closed?
92
100
  @file = nil
101
+ @sleeper.cancel if @sleeper
93
102
  @listener.stop if @listener
94
103
  @listener = nil
95
104
  @queue << nil
@@ -102,6 +111,10 @@ module OpenC3
102
111
  if @file
103
112
  # Read more data from existing file
104
113
  data = @file.read(@file_read_size)
114
+ # Throttle after each read size
115
+ if @throttle and @sleeper.sleep(@throttle)
116
+ return nil, nil
117
+ end
105
118
  if data and data.length > 0
106
119
  read_interface_base(data, nil)
107
120
  return data, nil
@@ -111,9 +124,16 @@ module OpenC3
111
124
  end
112
125
 
113
126
  # Find the next file to read
114
- file = get_next_telemetry_file()
115
- if file
116
- @file = File.open(file, 'rb')
127
+ @filename = get_next_telemetry_file()
128
+ if @filename
129
+ if File.extname(@filename) == ".gz"
130
+ @file = Zlib::GzipReader.open(@filename)
131
+ else
132
+ @file = File.open(@filename, "rb")
133
+ end
134
+ if @discard_file_header_bytes
135
+ @file.read(@discard_file_header_bytes)
136
+ end
117
137
  next
118
138
  end
119
139
 
@@ -135,8 +155,8 @@ module OpenC3
135
155
 
136
156
  def convert_data_to_packet(data, extra = nil)
137
157
  packet = super(data, extra)
138
- if packet and @stored
139
- packet.stored = true
158
+ if packet
159
+ packet.stored = @stored
140
160
  end
141
161
  return packet
142
162
  end
@@ -156,6 +176,11 @@ module OpenC3
156
176
  @polling = ConfigParser.handle_true_false(option_values[0])
157
177
  when 'RECURSIVE'
158
178
  @recursive = ConfigParser.handle_true_false(option_values[0])
179
+ when 'THROTTLE'
180
+ @throttle = Float(option_values[0])
181
+ @sleeper = Sleeper.new
182
+ when 'DISCARD_FILE_HEADER_BYTES'
183
+ @discard_file_header_bytes = Integer(option_values[0])
159
184
  end
160
185
  end
161
186
 
@@ -173,11 +198,15 @@ module OpenC3
173
198
  end
174
199
 
175
200
  def get_next_telemetry_file
201
+ files = []
176
202
  if @recursive
177
- return Dir.glob("#{@telemetry_read_folder}/**/*").sort[0]
203
+ files = Dir.glob("#{@telemetry_read_folder}/**/*")
178
204
  else
179
- return Dir.glob("#{@telemetry_read_folder}/*").sort[0]
205
+ files = Dir.glob("#{@telemetry_read_folder}/*")
180
206
  end
207
+ # Dir.glob includes directories, so filter them out
208
+ files = files.sort.select { |fn| File.file?(fn) }
209
+ return files[0]
181
210
  end
182
211
 
183
212
  def create_unique_filename
@@ -14,7 +14,7 @@
14
14
  # GNU Affero General Public License for more details.
15
15
 
16
16
  # Modified by OpenC3, Inc.
17
- # All changes Copyright 2022, OpenC3, Inc.
17
+ # All changes Copyright 2025, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
20
  # This file may also be used under the terms of a commercial license
@@ -37,7 +37,7 @@ module OpenC3
37
37
  # @param fill_fields [Boolean] Fill any required fields when writing packets
38
38
  # @param allow_empty_data [true/false/nil] See Protocol#initialize
39
39
  def initialize(discard_leading_bytes = 0, sync_pattern = nil, fill_fields = false, allow_empty_data = nil)
40
- super(allow_empty_data)
40
+ super(allow_empty_data) # Calls reset()
41
41
  @discard_leading_bytes = discard_leading_bytes.to_i
42
42
  @sync_pattern = ConfigParser.handle_nil(sync_pattern)
43
43
  @sync_pattern = @sync_pattern.hex_to_byte_string if @sync_pattern
@@ -65,7 +65,7 @@ module OpenC3
65
65
  # @return [String|nil] Data for a packet consisting of the bytes read
66
66
  def read_data(data, extra = nil)
67
67
  @data << data
68
- @extra = extra
68
+ @extra = extra unless (data.length == 0 and extra.nil?) # Maintain extra from last read read_data
69
69
 
70
70
  while true
71
71
  control = handle_sync_pattern()
@@ -14,7 +14,7 @@
14
14
  # GNU Affero General Public License for more details.
15
15
 
16
16
  # Modified by OpenC3, Inc.
17
- # All changes Copyright 2022, OpenC3, Inc.
17
+ # All changes Copyright 2025, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
20
  # This file may also be used under the terms of a commercial license
@@ -30,12 +30,12 @@ module OpenC3
30
30
 
31
31
  # @param sync_pattern (see BurstProtocol#initialize)
32
32
  # @param max_length [Integer] The maximum allowed value of the length field
33
+ # @param _unused [Integer] Legacy version number - unused
33
34
  # @param allow_empty_data [true/false/nil] See Protocol#initialize
34
- def initialize(sync_pattern = nil, max_length = nil, mode = 4, allow_empty_data = nil)
35
+ def initialize(sync_pattern = nil, max_length = nil, _unused = nil, allow_empty_data = nil)
35
36
  super(0, sync_pattern, false, allow_empty_data)
36
37
  @max_length = ConfigParser.handle_nil(max_length)
37
38
  @max_length = Integer(@max_length) if @max_length
38
- @mode = Integer(mode)
39
39
  end
40
40
 
41
41
  def reset
@@ -47,13 +47,11 @@ module OpenC3
47
47
  packet.received_time = @read_received_time
48
48
  packet.target_name = @read_target_name
49
49
  packet.packet_name = @read_packet_name
50
- if @mode == 4 # COSMOS4.3+ Protocol
51
- packet.stored = @read_stored
52
- if packet.extra and @read_extra
53
- packet.extra.merge(@read_extra)
54
- else
55
- packet.extra = @read_extra
56
- end
50
+ packet.stored = @read_stored
51
+ if packet.extra and @read_extra
52
+ packet.extra.merge(@read_extra)
53
+ else
54
+ packet.extra = @read_extra
57
55
  end
58
56
  return packet
59
57
  end
@@ -67,14 +65,12 @@ module OpenC3
67
65
  @write_target_name = 'UNKNOWN' unless @write_target_name
68
66
  @write_packet_name = packet.packet_name
69
67
  @write_packet_name = 'UNKNOWN' unless @write_packet_name
70
- if @mode == 4 # COSMOS4.3+ Protocol
71
- @write_flags = 0
72
- @write_flags |= COSMOS4_STORED_FLAG_MASK if packet.stored
73
- @write_extra = nil
74
- if packet.extra
75
- @write_flags |= COSMOS4_EXTRA_FLAG_MASK
76
- @write_extra = packet.extra.as_json(:allow_nan => true).to_json(:allow_nan => true)
77
- end
68
+ @write_flags = 0
69
+ @write_flags |= COSMOS4_STORED_FLAG_MASK if packet.stored
70
+ @write_extra = nil
71
+ if packet.extra
72
+ @write_flags |= COSMOS4_EXTRA_FLAG_MASK
73
+ @write_extra = packet.extra.as_json(:allow_nan => true).to_json(:allow_nan => true)
78
74
  end
79
75
  return packet
80
76
  end
@@ -83,12 +79,10 @@ module OpenC3
83
79
  data_length = [data.length].pack('N') # UINT32
84
80
  data_to_send = ''
85
81
  data_to_send << @sync_pattern if @sync_pattern
86
- if @mode == 4 # COSMOS4.3+ Protocol
87
- data_to_send << @write_flags
88
- if @write_extra
89
- data_to_send << [@write_extra.length].pack('N')
90
- data_to_send << @write_extra
91
- end
82
+ data_to_send << @write_flags
83
+ if @write_extra
84
+ data_to_send << [@write_extra.length].pack('N')
85
+ data_to_send << @write_extra
92
86
  end
93
87
  data_to_send << @write_time_seconds
94
88
  data_to_send << @write_time_microseconds
@@ -146,7 +140,7 @@ module OpenC3
146
140
  @reduction_state = :SYNC_REMOVED
147
141
  end
148
142
 
149
- if @reduction_state == :SYNC_REMOVED and @mode == 4
143
+ if @reduction_state == :SYNC_REMOVED
150
144
  # Read and remove flags
151
145
  return :STOP if @data.length < 1
152
146
 
@@ -171,7 +165,7 @@ module OpenC3
171
165
  @reduction_state = :FLAGS_REMOVED
172
166
  end
173
167
 
174
- if @reduction_state == :FLAGS_REMOVED or (@reduction_state == :SYNC_REMOVED and @mode != 4)
168
+ if @reduction_state == :FLAGS_REMOVED
175
169
  # Read and remove packet received time
176
170
  return :STOP if @data.length < 8
177
171
 
@@ -14,21 +14,22 @@
14
14
  # GNU Affero General Public License for more details.
15
15
 
16
16
  # Modified by OpenC3, Inc.
17
- # All changes Copyright 2024, OpenC3, Inc.
17
+ # All changes Copyright 2025, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
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
  module OpenC3
24
- autoload(:Interface, 'openc3/interfaces/interface.rb')
24
+ autoload(:FileInterface, 'openc3/interfaces/file_interface.rb')
25
25
  autoload(:HttpClientInterface, 'openc3/interfaces/http_client_interface.rb')
26
26
  autoload(:HttpServerInterface, 'openc3/interfaces/http_server_interface.rb')
27
+ autoload(:Interface, 'openc3/interfaces/interface.rb')
27
28
  autoload(:MqttInterface, 'openc3/interfaces/mqtt_interface.rb')
28
29
  autoload(:MqttStreamInterface, 'openc3/interfaces/mqtt_stream_interface.rb')
29
- autoload(:StreamInterface, 'openc3/interfaces/stream_interface.rb')
30
30
  autoload(:SerialInterface, 'openc3/interfaces/serial_interface.rb')
31
31
  autoload(:SimulatedTargetInterface, 'openc3/interfaces/simulated_target_interface.rb')
32
+ autoload(:StreamInterface, 'openc3/interfaces/stream_interface.rb')
32
33
  autoload(:TcpipClientInterface, 'openc3/interfaces/tcpip_client_interface.rb')
33
34
  autoload(:TcpipServerInterface, 'openc3/interfaces/tcpip_server_interface.rb')
34
35
  autoload(:UdpInterface, 'openc3/interfaces/udp_interface.rb')
@@ -14,7 +14,7 @@
14
14
  # GNU Affero General Public License for more details.
15
15
 
16
16
  # Modified by OpenC3, Inc.
17
- # All changes Copyright 2022, OpenC3, Inc.
17
+ # All changes Copyright 2025, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
20
  # This file may also be used under the terms of a commercial license
@@ -100,7 +100,8 @@ module OpenC3
100
100
  cycle_size = 1_000_000_000,
101
101
  cycle_hour = nil,
102
102
  cycle_minute = nil,
103
- enforce_time_order = true
103
+ enforce_time_order = true,
104
+ cycle_thread: true
104
105
  )
105
106
  @remote_log_directory = remote_log_directory
106
107
  @logging_enabled = ConfigParser.handle_true_false(logging_enabled)
@@ -135,13 +136,15 @@ module OpenC3
135
136
  # each time we create an entry which we do a LOT!
136
137
  @entry = String.new
137
138
 
138
- # Always make sure there is a cycle thread - (because it does trimming)
139
- @@mutex.synchronize do
140
- @@instances << self
139
+ if cycle_thread
140
+ # Always make sure there is a cycle thread - (because it does trimming)
141
+ @@mutex.synchronize do
142
+ @@instances << self
141
143
 
142
- unless @@cycle_thread
143
- @@cycle_thread = OpenC3.safe_thread("Log cycle") do
144
- cycle_thread_body()
144
+ unless @@cycle_thread
145
+ @@cycle_thread = OpenC3.safe_thread("Log cycle") do
146
+ cycle_thread_body()
147
+ end
145
148
  end
146
149
  end
147
150
  end
@@ -14,7 +14,7 @@
14
14
  # GNU Affero General Public License for more details.
15
15
 
16
16
  # Modified by OpenC3, Inc.
17
- # All changes Copyright 2024, OpenC3, Inc.
17
+ # All changes Copyright 2025, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
20
  # This file may also be used under the terms of a commercial license
@@ -309,7 +309,7 @@ module OpenC3
309
309
  if cbor
310
310
  extra = CBOR.decode(extra_encoded)
311
311
  else
312
- extra = JSON.parse(extra_encode, allow_nan: true, create_additions: true)
312
+ extra = JSON.parse(extra_encoded, allow_nan: true, create_additions: true)
313
313
  end
314
314
  end
315
315
  data = entry[next_offset..-1]
@@ -14,7 +14,7 @@
14
14
  # GNU Affero General Public License for more details.
15
15
 
16
16
  # Modified by OpenC3, Inc.
17
- # All changes Copyright 2022, OpenC3, Inc.
17
+ # All changes Copyright 2025, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
20
  # This file may also be used under the terms of a commercial license
@@ -40,7 +40,7 @@ require 'openc3/interfaces/interface'
40
40
  begin
41
41
  require 'openc3-enterprise/models/critical_cmd_model'
42
42
  rescue LoadError
43
- # Should never actual be used in Open Source
43
+ # LoadError expected in COSMOS Core
44
44
  end
45
45
 
46
46
  module OpenC3
@@ -208,9 +208,10 @@ module OpenC3
208
208
  shutdown()
209
209
  end
210
210
 
211
- def shutdown
211
+ def shutdown(state = 'STOPPED')
212
212
  return if @shutdown_complete
213
213
  @logger.info("Shutting down microservice: #{@name}")
214
+ @state = state
214
215
  @cancel_thread = true
215
216
  @microservice_status_sleeper.cancel if @microservice_status_sleeper
216
217
  MicroserviceStatusModel.set(as_json(:allow_nan => true), scope: @scope)
@@ -139,7 +139,7 @@ module OpenC3
139
139
 
140
140
  # Update the Redis hash at primary_key and set the field "name"
141
141
  # to the JSON generated via calling as_json
142
- def create(update: false, force: false, queued: false)
142
+ def create(update: false, force: false, queued: false, isoformat: false)
143
143
  unless force
144
144
  existing = self.class.store.hget(@primary_key, @name)
145
145
  if existing
@@ -148,7 +148,11 @@ module OpenC3
148
148
  raise "#{@primary_key}:#{@name} doesn't exist at update" if update
149
149
  end
150
150
  end
151
- @updated_at = Time.now.to_nsec_from_epoch
151
+ if isoformat
152
+ @updated_at = Time.now.utc.iso8601
153
+ else
154
+ @updated_at = Time.now.utc.to_nsec_from_epoch
155
+ end
152
156
 
153
157
  if queued
154
158
  write_store = self.class.store_queued
@@ -0,0 +1,242 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2025 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/models/model'
20
+
21
+ module OpenC3
22
+ class ScriptStatusModel < Model
23
+ # Note: ScriptRunner only has permissions for keys that start with running-script
24
+ RUNNING_PRIMARY_KEY = 'running-script'
25
+ COMPLETED_PRIMARY_KEY = 'running-script-completed'
26
+
27
+ def id
28
+ return @name
29
+ end
30
+ attr_reader :state # spawning, init, running, paused, waiting, breakpoint, error, crashed, stopped, completed, completed_errors, killed
31
+ attr_accessor :shard
32
+ attr_accessor :filename
33
+ attr_accessor :current_filename
34
+ attr_accessor :line_no
35
+ attr_accessor :start_line_no
36
+ attr_accessor :end_line_no
37
+ attr_accessor :username
38
+ attr_accessor :user_full_name
39
+ attr_accessor :start_time
40
+ attr_accessor :end_time
41
+ attr_accessor :disconnect
42
+ attr_accessor :environment
43
+ attr_accessor :suite_runner
44
+ attr_accessor :errors
45
+ attr_accessor :pid
46
+ attr_accessor :log
47
+ attr_accessor :report
48
+
49
+ # NOTE: The following three class methods are used by the ModelController
50
+ # and are reimplemented to enable various Model class methods to work
51
+ def self.get(name:, scope:, type: "auto")
52
+ if type == "auto" or type == "running"
53
+ # Check for running first
54
+ running = super("#{RUNNING_PRIMARY_KEY}__#{scope}", name: name)
55
+ return running if running
56
+ end
57
+ return super("#{COMPLETED_PRIMARY_KEY}__#{scope}", name: name)
58
+ end
59
+
60
+ def self.names(scope:, type: "running")
61
+ if type == "running"
62
+ return super("#{RUNNING_PRIMARY_KEY}__#{scope}")
63
+ else
64
+ return super("#{COMPLETED_PRIMARY_KEY}__#{scope}")
65
+ end
66
+ end
67
+
68
+ def self.all(scope:, offset: 0, limit: 10, type: "running")
69
+ if type == "running"
70
+ keys = self.store.zrevrange("#{RUNNING_PRIMARY_KEY}__#{scope}__LIST", offset.to_i, offset.to_i + limit.to_i - 1)
71
+ return [] if keys.empty?
72
+ result = self.store.redis_pool.pipelined do
73
+ keys.each do |key|
74
+ self.store.hget("#{RUNNING_PRIMARY_KEY}__#{scope}", key)
75
+ end
76
+ end
77
+ result = result.map do |r|
78
+ if r.nil?
79
+ nil
80
+ else
81
+ JSON.parse(r, :allow_nan => true, :create_additions => true)
82
+ end
83
+ end
84
+ return result
85
+ else
86
+ keys = self.store.zrevrange("#{COMPLETED_PRIMARY_KEY}__#{scope}__LIST", offset.to_i, offset.to_i + limit.to_i - 1)
87
+ return [] if keys.empty?
88
+ result = self.store.redis_pool.pipelined do
89
+ keys.each do |key|
90
+ self.store.hget("#{COMPLETED_PRIMARY_KEY}__#{scope}", key)
91
+ end
92
+ end
93
+ result = result.map do |r|
94
+ if r.nil?
95
+ nil
96
+ else
97
+ JSON.parse(r, :allow_nan => true, :create_additions => true)
98
+ end
99
+ end
100
+ return result
101
+ end
102
+ end
103
+
104
+ def self.count(scope:, type: "running")
105
+ if type == "running"
106
+ return self.store.zcount("#{RUNNING_PRIMARY_KEY}__#{scope}__LIST", 0, Float::INFINITY)
107
+ else
108
+ return self.store.zcount("#{COMPLETED_PRIMARY_KEY}__#{scope}__LIST", 0, Float::INFINITY)
109
+ end
110
+ end
111
+
112
+ def initialize(
113
+ name:, # id
114
+ state:, # spawning, init, running, paused, waiting, error, breakpoint, crashed, stopped, completed, completed_errors, killed
115
+ shard: 0, # Future enhancement of script runner shards
116
+ filename:, # The initial filename
117
+ current_filename: nil, # The current filename
118
+ line_no: 0, # The current line number
119
+ start_line_no: 1, # The line number to start the script at
120
+ end_line_no: nil, # The line number to end the script at
121
+ username:, # The username of the person who started the script
122
+ user_full_name:, # The full name of the person who started the script
123
+ start_time:, # The time the script started ISO format
124
+ end_time: nil, # The time the script ended ISO format
125
+ disconnect: false,
126
+ environment: nil,
127
+ suite_runner: nil,
128
+ errors: nil,
129
+ pid: nil,
130
+ log: nil,
131
+ report: nil,
132
+ updated_at: nil,
133
+ scope:
134
+ )
135
+ @state = state
136
+ if is_complete?()
137
+ super("#{COMPLETED_PRIMARY_KEY}__#{scope}", name: name, updated_at: updated_at, plugin: nil, scope: scope)
138
+ else
139
+ super("#{RUNNING_PRIMARY_KEY}__#{scope}", name: name, updated_at: updated_at, plugin: nil, scope: scope)
140
+ end
141
+ @shard = shard.to_i
142
+ @filename = filename
143
+ @current_filename = current_filename
144
+ @line_no = line_no
145
+ @start_line_no = start_line_no
146
+ @end_line_no = end_line_no
147
+ @username = username
148
+ @user_full_name = user_full_name
149
+ @start_time = start_time
150
+ @end_time = end_time
151
+ @disconnect = disconnect
152
+ @environment = environment
153
+ @suite_runner = suite_runner
154
+ @errors = errors
155
+ @pid = pid
156
+ @log = log
157
+ @report = report
158
+ end
159
+
160
+ def is_complete?
161
+ return (@state == 'completed' or @state == 'completed_errors' or @state == 'stopped' or @state == 'crashed' or @state == 'killed')
162
+ end
163
+
164
+ def state=(new_state)
165
+ # If the state is already a flavor of complete, leave it alone (first wins)
166
+ if not is_complete?()
167
+ @state = new_state
168
+ # If setting to complete, check for errors
169
+ # and set the state to complete_errors if they exist
170
+ if @state == 'completed' and @errors
171
+ @state = 'completed_errors'
172
+ end
173
+ end
174
+ end
175
+
176
+ # Update the Redis hash at primary_key and set the field "name"
177
+ # to the JSON generated via calling as_json
178
+ def create(update: false, force: false, queued: false, isoformat: true)
179
+ @updated_at = Time.now.utc.to_nsec_from_epoch
180
+
181
+ if queued
182
+ write_store = self.class.store_queued
183
+ else
184
+ write_store = self.class.store
185
+ end
186
+ write_store.hset(@primary_key, @name, JSON.generate(self.as_json(:allow_nan => true), :allow_nan => true))
187
+
188
+ # Also add to ordered set on create
189
+ write_store.zadd(@primary_key + "__LIST", @name.to_i, @name) if not update
190
+ end
191
+
192
+ def update(force: false, queued: false)
193
+ # Magically handle the change from running to completed
194
+ if is_complete?() and @primary_key == "#{RUNNING_PRIMARY_KEY}__#{@scope}"
195
+ # Destroy the running key
196
+ destroy()
197
+ @destroyed = false
198
+
199
+ # Move to completed
200
+ @primary_key = "#{COMPLETED_PRIMARY_KEY}__#{@scope}"
201
+ create(update: false, force: force, queued: queued, isoformat: true)
202
+ else
203
+ create(update: true, force: force, queued: queued, isoformat: true)
204
+ end
205
+ end
206
+
207
+ # Delete the model from the Store
208
+ def destroy
209
+ @destroyed = true
210
+ undeploy()
211
+ self.class.store.hdel(@primary_key, @name)
212
+ # Also remove from ordered set
213
+ self.class.store.zremrangebyscore(@primary_key + "__LIST", @name.to_i, @name.to_i)
214
+ end
215
+
216
+ def as_json(*a)
217
+ {
218
+ 'name' => @name,
219
+ 'state' => @state,
220
+ 'shard' => @shard,
221
+ 'filename' => @filename,
222
+ 'current_filename' => @current_filename,
223
+ 'line_no' => @line_no,
224
+ 'start_line_no' => @start_line_no,
225
+ 'end_line_no' => @end_line_no,
226
+ 'username' => @username,
227
+ 'user_full_name' => @user_full_name,
228
+ 'start_time' => @start_time,
229
+ 'end_time' => @end_time,
230
+ 'disconnect' => @disconnect,
231
+ 'environment' => @environment,
232
+ 'suite_runner' => @suite_runner,
233
+ 'errors' => @errors,
234
+ 'pid' => @pid,
235
+ 'log' => @log,
236
+ 'report' => @report,
237
+ 'updated_at' => @updated_at,
238
+ 'scope' => @scope
239
+ }
240
+ end
241
+ end
242
+ end
@@ -503,6 +503,10 @@ module OpenC3
503
503
  !cached
504
504
  end
505
505
 
506
+ def goto(line_no_or_procedure_name, line_no = nil)
507
+ raise "goto is not supported outside of ScriptRunner"
508
+ end
509
+
506
510
  # Require an additional ruby file
507
511
  def load_utility(procedure_name)
508
512
  return start(procedure_name)
@@ -167,17 +167,17 @@ module OpenC3
167
167
  end
168
168
  end
169
169
 
170
- def running_script_list(scope: $openc3_scope)
170
+ def running_script_list(limit: 10, offset: 0, scope: $openc3_scope)
171
171
  endpoint = "/script-api/running-script"
172
- response = $script_runner_api_server.request('get', endpoint, scope: scope)
172
+ response = $script_runner_api_server.request('get', endpoint, query: {"limit" => limit, "offset" => offset}, scope: scope)
173
173
  if response.nil? || response.status != 200
174
174
  _script_response_error(response, "Running script list request failed", scope: scope)
175
175
  else
176
- return JSON.parse(response.body, :allow_nan => true, :create_additions => true)
176
+ return JSON.parse(response.body, :allow_nan => true, :create_additions => true)['items']
177
177
  end
178
178
  end
179
179
 
180
- def running_script_get(id, scope: $openc3_scope)
180
+ def script_get(id, scope: $openc3_scope)
181
181
  endpoint = "/script-api/running-script/#{id}"
182
182
  response = $script_runner_api_server.request('get', endpoint, scope: scope)
183
183
  if response.nil? || response.status != 200
@@ -186,6 +186,7 @@ module OpenC3
186
186
  return JSON.parse(response.body, :allow_nan => true, :create_additions => true)
187
187
  end
188
188
  end
189
+ alias running_script_get script_get # Deprecated alias for compatibility
189
190
 
190
191
  def _running_script_action(id, action_name, scope: $openc3_scope)
191
192
  endpoint = "/script-api/running-script/#{id}/#{action_name}"
@@ -249,13 +250,27 @@ module OpenC3
249
250
  end
250
251
  end
251
252
 
252
- def completed_script_list(scope: $openc3_scope)
253
+ def running_script_execute_while_paused(id, filename, line_no, end_line_no = nil, scope: $openc3_scope)
254
+ endpoint = "/script-api/running-script/#{id}/executewhilepaused"
255
+ if end_line_no
256
+ response = $script_runner_api_server.request('post', endpoint, json: true, data: {'args' => [filename, line_no, end_line_no]}, scope: scope)
257
+ else
258
+ response = $script_runner_api_server.request('post', endpoint, json: true, data: {'args' => [filename, line_no]}, scope: scope)
259
+ end
260
+ if response.nil? || response.status != 200
261
+ _script_response_error(response, "Running script executewhilepaused request failed", scope: scope)
262
+ else
263
+ return true
264
+ end
265
+ end
266
+
267
+ def completed_script_list(limit: 10, offset: 0, scope: $openc3_scope)
253
268
  endpoint = "/script-api/completed-scripts"
254
- response = $script_runner_api_server.request('get', endpoint, scope: scope)
269
+ response = $script_runner_api_server.request('get', endpoint, query: {"limit" => limit, "offset" => offset}, scope: scope)
255
270
  if response.nil? || response.status != 200
256
271
  _script_response_error(response, "Completed script list request failed", scope: scope)
257
272
  else
258
- return JSON.parse(response.body, :allow_nan => true, :create_additions => true)
273
+ return JSON.parse(response.body, :allow_nan => true, :create_additions => true)['items']
259
274
  end
260
275
  end
261
276
  end
@@ -29,7 +29,7 @@ module OpenC3
29
29
  class OpenC3AuthenticationError < StandardError; end
30
30
  class OpenC3AuthenticationRetryableError < OpenC3AuthenticationError; end
31
31
 
32
- # OpenC3 base / open source authentication code
32
+ # OpenC3 COSMOS Core authentication code
33
33
  class OpenC3Authentication
34
34
  def initialize()
35
35
  @token = ENV['OPENC3_API_PASSWORD']
@@ -29,7 +29,7 @@ module OpenC3
29
29
  user = {}
30
30
  end
31
31
  username = user['username']
32
- # Open Source username (EE has the actual username)
32
+ # Core username (EE has the actual username)
33
33
  username ||= 'anonymous'
34
34
  end
35
35
  end
@@ -64,6 +64,7 @@ module OpenC3
64
64
 
65
65
  # Closes the message log and marks it read only
66
66
  def stop(take_mutex = true, metadata: {})
67
+ bucket_key = nil
67
68
  @mutex.lock if take_mutex
68
69
  if @file and not @file.closed?
69
70
  @file.close
@@ -77,6 +78,7 @@ module OpenC3
77
78
  end
78
79
  end
79
80
  @mutex.unlock if take_mutex
81
+ return bucket_key
80
82
  end
81
83
 
82
84
  # Creates a new message log and sets the filename
@@ -133,5 +133,5 @@ end
133
133
  begin
134
134
  require 'openc3-enterprise/utilities/metric'
135
135
  rescue LoadError
136
- # Open Source Edition - Do nothing here
136
+ # LoadError expected in COSMOS Core
137
137
  end
@@ -1,14 +1,14 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- OPENC3_VERSION = '6.3.0'
3
+ OPENC3_VERSION = '6.4.0'
4
4
  module OpenC3
5
5
  module Version
6
6
  MAJOR = '6'
7
- MINOR = '3'
7
+ MINOR = '4'
8
8
  PATCH = '0'
9
9
  OTHER = ''
10
- BUILD = '52f29bf512b463d226b93d18f31aa71936d472b2'
10
+ BUILD = '7f674e6c8a5d1e77fc64182365b831a17751b78d'
11
11
  end
12
- VERSION = '6.3.0'
13
- GEM_VERSION = '6.3.0'
12
+ VERSION = '6.4.0'
13
+ GEM_VERSION = '6.4.0'
14
14
  end
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "<%= tool_name %>",
3
- "version": "6.3.0",
3
+ "version": "6.4.0",
4
4
  "scripts": {
5
5
  "ng": "ng",
6
6
  "start": "ng serve",
@@ -23,7 +23,7 @@
23
23
  "@angular/platform-browser-dynamic": "^18.2.6",
24
24
  "@angular/router": "^18.2.6",
25
25
  "@astrouxds/astro-web-components": "^7.24.0",
26
- "@openc3/js-common": "6.3.0",
26
+ "@openc3/js-common": "6.4.0",
27
27
  "rxjs": "~7.8.0",
28
28
  "single-spa": "^5.9.5",
29
29
  "single-spa-angular": "^9.2.0",
@@ -16,7 +16,7 @@
16
16
  "@emotion/react": "^11.13.3",
17
17
  "@emotion/styled": "^11.11.0",
18
18
  "@mui/material": "^6.1.1",
19
- "@openc3/js-common": "6.3.0",
19
+ "@openc3/js-common": "6.4.0",
20
20
  "react": "^18.2.0",
21
21
  "react-dom": "^18.2.0",
22
22
  "single-spa-react": "^5.1.4"
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@astrouxds/astro-web-components": "^7.24.0",
15
- "@openc3/js-common": "6.3.0",
15
+ "@openc3/js-common": "6.4.0",
16
16
  "@smui/button": "^7.0.0",
17
17
  "@smui/common": "^7.0.0",
18
18
  "@smui/card": "^7.0.0",
@@ -1,30 +1,11 @@
1
- import prettier from 'eslint-plugin-prettier'
2
- import globals from 'globals'
3
- import parser from 'vue-eslint-parser'
4
- import path from 'node:path'
5
- import { fileURLToPath } from 'node:url'
6
- import js from '@eslint/js'
7
- import { FlatCompat } from '@eslint/eslintrc'
8
-
9
- const __filename = fileURLToPath(import.meta.url)
10
- const __dirname = path.dirname(__filename)
11
- const compat = new FlatCompat({
12
- baseDirectory: __dirname,
13
- recommendedConfig: js.configs.recommended,
14
- allConfig: js.configs.all,
15
- })
1
+ import prettierConfig from "@vue/eslint-config-prettier"
2
+ import pluginVue from "eslint-plugin-vue"
3
+ import globals from "globals"
4
+ import parser from "vue-eslint-parser"
16
5
 
17
6
  export default [
18
- ...compat.extends(
19
- 'plugin:vue/vue3-essential',
20
- 'plugin:prettier/recommended',
21
- '@vue/prettier',
22
- ),
7
+ ...pluginVue.configs['flat/recommended'],
23
8
  {
24
- plugins: {
25
- prettier,
26
- },
27
-
28
9
  languageOptions: {
29
10
  globals: {
30
11
  ...globals.node,
@@ -32,32 +13,26 @@ export default [
32
13
 
33
14
  parser: parser,
34
15
  ecmaVersion: 2022,
35
- sourceType: 'module',
16
+ sourceType: "module",
36
17
  },
37
18
 
38
19
  rules: {
39
- 'no-console': 'error',
40
- 'no-debugger': 'error',
20
+ "no-console": "error",
21
+ "no-debugger": "error",
41
22
 
42
- 'prettier/prettier': [
43
- 'warn',
44
- {
45
- endOfLine: 'auto',
46
- },
47
- ],
23
+ "prettier/prettier": ["warn", {
24
+ endOfLine: "auto",
25
+ }],
48
26
 
49
- 'vue/multi-word-component-names': 'off',
27
+ "vue/multi-word-component-names": "off",
50
28
 
51
- 'vue/valid-v-slot': [
52
- 'error',
53
- {
54
- allowModifiers: true,
55
- },
56
- ],
29
+ "vue/valid-v-slot": ["error", {
30
+ allowModifiers: true,
31
+ }],
57
32
  },
58
33
  },
59
34
  {
60
- files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'],
35
+ files: ["**/__tests__/*.{j,t}s?(x)", "**/tests/unit/**/*.spec.{j,t}s?(x)"],
61
36
 
62
37
  languageOptions: {
63
38
  globals: {
@@ -65,4 +40,5 @@ export default [
65
40
  },
66
41
  },
67
42
  },
43
+ prettierConfig,
68
44
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "<%= tool_name %>",
3
- "version": "6.3.0",
3
+ "version": "6.4.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -11,8 +11,8 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@astrouxds/astro-web-components": "^7.24.0",
14
- "@openc3/js-common": "6.3.0",
15
- "@openc3/vue-common": "6.3.0",
14
+ "@openc3/js-common": "6.4.0",
15
+ "@openc3/vue-common": "6.4.0",
16
16
  "axios": "^1.7.7",
17
17
  "date-fns": "^4.1.0",
18
18
  "lodash": "^4.17.21",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "<%= widget_name %>",
3
- "version": "6.3.0",
3
+ "version": "6.4.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@astrouxds/astro-web-components": "^7.24.0",
11
- "@openc3/vue-common": "6.3.0",
11
+ "@openc3/vue-common": "6.4.0",
12
12
  "vuetify": "^3.7.1"
13
13
  },
14
14
  "devDependencies": {
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: 6.3.0
4
+ version: 6.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Melton
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 1.3.0
89
+ version: '1.3'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 1.3.0
96
+ version: '1.3'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: psych
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -1033,6 +1033,7 @@ files:
1033
1033
  - lib/openc3/models/router_model.rb
1034
1034
  - lib/openc3/models/router_status_model.rb
1035
1035
  - lib/openc3/models/scope_model.rb
1036
+ - lib/openc3/models/script_status_model.rb
1036
1037
  - lib/openc3/models/secret_model.rb
1037
1038
  - lib/openc3/models/setting_model.rb
1038
1039
  - lib/openc3/models/sorted_model.rb
@@ -1284,7 +1285,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1284
1285
  - !ruby/object:Gem::Version
1285
1286
  version: '0'
1286
1287
  requirements: []
1287
- rubygems_version: 3.6.7
1288
+ rubygems_version: 3.6.8
1288
1289
  specification_version: 4
1289
1290
  summary: OpenC3
1290
1291
  test_files: []