openc3 5.7.0 → 5.8.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/data/config/widgets.yaml +6 -6
  4. data/lib/openc3/api/cmd_api.rb +29 -0
  5. data/lib/openc3/api/limits_api.rb +31 -1
  6. data/lib/openc3/api/tlm_api.rb +5 -7
  7. data/lib/openc3/core_ext/faraday.rb +8 -0
  8. data/lib/openc3/core_ext.rb +1 -1
  9. data/lib/openc3/io/json_api_object.rb +24 -12
  10. data/lib/openc3/io/json_drb_object.rb +3 -7
  11. data/lib/openc3/logs/buffered_packet_log_writer.rb +20 -12
  12. data/lib/openc3/logs/log_writer.rb +12 -7
  13. data/lib/openc3/logs/packet_log_writer.rb +9 -6
  14. data/lib/openc3/microservices/decom_microservice.rb +4 -0
  15. data/lib/openc3/microservices/interface_decom_common.rb +32 -0
  16. data/lib/openc3/microservices/reducer_microservice.rb +15 -11
  17. data/lib/openc3/models/cvt_model.rb +10 -5
  18. data/lib/openc3/models/gem_model.rb +0 -1
  19. data/lib/openc3/models/scope_model.rb +0 -3
  20. data/lib/openc3/models/target_model.rb +7 -7
  21. data/lib/openc3/models/timeline_model.rb +0 -1
  22. data/lib/openc3/models/trigger_group_model.rb +0 -1
  23. data/lib/openc3/script/metadata.rb +7 -7
  24. data/lib/openc3/script/screen.rb +5 -5
  25. data/lib/openc3/script/script_runner.rb +17 -17
  26. data/lib/openc3/script/storage.rb +2 -2
  27. data/lib/openc3/topics/config_topic.rb +1 -6
  28. data/lib/openc3/topics/decom_interface_topic.rb +62 -0
  29. data/lib/openc3/topics/telemetry_decom_topic.rb +0 -9
  30. data/lib/openc3/topics/topic.rb +4 -11
  31. data/lib/openc3/utilities/authentication.rb +4 -4
  32. data/lib/openc3/utilities/bucket_require.rb +65 -0
  33. data/lib/openc3/utilities/bucket_utilities.rb +38 -5
  34. data/lib/openc3/utilities/open_telemetry.rb +4 -4
  35. data/lib/openc3/utilities/process_manager.rb +4 -2
  36. data/lib/openc3/utilities/ruby_lex_utils.rb +62 -36
  37. data/lib/openc3/utilities/store_autoload.rb +7 -28
  38. data/lib/openc3/version.rb +5 -5
  39. data/lib/openc3.rb +1 -1
  40. data/templates/widget/package.json +7 -7
  41. data/templates/widget/yarn.lock +46 -47
  42. metadata +55 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9233a93be94c8a50cf561413812f9ae718edb25838cefe5fedf6bcb6efdd5ade
4
- data.tar.gz: 9a7a7e09b815c0f5c077c9987dadad5da1fcd986f7bf6c3db213b528ff2a293a
3
+ metadata.gz: 4ff64c4ea935a504f12164ff90107fbe35e10b31f0628ca21a8be8f411711e8f
4
+ data.tar.gz: 243be1e6c1e5f61cfc7789bcb6319db6cae3cd733e186ae33a1e8de08fa3a6e1
5
5
  SHA512:
6
- metadata.gz: 86bdc47e5dec0e3ca71de24ae2e4ce4e2fca4824696f214d37bacbbff1f914380e50e414ac270c6b52f64d965bf8eaa2ca22733993df261c42510480731b66ff
7
- data.tar.gz: 8e813eb9fb74aae5da6ffd11e196e974be2e9ca9718e7a6ef08eedba8668078f3ea18bd9968f431ec7562de7772888f28e4e05e366f4226a808225571b447574
6
+ metadata.gz: a379ce7d179aff5f45edd1413d7d10a19a49034685affda022e7cf1ad45285bcd496a522c3aa786f9325d591e9c5ff65201f5ba9486ffae8743c0c2b1b52052a
7
+ data.tar.gz: 991a47c1deb6d78415bf3e77d1569a8d6279f629d8ac8169491ebfd4dbd85652e422747d28d52fef49b1db02b04c6c2cc5164aa291a2946be4f6c4daccb67352
data/Gemfile CHANGED
@@ -9,7 +9,7 @@ gemspec :name => 'openc3'
9
9
  # Include the rails gems for the convenience of custom microservice plugins
10
10
  gem 'rails', '~> 7.0.0'
11
11
  gem 'bootsnap', '>= 1.9.3', require: false
12
- gem 'rack-cors', '~> 1.1'
12
+ gem 'rack-cors', '~> 2.0'
13
13
  gem 'tzinfo-data'
14
14
  gem 'rspec-rails', '~> 6.0'
15
15
  gem 'simplecov', '~> 0.20'
@@ -12,7 +12,7 @@ Layout Widgets:
12
12
  parameters:
13
13
  - name: Margin
14
14
  required: false
15
- description: Margin between widgets (default = 1px)
15
+ description: Margin between widgets (default = 0px)
16
16
  values: .*
17
17
  example: |
18
18
  VERTICAL 5px
@@ -30,7 +30,7 @@ Layout Widgets:
30
30
  values: .*
31
31
  - name: Margin
32
32
  required: false
33
- description: Margin between widgets (default = 1px)
33
+ description: Margin between widgets (default = 0px)
34
34
  values: .*
35
35
  example: |
36
36
  VERTICALBOX Info
@@ -43,7 +43,7 @@ Layout Widgets:
43
43
  parameters:
44
44
  - name: Margin
45
45
  required: false
46
- description: Margin between widgets (default = 1px)
46
+ description: Margin between widgets (default = 0px)
47
47
  values: .*
48
48
  example: |
49
49
  HORIZONTAL 100
@@ -60,7 +60,7 @@ Layout Widgets:
60
60
  values: .*
61
61
  - name: Margin
62
62
  required: false
63
- description: Margin between widgets (default = 1px)
63
+ description: Margin between widgets (default = 0px)
64
64
  values: .*
65
65
  example: |
66
66
  HORIZONTALBOX Info 10
@@ -77,7 +77,7 @@ Layout Widgets:
77
77
  values: .*
78
78
  - name: Margin
79
79
  required: false
80
- description: Margin between widgets (default = 1px)
80
+ description: Margin between widgets (default = 0px)
81
81
  values: .*
82
82
  example: |
83
83
  MATRIXBYCOLUMNS 3 10
@@ -98,7 +98,7 @@ Layout Widgets:
98
98
  values: .*
99
99
  - name: Margin
100
100
  required: false
101
- description: Margin between widgets (default = 1px)
101
+ description: Margin between widgets (default = 0px)
102
102
  values: .*
103
103
  example: |
104
104
  SCROLLWINDOW 100 10
@@ -23,6 +23,7 @@
23
23
  require 'openc3/models/target_model'
24
24
  require 'openc3/topics/command_topic'
25
25
  require 'openc3/topics/command_decom_topic'
26
+ require 'openc3/topics/decom_interface_topic'
26
27
  require 'openc3/topics/interface_topic'
27
28
  require 'openc3/script/extract'
28
29
 
@@ -38,6 +39,7 @@ module OpenC3
38
39
  'cmd_raw_no_range_check',
39
40
  'cmd_raw_no_hazardous_check',
40
41
  'cmd_raw_no_checks',
42
+ 'build_command',
41
43
  'send_raw',
42
44
  'get_all_commands',
43
45
  'get_all_command_names',
@@ -95,6 +97,33 @@ module OpenC3
95
97
  cmd_implementation('cmd_raw_no_checks', *args, range_check: false, hazardous_check: false, raw: true, **kwargs)
96
98
  end
97
99
 
100
+ # Build a command binary
101
+ #
102
+ # @since 5.8.0
103
+ def build_command(*args, range_check: true, raw: false, scope: $openc3_scope, token: $openc3_token, **kwargs)
104
+ extract_string_kwargs_to_args(args, kwargs)
105
+ case args.length
106
+ when 1
107
+ target_name, cmd_name, cmd_params = extract_fields_from_cmd_text(args[0], scope: scope)
108
+ when 2, 3
109
+ target_name = args[0]
110
+ cmd_name = args[1]
111
+ if args.length == 2
112
+ cmd_params = {}
113
+ else
114
+ cmd_params = args[2]
115
+ end
116
+ else
117
+ # Invalid number of arguments
118
+ raise "ERROR: Invalid number of arguments (#{args.length}) passed to build_command()"
119
+ end
120
+ target_name = target_name.upcase
121
+ cmd_name = cmd_name.upcase
122
+ cmd_params = cmd_params.transform_keys(&:upcase)
123
+ authorize(permission: 'cmd_info', target_name: target_name, scope: scope, token: token)
124
+ DecomInterfaceTopic.build_cmd(target_name, cmd_name, cmd_params, range_check, raw, scope: scope)
125
+ end
126
+
98
127
  # Send a raw binary string to the specified interface.
99
128
  #
100
129
  # @param interface_name [String] The interface to send the raw binary
@@ -21,6 +21,7 @@
21
21
  # if purchased from OpenC3, Inc.
22
22
 
23
23
  require 'openc3/api/target_api'
24
+ require 'openc3/models/cvt_model'
24
25
 
25
26
  module OpenC3
26
27
  module Api
@@ -191,7 +192,7 @@ module OpenC3
191
192
  def get_limits(target_name, packet_name, item_name, scope: $openc3_scope, token: $openc3_token)
192
193
  authorize(permission: 'tlm', target_name: target_name, packet_name: packet_name, scope: scope, token: token)
193
194
  limits = {}
194
- item = TargetModel.packet_item(target_name, packet_name, item_name, scope: scope)
195
+ item = _get_item(target_name, packet_name, item_name, scope: scope)
195
196
  item['limits'].each do |key, vals|
196
197
  next unless vals.is_a?(Hash)
197
198
 
@@ -364,5 +365,34 @@ module OpenC3
364
365
  TargetModel.set_packet(last_target_name, last_packet_name, packet, scope: scope)
365
366
  end
366
367
  end
368
+
369
+ # Gets an item. The code below is mostly duplicated from tlm_process_args in tlm_api.rb.
370
+ #
371
+ # @param target_name [String] target name
372
+ # @param packet_name [String] packet name
373
+ # @param item_name [String] item name
374
+ # @param scope [String] scope
375
+ # @return Hash The requested item based on the packet name
376
+ def _get_item(target_name, packet_name, item_name, scope:)
377
+ requested_item = nil
378
+ if packet_name == 'LATEST'
379
+ latest = -1
380
+ TargetModel.packets(target_name, scope: scope).each do |packet|
381
+ item = packet['items'].find { |item| item['name'] == item_name }
382
+ if item
383
+ hash = CvtModel.get(target_name: target_name, packet_name: packet['packet_name'], scope: scope)
384
+ if hash['PACKET_TIMESECONDS'] && hash['PACKET_TIMESECONDS'] > latest
385
+ latest = hash['PACKET_TIMESECONDS']
386
+ requested_item = item
387
+ end
388
+ end
389
+ end
390
+ raise "Item '#{target_name} LATEST #{item_name}' does not exist" if latest == -1
391
+ else
392
+ # Determine if this item exists, it will raise appropriate errors if not
393
+ requested_item = TargetModel.packet_item(target_name, packet_name, item_name, scope: scope)
394
+ end
395
+ return requested_item
396
+ end
367
397
  end
368
398
  end
@@ -25,7 +25,7 @@ require 'openc3/models/cvt_model'
25
25
  require 'openc3/packets/packet'
26
26
  require 'openc3/topics/telemetry_topic'
27
27
  require 'openc3/topics/interface_topic'
28
- require 'openc3/topics/telemetry_decom_topic'
28
+ require 'openc3/topics/decom_interface_topic'
29
29
 
30
30
  module OpenC3
31
31
  module Api
@@ -150,7 +150,7 @@ module OpenC3
150
150
  if interface_name
151
151
  InterfaceTopic.inject_tlm(interface_name, target_name, packet_name, item_hash, type: type, scope: scope)
152
152
  else
153
- TelemetryDecomTopic.inject_tlm(target_name, packet_name, item_hash, type: type, scope: scope)
153
+ DecomInterfaceTopic.inject_tlm(target_name, packet_name, item_hash, type: type, scope: scope)
154
154
  end
155
155
  end
156
156
 
@@ -449,12 +449,10 @@ module OpenC3
449
449
  TargetModel.packets(target_name, scope: scope).each do |packet|
450
450
  item = packet['items'].find { |item| item['name'] == item_name }
451
451
  if item
452
- # TODO: Fixme: This should be using the CVT not topics - Will possibly choose wrong packet if mixed with stored
453
- _, msg_hash = Topic.get_newest_message("#{scope}__DECOM__{#{target_name}}__#{packet['packet_name']}")
454
- packet_name = packet['packet_name'] if packet_name == 'LATEST'
455
- if msg_hash && msg_hash['time'] && msg_hash['time'].to_i > latest
452
+ hash = CvtModel.get(target_name: target_name, packet_name: packet['packet_name'], scope: scope)
453
+ if hash['PACKET_TIMESECONDS'] && hash['PACKET_TIMESECONDS'] > latest
454
+ latest = hash['PACKET_TIMESECONDS']
456
455
  packet_name = packet['packet_name']
457
- latest = msg_hash['time'].to_i
458
456
  end
459
457
  end
460
458
  end
@@ -0,0 +1,8 @@
1
+ require 'faraday'
2
+
3
+ module Faraday
4
+ class Response
5
+ # Add an alias of status to code to feel more like httpclient
6
+ alias code status
7
+ end
8
+ end
@@ -23,9 +23,9 @@
23
23
  require 'openc3/core_ext/array'
24
24
  require 'openc3/core_ext/binding'
25
25
  require 'openc3/core_ext/class'
26
- require 'openc3/core_ext/httpclient'
27
26
  require 'openc3/core_ext/openc3_io'
28
27
  require 'openc3/core_ext/exception'
28
+ require 'openc3/core_ext/faraday'
29
29
  require 'openc3/core_ext/file'
30
30
  require 'openc3/core_ext/hash'
31
31
  require 'openc3/core_ext/io'
@@ -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 2023, 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,7 +30,7 @@ require 'json'
30
30
  # require 'drb/acl'
31
31
  require 'drb/drb'
32
32
  require 'uri'
33
- require 'httpclient'
33
+ require 'faraday'
34
34
 
35
35
 
36
36
  module OpenC3
@@ -101,7 +101,7 @@ module OpenC3
101
101
 
102
102
  # Disconnects from http server
103
103
  def disconnect
104
- @http.reset_all() if @http
104
+ @http.close if @http
105
105
  @http = nil
106
106
  end
107
107
 
@@ -115,9 +115,14 @@ module OpenC3
115
115
 
116
116
  def connect
117
117
  begin
118
- @http = HTTPClient.new
119
- @http.connect_timeout = @timeout
120
- @http.receive_timeout = nil # Allow long polling
118
+ # Per https://github.com/lostisland/faraday/blob/main/lib/faraday/options/env.rb
119
+ # :timeout - time limit for the entire request (Integer in seconds)
120
+ # :open_timeout - time limit for just the connection phase (e.g. handshake) (Integer in seconds)
121
+ # :read_timeout - time limit for the first response byte received from the server (Integer in seconds)
122
+ # :write_timeout - time limit for the client to send the request to the server (Integer in seconds)
123
+ @http = Faraday.new(request: { open_timeout: @timeout.to_i, read_timeout: nil }) do |f|
124
+ f.adapter :net_http # adds the adapter to the connection, defaults to `Faraday.default_adapter`
125
+ end
121
126
  rescue => e
122
127
  raise JsonApiError, e.message
123
128
  end
@@ -216,17 +221,24 @@ module OpenC3
216
221
  def _http_request(method:, uri:, kwargs:)
217
222
  case method
218
223
  when 'get', :get
219
- return @http.get(uri, :header => kwargs[:headers], :query => kwargs[:query])
224
+ return @http.get(uri, kwargs[:query], kwargs[:headers])
220
225
  when 'post', :post
221
- return @http.post(uri, :header => kwargs[:headers], :query => kwargs[:query], :body => kwargs[:data])
226
+ return @http.post(uri) do |req|
227
+ req.params = kwargs[:query]
228
+ req.headers = kwargs[:headers]
229
+ req.body = kwargs[:data]
230
+ end
222
231
  when 'put', :put
223
- return @http.put(uri, :header => kwargs[:headers], :query => kwargs[:query], :body => kwargs[:data])
232
+ return @http.put(uri) do |req|
233
+ req.params = kwargs[:query]
234
+ req.headers = kwargs[:headers]
235
+ req.body = kwargs[:data]
236
+ end
224
237
  when 'delete', :delete
225
- return @http.delete(uri, :header => kwargs[:headers], :query => kwargs[:query])
238
+ return @http.delete(uri, kwargs[:query], kwargs[:headers])
226
239
  else
227
240
  raise JsonApiError, "no method found: '#{method}'"
228
241
  end
229
242
  end
230
-
231
- end # class JsonApiObject
243
+ end
232
244
  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 2022, OpenC3, Inc.
17
+ # All changes Copyright 2023, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
20
  # This file may also be used under the terms of a commercial license
@@ -24,7 +24,6 @@ require 'openc3'
24
24
  require 'openc3/io/json_api_object'
25
25
 
26
26
  module OpenC3
27
-
28
27
  class JsonDRbError < JsonApiError; end
29
28
 
30
29
  # Used to forward all method calls to the remote server object. Before using
@@ -39,7 +38,6 @@ module OpenC3
39
38
  # server.cmd(*args)
40
39
  #
41
40
  class JsonDRbObject < JsonApiObject
42
-
43
41
  USER_AGENT = 'OpenC3 / v5 (ruby/openc3/lib/io/json_drb_object)'
44
42
 
45
43
  # @param url [String] The url of openc3-cosmos-cmd-tlm-api http://openc3-cosmos-cmd-tlm-api:2901
@@ -80,7 +78,6 @@ module OpenC3
80
78
 
81
79
  private
82
80
 
83
- #
84
81
  def make_request(data:, token: nil)
85
82
  token = @authentication.token if @authentication and not token
86
83
  if token
@@ -98,7 +95,7 @@ module OpenC3
98
95
  begin
99
96
  @log[0] = "Request: #{@uri.to_s} #{USER_AGENT} #{data.to_s}"
100
97
  STDOUT.puts @log[0] if JsonDRb.debug?
101
- resp = @http.post(@uri, :body => data, :header => headers)
98
+ resp = @http.post(@uri, data, headers)
102
99
  @log[1] = "Response: #{resp.status} #{resp.headers} #{resp.body}"
103
100
  @response_data = resp.body
104
101
  STDOUT.puts @log[1] if JsonDRb.debug?
@@ -109,7 +106,6 @@ module OpenC3
109
106
  end
110
107
  end
111
108
 
112
- #
113
109
  def handle_response(response:)
114
110
  # The code below will always either raise or return breaking out of the loop
115
111
  if JsonRpcErrorResponse === response
@@ -122,5 +118,5 @@ module OpenC3
122
118
  return response.result
123
119
  end
124
120
  end
125
- end # class JsonDRbObject
121
+ end
126
122
  end
@@ -96,23 +96,31 @@ module OpenC3
96
96
  return nil
97
97
  end
98
98
 
99
- def start_new_file(empty_buffer = false)
100
- write_buffer() if empty_buffer
101
- super()
99
+ def close_file(take_mutex = true)
100
+ @mutex.lock if take_mutex
101
+ begin
102
+ # Need to write out the buffer before closing out the file
103
+ if @buffer.length > 0
104
+ start_new_file() unless @file
105
+ write_buffer()
106
+ end
107
+ return super(false) # Someone has already taken mutex here
108
+ ensure
109
+ @mutex.unlock if take_mutex
110
+ end
102
111
  end
103
112
 
113
+ # Mutex is already taken when this is called so we need to adjust
114
+ # prepare_write and write accordingly
104
115
  def write_buffer
105
- @buffer.each do |entry|
106
- write(*entry)
116
+ begin
117
+ @buffer.each do |entry|
118
+ write(*entry, allow_new_file: false, take_mutex: false)
119
+ end
120
+ rescue => err
121
+ Logger.instance.error "Error writing out buffer : #{err.formatted}"
107
122
  end
108
123
  @buffer = []
109
124
  end
110
-
111
- # Need to write out all remaining buffer entries and then shutdown
112
- # Returns thread that moves final log to bucket
113
- def shutdown
114
- write_buffer()
115
- return super()
116
- end
117
125
  end
118
126
  end
@@ -130,6 +130,7 @@ module OpenC3
130
130
  @cleanup_offsets = []
131
131
  @cleanup_times = []
132
132
  @previous_time_nsec_since_epoch = nil
133
+ @tmp_dir = Dir.mktmpdir
133
134
 
134
135
  # This is an optimization to avoid creating a new entry object
135
136
  # each time we create an entry which we do a LOT!
@@ -157,7 +158,7 @@ module OpenC3
157
158
  # Stops all logging and closes the current log file.
158
159
  def stop
159
160
  threads = nil
160
- @mutex.synchronize { @logging_enabled = false; threads = close_file(false) }
161
+ @mutex.synchronize { threads = close_file(false); @logging_enabled = false; }
161
162
  return threads
162
163
  end
163
164
 
@@ -187,10 +188,11 @@ module OpenC3
187
188
  while true
188
189
  filename_parts = [attempt]
189
190
  filename_parts.unshift @label if @label
190
- filename = File.join(Dir.tmpdir, File.build_timestamped_filename([@label, attempt], ext))
191
+ filename = File.join(@tmp_dir, File.build_timestamped_filename([@label, attempt], ext))
191
192
  if File.exist?(filename)
192
193
  attempt ||= 0
193
194
  attempt += 1
195
+ Logger.warn("Unexpected file name conflict: #{filename}")
194
196
  else
195
197
  return filename
196
198
  end
@@ -260,7 +262,7 @@ module OpenC3
260
262
  # wrapped with a rescue and handled with handle_critical_exception
261
263
  # Assumes mutex has already been taken
262
264
  def start_new_file
263
- close_file(false)
265
+ close_file(false) if @file
264
266
 
265
267
  # Start log file
266
268
  @filename = create_unique_filename()
@@ -279,16 +281,16 @@ module OpenC3
279
281
  OpenC3.handle_critical_exception(err)
280
282
  end
281
283
 
282
- def prepare_write(time_nsec_since_epoch, data_length, redis_topic = nil, redis_offset = nil)
284
+ def prepare_write(time_nsec_since_epoch, data_length, redis_topic = nil, redis_offset = nil, allow_new_file: true)
283
285
  # This check includes logging_enabled again because it might have changed since we acquired the mutex
284
286
  # Ensures new files based on size, and ensures always increasing time order in files
285
287
  if @logging_enabled
286
288
  if !@file
287
289
  Logger.debug("Log writer start new file because no file opened")
288
- start_new_file()
290
+ start_new_file() if allow_new_file
289
291
  elsif @cycle_size and ((@file_size + data_length) > @cycle_size)
290
292
  Logger.debug("Log writer start new file due to cycle size #{@cycle_size}")
291
- start_new_file()
293
+ start_new_file() if allow_new_file
292
294
  elsif @enforce_time_order and @previous_time_nsec_since_epoch and (@previous_time_nsec_since_epoch > time_nsec_since_epoch)
293
295
  # Warning: Creating new files here can cause lots of files to be created if packets make it through out of order
294
296
  # Changed to just a error to prevent file thrashing
@@ -315,6 +317,9 @@ module OpenC3
315
317
  Logger.debug "Log File Closed : #{@filename}"
316
318
  date = first_timestamp[0..7] # YYYYMMDD
317
319
  bucket_key = File.join(@remote_log_directory, date, bucket_filename())
320
+ # Cleanup timestamps here so they are unset for the next file
321
+ @first_time = nil
322
+ @last_time = nil
318
323
  threads << BucketUtilities.move_log_file_to_bucket(@filename, bucket_key)
319
324
  # Now that the file is in storage, trim the Redis stream after a delay
320
325
  @cleanup_offsets << {}
@@ -324,7 +329,7 @@ module OpenC3
324
329
  @cleanup_times << Time.now + CLEANUP_DELAY
325
330
  @last_offsets.clear
326
331
  rescue Exception => err
327
- Logger.instance.error "Error closing #{@filename} : #{err.formatted}"
332
+ Logger.error "Error closing #{@filename} : #{err.formatted}"
328
333
  end
329
334
 
330
335
  @file = nil
@@ -97,12 +97,15 @@ module OpenC3
97
97
  # @param data [String] Binary string of data
98
98
  # @param id [Integer] Target ID
99
99
  # @param redis_offset [Integer] The offset of this packet in its Redis stream
100
- def write(entry_type, cmd_or_tlm, target_name, packet_name, time_nsec_since_epoch, stored, data, id = nil, redis_topic = nil, redis_offset = '0-0')
100
+ def write(entry_type, cmd_or_tlm, target_name, packet_name, time_nsec_since_epoch, stored, data, id = nil, redis_topic = nil, redis_offset = '0-0', take_mutex: true, allow_new_file: true)
101
101
  return if !@logging_enabled
102
102
 
103
- @mutex.synchronize do
104
- prepare_write(time_nsec_since_epoch, data.length, redis_topic, redis_offset)
103
+ @mutex.lock if take_mutex
104
+ begin
105
+ prepare_write(time_nsec_since_epoch, data.length, redis_topic, redis_offset, allow_new_file: allow_new_file)
105
106
  write_entry(entry_type, cmd_or_tlm, target_name, packet_name, time_nsec_since_epoch, stored, data, id) if @file
107
+ ensure
108
+ @mutex.unlock if take_mutex
106
109
  end
107
110
  rescue => err
108
111
  Logger.instance.error "Error writing #{@filename} : #{err.formatted}"
@@ -121,7 +124,6 @@ module OpenC3
121
124
  @index_filename = create_unique_filename('.idx'.freeze)
122
125
  @index_file = File.new(@index_filename, 'wb')
123
126
  @index_file.write(OPENC3_INDEX_HEADER)
124
-
125
127
  @cmd_packet_table = {}
126
128
  @tlm_packet_table = {}
127
129
  @key_map_table = {}
@@ -149,8 +151,6 @@ module OpenC3
149
151
  write_entry(:OFFSET_MARKER, nil, nil, nil, nil, nil, last_offset + ',' + redis_topic, nil) if @file
150
152
  end
151
153
 
152
- threads.concat(super(false))
153
-
154
154
  if @index_file
155
155
  begin
156
156
  write_index_file_footer()
@@ -166,6 +166,9 @@ module OpenC3
166
166
  @index_file = nil
167
167
  @index_filename = nil
168
168
  end
169
+
170
+ threads.concat(super(false))
171
+
169
172
  ensure
170
173
  @mutex.unlock if take_mutex
171
174
  end
@@ -60,6 +60,10 @@ module OpenC3
60
60
  handle_inject_tlm(msg_hash['inject_tlm'])
61
61
  next
62
62
  end
63
+ if msg_hash.key?('build_cmd')
64
+ handle_build_cmd(msg_hash['build_cmd'], msg_id)
65
+ next
66
+ end
63
67
  else
64
68
  decom_packet(topic, msg_id, msg_hash, redis)
65
69
  @metric.set(name: 'decom_total', value: @count, type: 'counter')
@@ -37,5 +37,37 @@ module OpenC3
37
37
  packet.received_time = Time.now.sys
38
38
  TelemetryTopic.write_packet(packet, scope: @scope)
39
39
  end
40
+
41
+ def handle_build_cmd(build_cmd_json, msg_id)
42
+ build_cmd_hash = JSON.parse(build_cmd_json, allow_nan: true, create_additions: true)
43
+ target_name = build_cmd_hash['target_name']
44
+ cmd_name = build_cmd_hash['cmd_name']
45
+ cmd_params = build_cmd_hash['cmd_params']
46
+ range_check = build_cmd_hash['range_check']
47
+ raw = build_cmd_hash['raw']
48
+ ack_topic = "{#{@scope}__ACKCMD}TARGET__#{target_name}"
49
+ begin
50
+ command = System.commands.build_cmd(target_name, cmd_name, cmd_params, range_check, raw)
51
+ msg_hash = {
52
+ id: msg_id,
53
+ result: 'SUCCESS',
54
+ time: command.packet_time.to_nsec_from_epoch,
55
+ received_time: command.received_time.to_nsec_from_epoch,
56
+ target_name: command.target_name,
57
+ packet_name: command.packet_name,
58
+ received_count: command.received_count,
59
+ buffer: command.buffer(false)
60
+ }
61
+ # If there is an error due to parameter out of range, etc, we rescue it so we can
62
+ # write the ACKCMD}TARGET topic and allow the TelemetryDecomTopic.build_cmd to return
63
+ rescue => error
64
+ msg_hash = {
65
+ id: msg_id,
66
+ result: 'ERROR',
67
+ message: error.message
68
+ }
69
+ end
70
+ Topic.write_topic(ack_topic, msg_hash)
71
+ end
40
72
  end
41
73
  end
@@ -386,7 +386,7 @@ module OpenC3
386
386
  # Write out all entries in progress
387
387
  write_all_entries(reducer_state, plw, type, target_name, stored)
388
388
  reducer_state.clear
389
- plw.start_new_file(true) # Automatically closes the current file
389
+ plw.close_file
390
390
  return true
391
391
  else
392
392
  return false
@@ -450,20 +450,24 @@ module OpenC3
450
450
  # We've collected all the values so calculate the AVG and STDDEV
451
451
  if type == 'minute'
452
452
  raw_keys.each do |key|
453
- reduced["_NUM_SAMPLES"] ||= reduced["#{key}__VALS"].length # Keep a single sample count per packet
454
- reduced["#{key}__A"], reduced["#{key}__S"] =
455
- Math.stddev_population(reduced["#{key}__VALS"])
456
- # Remove the raw values as they're only used for AVG / STDDEV calculation
457
- reduced.delete("#{key}__VALS")
453
+ if reduced["#{key}__VALS"]
454
+ reduced["_NUM_SAMPLES"] ||= reduced["#{key}__VALS"].length # Keep a single sample count per packet
455
+ reduced["#{key}__A"], reduced["#{key}__S"] =
456
+ Math.stddev_population(reduced["#{key}__VALS"])
457
+ # Remove the raw values as they're only used for AVG / STDDEV calculation
458
+ reduced.delete("#{key}__VALS")
459
+ end
458
460
  end
459
461
 
460
462
  converted_keys.each do |key|
461
- reduced["_NUM_SAMPLES"] ||= reduced["#{key}__CVALS"].length # Keep a single sample count per packet
462
- reduced["#{key}__CA"], reduced["#{key}__CS"] =
463
- Math.stddev_population(reduced["#{key}__CVALS"])
463
+ if reduced["#{key}__CVALS"]
464
+ reduced["_NUM_SAMPLES"] ||= reduced["#{key}__CVALS"].length # Keep a single sample count per packet
465
+ reduced["#{key}__CA"], reduced["#{key}__CS"] =
466
+ Math.stddev_population(reduced["#{key}__CVALS"])
464
467
 
465
- # Remove the converted values as they're only used for AVG / STDDEV calculation
466
- reduced.delete("#{key}__CVALS")
468
+ # Remove the converted values as they're only used for AVG / STDDEV calculation
469
+ reduced.delete("#{key}__CVALS")
470
+ end
467
471
  end
468
472
  else
469
473
  samples = reduced["_NUM_SAMPLES__VALS"]
@@ -40,9 +40,16 @@ module OpenC3
40
40
  Store.hset("#{scope}__tlm__#{target_name}", packet_name, JSON.generate(hash.as_json(:allow_nan => true)))
41
41
  end
42
42
 
43
+ # Get the hash for packet in the CVT
44
+ def self.get(target_name:, packet_name:, scope: $openc3_scope)
45
+ packet = Store.hget("#{scope}__tlm__#{target_name}", packet_name)
46
+ raise "Packet '#{target_name} #{packet_name}' does not exist" unless packet
47
+ JSON.parse(packet, :allow_nan => true, :create_additions => true)
48
+ end
49
+
43
50
  # Set an item in the current value table
44
51
  def self.set_item(target_name, packet_name, item_name, value, type:, scope: $openc3_scope)
45
- hash = JSON.parse(Store.hget("#{scope}__tlm__#{target_name}", packet_name), :allow_nan => true, :create_additions => true)
52
+ hash = get(target_name: target_name, packet_name: packet_name, scope: scope)
46
53
  case type
47
54
  when :WITH_UNITS
48
55
  hash["#{item_name}__U"] = value.to_s # WITH_UNITS should always be a string
@@ -87,7 +94,7 @@ module OpenC3
87
94
  result = JSON.parse(overrides, :allow_nan => true, :create_additions => true)[override_key]
88
95
  return result if result
89
96
  end
90
- hash = JSON.parse(Store.hget("#{scope}__tlm__#{target_name}", packet_name), :allow_nan => true, :create_additions => true)
97
+ hash = get(target_name: target_name, packet_name: packet_name, scope: scope)
91
98
  hash.values_at(*types).each do |result|
92
99
  if result
93
100
  if type == :FORMATTED or type == :WITH_UNITS
@@ -115,9 +122,7 @@ module OpenC3
115
122
 
116
123
  lookups.each do |target_packet_key, target_name, packet_name, value_keys|
117
124
  unless packet_lookup[target_packet_key]
118
- packet = Store.hget("#{scope}__tlm__#{target_name}", packet_name)
119
- raise "Packet '#{target_name} #{packet_name}' does not exist" unless packet
120
- packet_lookup[target_packet_key] = JSON.parse(packet, :allow_nan => true, :create_additions => true)
125
+ packet_lookup[target_packet_key] = get(target_name: target_name, packet_name: packet_name, scope: scope)
121
126
  end
122
127
  hash = packet_lookup[target_packet_key]
123
128
  item_result = []