openc3 5.7.2 → 5.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) 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/operators/operator.rb +3 -0
  24. data/lib/openc3/script/metadata.rb +7 -7
  25. data/lib/openc3/script/screen.rb +5 -5
  26. data/lib/openc3/script/script_runner.rb +17 -17
  27. data/lib/openc3/script/storage.rb +2 -2
  28. data/lib/openc3/topics/config_topic.rb +1 -6
  29. data/lib/openc3/topics/decom_interface_topic.rb +62 -0
  30. data/lib/openc3/topics/telemetry_decom_topic.rb +0 -9
  31. data/lib/openc3/topics/topic.rb +4 -11
  32. data/lib/openc3/utilities/authentication.rb +4 -4
  33. data/lib/openc3/utilities/bucket_require.rb +65 -0
  34. data/lib/openc3/utilities/bucket_utilities.rb +38 -5
  35. data/lib/openc3/utilities/open_telemetry.rb +4 -4
  36. data/lib/openc3/utilities/process_manager.rb +4 -2
  37. data/lib/openc3/utilities/ruby_lex_utils.rb +62 -36
  38. data/lib/openc3/utilities/store_autoload.rb +7 -28
  39. data/lib/openc3/version.rb +6 -6
  40. data/lib/openc3.rb +1 -1
  41. data/templates/widget/package.json +7 -7
  42. data/templates/widget/yarn.lock +46 -47
  43. metadata +55 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2926debd9479ec12631eab7287a0a4ff480c8c4957f7702de4c148f5e87a376
4
- data.tar.gz: 687f5c815c7016f34feb4f484cbcf582c8e5d3ef621098beb0d3aacf612c49c0
3
+ metadata.gz: f6639b1c8a8f8b4902a85d6bdf1f23800aefd25b61b333c661c25236058e9595
4
+ data.tar.gz: e2b037827217d57a34a126189895afc3ff2c62e9758cc326f1cf24be08e1b791
5
5
  SHA512:
6
- metadata.gz: 4a33e7fe434b726dc9231bcf066d112114ca3e8109496ee4c2742b8925d4c3297f22a7e3d43ec5592ba298969eafd9e37713754efc1556f257ae9b584e7ef38c
7
- data.tar.gz: b552b42d338e23625ed78f7fb429deb59a456bd5ab7c783930fe050d8c5ec360c39e2630f39f005c9b370c5f3a49f8d943a04951990edb8e261c5d5e1ab0bd71
6
+ metadata.gz: 6d2b3412ed3db8cdee1772416419737fc895d54145f58c087168c8b8c17649ab155cc36c4563a6bb826852c02ae0f89ac0972f101e28db04fa2cdc9163799732
7
+ data.tar.gz: 39eacdb1118b721017fb84359426576ced5f08be342972a46429797b22e2f6ad46db4999171a03dc310dbb8c41e09d3bbbc8dc86145f807dfa50bfa0ffbacaf3
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 = []