openc3 5.15.1 → 5.16.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Rakefile +1 -0
  4. data/bin/openc3cli +20 -0
  5. data/bin/pipinstall +3 -0
  6. data/data/config/interface_modifiers.yaml +4 -1
  7. data/data/config/telemetry_modifiers.yaml +6 -1
  8. data/data/config/widgets.yaml +1 -1
  9. data/ext/openc3/ext/burst_protocol/burst_protocol.c +317 -0
  10. data/ext/openc3/ext/burst_protocol/extconf.rb +13 -0
  11. data/lib/openc3/accessors/accessor.rb +1 -1
  12. data/lib/openc3/accessors/json_accessor.rb +11 -3
  13. data/lib/openc3/api/tlm_api.rb +1 -1
  14. data/lib/openc3/interfaces/http_client_interface.rb +8 -4
  15. data/lib/openc3/interfaces/http_server_interface.rb +22 -6
  16. data/lib/openc3/interfaces/interface.rb +6 -0
  17. data/lib/openc3/interfaces/linc_interface.rb +5 -3
  18. data/lib/openc3/interfaces/mqtt_interface.rb +7 -3
  19. data/lib/openc3/interfaces/mqtt_stream_interface.rb +8 -1
  20. data/lib/openc3/interfaces/protocols/burst_protocol.rb +104 -100
  21. data/lib/openc3/interfaces/protocols/fixed_protocol.rb +11 -3
  22. data/lib/openc3/interfaces/serial_interface.rb +16 -1
  23. data/lib/openc3/interfaces/simulated_target_interface.rb +7 -3
  24. data/lib/openc3/interfaces/tcpip_client_interface.rb +18 -1
  25. data/lib/openc3/interfaces/tcpip_server_interface.rb +24 -15
  26. data/lib/openc3/interfaces/udp_interface.rb +11 -1
  27. data/lib/openc3/io/posix_serial_driver.rb +20 -5
  28. data/lib/openc3/logs/packet_log_writer.rb +1 -1
  29. data/lib/openc3/microservices/decom_microservice.rb +3 -2
  30. data/lib/openc3/microservices/interface_microservice.rb +5 -5
  31. data/lib/openc3/models/activity_model.rb +104 -40
  32. data/lib/openc3/models/gem_model.rb +1 -1
  33. data/lib/openc3/models/plugin_model.rb +5 -3
  34. data/lib/openc3/models/python_package_model.rb +15 -5
  35. data/lib/openc3/models/scope_model.rb +1 -1
  36. data/lib/openc3/models/target_model.rb +1 -1
  37. data/lib/openc3/packets/packet.rb +27 -24
  38. data/lib/openc3/packets/packet_config.rb +18 -1
  39. data/lib/openc3/packets/parsers/packet_item_parser.rb +10 -6
  40. data/lib/openc3/packets/structure.rb +7 -7
  41. data/lib/openc3/packets/structure_item.rb +4 -2
  42. data/lib/openc3/script/api_shared.rb +33 -29
  43. data/lib/openc3/script/plugins.rb +13 -13
  44. data/lib/openc3/script/storage.rb +3 -4
  45. data/lib/openc3/script/web_socket_api.rb +10 -0
  46. data/lib/openc3/version.rb +6 -6
  47. data/templates/target/targets/TARGET/lib/target.py +2 -0
  48. data/templates/tool_angular/package.json +8 -8
  49. data/templates/tool_react/package.json +2 -2
  50. data/templates/tool_svelte/build/smui.css +1 -5
  51. data/templates/tool_svelte/package.json +3 -3
  52. data/templates/tool_vue/package.json +12 -12
  53. data/templates/widget/package.json +11 -11
  54. metadata +21 -18
@@ -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 2024, 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,6 +24,7 @@
24
24
 
25
25
  require 'openc3/models/model'
26
26
  require 'openc3/topics/timeline_topic'
27
+ require 'securerandom'
27
28
 
28
29
  module OpenC3
29
30
  class ActivityError < StandardError; end
@@ -128,7 +129,7 @@ module OpenC3
128
129
  self.new(**json.transform_keys(&:to_sym), name: name, scope: scope)
129
130
  end
130
131
 
131
- attr_reader :duration, :start, :stop, :kind, :data, :events, :fulfillment
132
+ attr_reader :start, :stop, :kind, :data, :events, :fulfillment, :recurring
132
133
 
133
134
  def initialize(
134
135
  name:,
@@ -138,9 +139,9 @@ module OpenC3
138
139
  data:,
139
140
  scope:,
140
141
  updated_at: 0,
141
- duration: 0,
142
142
  fulfillment: nil,
143
- events: nil
143
+ events: nil,
144
+ recurring: {}
144
145
  )
145
146
  super("#{scope}#{PRIMARY_KEY}__#{name}", name: name, scope: scope)
146
147
  set_input(
@@ -150,22 +151,24 @@ module OpenC3
150
151
  kind: kind,
151
152
  data: data,
152
153
  events: events,
154
+ recurring: recurring,
153
155
  )
154
156
  @updated_at = updated_at
155
157
  end
156
158
 
157
- # validate_time will be called on create this will pull the time up to MAX_DURATION of an activity
158
- # this will make sure that the activity is the only activity on the timeline for the duration of the
159
- # activity. Score is the Seconds since the Unix Epoch: (%s) Number of seconds since 1970-01-01 00:00:00 UTC.
160
- # We then search back from the stop of the activity and check to see if any activities are in the
161
- # last x seconds (MAX_DURATION), if the zrange rev byscore finds activites from in reverse order so the
162
- # first task is the closest task to the current score. In this a parameter ignore_score allows the request
163
- # to ignore that time and skip to the next time but if nothing is found in the time range we can return nil.
159
+ # validate_time searches from the current activity @stop - 1 (because we allow overlap of stop with start)
160
+ # back through @start - MAX_DURATION. The method is trying to validate that this new activity does not
161
+ # overlap with anything else. The reason we search back past @start through MAX_DURATION is because we
162
+ # need to return all the activities that may start before us and verify that we don't overlap them.
163
+ # Activities are only inserted by @start time so we need to go back to verify we don't overlap existing @stop.
164
+ # Note: Score is the Seconds since the Unix Epoch: (%s) Number of seconds since 1970-01-01 00:00:00 UTC.
165
+ # zrange rev byscore finds activites from in reverse order so the first task is the closest task to the current score.
166
+ # In this case a parameter ignore_score allows the request to ignore that time and skip to the next time
167
+ # but if nothing is found in the time range we can return nil.
164
168
  #
165
169
  # @param [Integer] ignore_score - should be nil unless you want to ignore a time when doing an update
166
170
  def validate_time(ignore_score = nil)
167
- max_score = @start - MAX_DURATION
168
- array = Store.zrevrangebyscore(@primary_key, @stop, max_score)
171
+ array = Store.zrevrangebyscore(@primary_key, @stop - 1, @start - MAX_DURATION)
169
172
  array.each do |value|
170
173
  activity = JSON.parse(value, :allow_nan => true, :create_additions => true)
171
174
  if ignore_score == activity['start']
@@ -190,10 +193,14 @@ module OpenC3
190
193
  DateTime.strptime(start.to_s, '%s')
191
194
  DateTime.strptime(stop.to_s, '%s')
192
195
  rescue Date::Error
193
- raise ActivityInputError.new "failed validation input must be seconds: #{start}, #{stop}"
196
+ raise ActivityInputError.new "start and stop must be seconds: #{start}, #{stop}"
197
+ end
198
+ now_i = Time.now.to_i
199
+ begin
200
+ duration = stop - start
201
+ rescue NoMethodError
202
+ raise ActivityInputError.new "start and stop must be seconds: #{start}, #{stop}"
194
203
  end
195
- now_i = Time.now.to_i + 10
196
- duration = stop - start
197
204
  if now_i >= start
198
205
  raise ActivityInputError.new "activity must be in the future, current_time: #{now_i} vs #{start}"
199
206
  elsif duration >= MAX_DURATION
@@ -210,47 +217,92 @@ module OpenC3
210
217
  end
211
218
 
212
219
  # Set the values of the instance, @start, @kind, @data, @events...
213
- def set_input(start:, stop:, kind: nil, data: nil, events: nil, fulfillment: nil)
214
- begin
215
- DateTime.strptime(start.to_s, '%s')
216
- DateTime.strptime(stop.to_s, '%s')
217
- rescue ArgumentError
218
- raise ActivityInputError.new "invalid input must be seconds: #{start}, #{stop}"
219
- end
220
+ def set_input(start:, stop:, kind: nil, data: nil, events: nil, fulfillment: nil, recurring: nil)
221
+ validate_input(start: start, stop: stop, kind: kind, data: data)
220
222
  @start = start
221
223
  @stop = stop
222
- @duration = @stop - @start
223
224
  @fulfillment = fulfillment.nil? ? false : fulfillment
224
225
  @kind = kind.nil? ? @kind : kind
225
226
  @data = data.nil? ? @data : data
226
227
  @events = events.nil? ? Array.new : events
228
+ @recurring = recurring.nil? ? @recurring : recurring
227
229
  end
228
230
 
229
231
  # Update the Redis hash at primary_key and set the score equal to the start Epoch time
230
232
  # the member is set to the JSON generated via calling as_json
231
233
  def create
232
- validate_input(start: @start, stop: @stop, kind: @kind, data: @data)
233
- collision = validate_time()
234
- unless collision.nil?
235
- raise ActivityOverlapError.new "no activities can overlap, collision: #{collision}"
236
- end
234
+ if @recurring['end'] and @recurring['frequency'] and @recurring['span']
235
+ # First validate the initial recurring activity ... all others are just offsets
236
+ validate_input(start: @start, stop: @stop, kind: @kind, data: @data)
237
+
238
+ # Create a uuid for deleting related recurring in the future
239
+ @recurring['uuid'] = SecureRandom.uuid
240
+ @recurring['start'] = @start
241
+ duration = @stop - @start
242
+ recurrance = 0
243
+ case @recurring['span']
244
+ when 'minutes'
245
+ recurrance = @recurring['frequency'].to_i * 60
246
+ when 'hours'
247
+ recurrance = @recurring['frequency'].to_i * 3600
248
+ when 'days'
249
+ recurrance = @recurring['frequency'].to_i * 86400
250
+ end
237
251
 
238
- @updated_at = Time.now.to_nsec_from_epoch
239
- add_event(status: 'created')
240
- Store.zadd(@primary_key, @start, JSON.generate(self.as_json(:allow_nan => true)))
241
- notify(kind: 'created')
252
+ # Get all the existing events in the recurring time range as well as those before
253
+ # the start of the recurring time range to ensure we don't start inside an existing event
254
+ existing = Store.zrevrangebyscore(@primary_key, @recurring['end'] - 1, @recurring['start'] - MAX_DURATION)
255
+ existing.map! {|value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
256
+ last_stop = nil
257
+
258
+ # Update @updated_at and add an event assuming it all completes ok
259
+ @updated_at = Time.now.to_nsec_from_epoch
260
+ add_event(status: 'created')
261
+
262
+ Store.multi do |multi|
263
+ (@start..@recurring['end']).step(recurrance).each do |start_time|
264
+ @start = start_time
265
+ @stop = start_time + duration
266
+
267
+ if last_stop and @start < last_stop
268
+ @events.pop # Remove previously created event
269
+ raise ActivityOverlapError.new "Recurring activity overlap. Increase recurrance delta or decrease activity duration."
270
+ end
271
+ existing.each do |value|
272
+ if (@start >= value['start'] and @start < value['stop']) ||
273
+ (@stop > value['start'] and @stop <= value['stop'])
274
+ @events.pop # Remove previously created event
275
+ raise ActivityOverlapError.new "activity overlaps existing at #{value['start']}"
276
+ end
277
+ end
278
+ multi.zadd(@primary_key, @start, JSON.generate(self.as_json(:allow_nan => true)))
279
+ last_stop = @stop
280
+ end
281
+ end
282
+ notify(kind: 'created')
283
+ else
284
+ validate_input(start: @start, stop: @stop, kind: @kind, data: @data)
285
+ collision = validate_time()
286
+ unless collision.nil?
287
+ raise ActivityOverlapError.new "activity overlaps existing at #{collision}"
288
+ end
289
+
290
+ @updated_at = Time.now.to_nsec_from_epoch
291
+ add_event(status: 'created')
292
+ Store.zadd(@primary_key, @start, JSON.generate(self.as_json(:allow_nan => true)))
293
+ notify(kind: 'created')
294
+ end
242
295
  end
243
296
 
244
297
  # Update the Redis hash at primary_key and remove the current activity at the current score
245
298
  # and update the score to the new score equal to the start Epoch time this uses a multi
246
- # to execute both the remove and create. The member via the JSON generated via calling as_json
299
+ # to execute both the remove and create.
247
300
  def update(start:, stop:, kind:, data:)
248
301
  array = Store.zrangebyscore(@primary_key, @start, @start)
249
302
  if array.length == 0
250
303
  raise ActivityError.new "failed to find activity at: #{@start}"
251
304
  end
252
305
 
253
- validate_input(start: start, stop: stop, kind: kind, data: data)
254
306
  old_start = @start
255
307
  set_input(start: start, stop: stop, kind: kind, data: data, events: @events)
256
308
  @updated_at = Time.now.to_nsec_from_epoch
@@ -299,12 +351,24 @@ module OpenC3
299
351
  end
300
352
 
301
353
  # destroy the activity from the redis database
302
- def destroy
303
- Store.zremrangebyscore(@primary_key, @start, @start)
354
+ def destroy(recurring: false)
355
+ # Delete all recurring activities
356
+ if recurring and @recurring['end'] and @recurring['uuid']
357
+ uuid = @recurring['uuid']
358
+ array = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{@name}", @recurring['start'], @recurring['end'])
359
+ array.each do |value|
360
+ model = ActivityModel.from_json(value, name: @name, scope: @scope)
361
+ if model.recurring['uuid'] == uuid
362
+ Store.zremrangebyscore(@primary_key, model.start, model.start)
363
+ end
364
+ end
365
+ else
366
+ Store.zremrangebyscore(@primary_key, @start, @start)
367
+ end
304
368
  notify(kind: 'deleted')
305
369
  end
306
370
 
307
- # @return [] update the redis stream / timeline topic that something has changed
371
+ # update the redis stream / timeline topic that something has changed
308
372
  def notify(kind:, extra: nil)
309
373
  notification = {
310
374
  'data' => JSON.generate(as_json(:allow_nan => true)),
@@ -326,12 +390,12 @@ module OpenC3
326
390
  'name' => @name,
327
391
  'updated_at' => @updated_at,
328
392
  'fulfillment' => @fulfillment,
329
- 'duration' => @duration,
330
393
  'start' => @start,
331
394
  'stop' => @stop,
332
395
  'kind' => @kind,
333
396
  'events' => @events,
334
- 'data' => @data.as_json(*a)
397
+ 'data' => @data.as_json(*a),
398
+ 'recurring' => @recurring.as_json(*a)
335
399
  }
336
400
  end
337
401
  end
@@ -36,7 +36,7 @@ module OpenC3
36
36
  # and destroy to allow interaction with gem files from the PluginModel and
37
37
  # the GemsController.
38
38
  class GemModel
39
- include Api
39
+ extend Api
40
40
 
41
41
  def self.names
42
42
  if Dir.exist?("#{ENV['GEM_HOME']}/gems")
@@ -44,7 +44,7 @@ module OpenC3
44
44
  # microservices and tools. The PluginModel installs all these pieces as well
45
45
  # as destroys them all when the plugin is removed.
46
46
  class PluginModel < Model
47
- include Api
47
+ extend Api
48
48
 
49
49
  PRIMARY_KEY = 'openc3_plugins'
50
50
  # Reserved VARIABLE names. See local_mode.rb: update_local_plugin()
@@ -198,8 +198,10 @@ module OpenC3
198
198
  pypi_url ||= 'https://pypi.org/simple'
199
199
  end
200
200
  end
201
- Logger.info "Installing python packages from requirements.txt with pypi_url=#{pypi_url}"
202
- puts `/openc3/bin/pipinstall --user -i #{pypi_url} -r #{File.join(gem_path, 'requirements.txt')}`
201
+ unless validate_only
202
+ Logger.info "Installing python packages from requirements.txt with pypi_url=#{pypi_url}"
203
+ puts `/openc3/bin/pipinstall --user --no-warn-script-location -i #{pypi_url} -r #{File.join(gem_path, 'requirements.txt')}`
204
+ end
203
205
  needs_dependencies = true
204
206
  end
205
207
 
@@ -18,6 +18,7 @@
18
18
 
19
19
  require 'fileutils'
20
20
  require 'openc3/utilities/process_manager'
21
+ require 'openc3/api/api'
21
22
  require 'pathname'
22
23
 
23
24
  module OpenC3
@@ -26,6 +27,8 @@ module OpenC3
26
27
  # and destroy to allow interaction with python package files from the PluginModel and
27
28
  # the PackagesController.
28
29
  class PythonPackageModel
30
+ extend Api
31
+
29
32
  def self.names
30
33
  paths = Dir.glob("#{ENV['PYTHONUSERBASE']}/lib/*")
31
34
  results = []
@@ -71,16 +74,23 @@ module OpenC3
71
74
  package_filename = File.basename(package_file_path)
72
75
  begin
73
76
  pypi_url = get_setting('pypi_url', scope: scope)
74
- rescue
75
- # If Redis isn't running try the ENV, then simply pypi.org/simple
76
- pypi_url = ENV['PYPI_URL']
77
77
  if pypi_url
78
78
  pypi_url += '/simple'
79
79
  end
80
- pypi_url ||= 'https://pypi.org/simple'
80
+ rescue => e
81
+ Logger.error("Failed to retrieve pypi_url: #{e.formatted}")
82
+ ensure
83
+ if pypi_url.nil?
84
+ # If Redis isn't running try the ENV, then simply pypi.org/simple
85
+ pypi_url = ENV['PYPI_URL']
86
+ if pypi_url
87
+ pypi_url += '/simple'
88
+ end
89
+ pypi_url ||= 'https://pypi.org/simple'
90
+ end
81
91
  end
82
92
  Logger.info "Installing python package: #{name_or_path}"
83
- result = OpenC3::ProcessManager.instance.spawn(["pip", "install", "--user", "-i", pypi_url, package_file_path], "package_install", package_filename, Time.now + 3600.0, scope: scope)
93
+ result = OpenC3::ProcessManager.instance.spawn(["/openc3/bin/pipinstall", "--user", "--no-warn-script-location", "-i", pypi_url, package_file_path], "package_install", package_filename, Time.now + 3600.0, scope: scope)
84
94
  return result.name
85
95
  end
86
96
 
@@ -306,7 +306,7 @@ module OpenC3
306
306
  setting = SettingModel.get(name: 'rubygems_url')
307
307
  SettingModel.set({ name: 'rubygems_url', data: 'https://rubygems.org' }, scope: @scope) unless setting
308
308
  setting = SettingModel.get(name: 'pypi_url')
309
- SettingModel.set({ name: 'pypi_url', data: 'https://pypi.org/simple' }, scope: @scope) unless setting
309
+ SettingModel.set({ name: 'pypi_url', data: 'https://pypi.org' }, scope: @scope) unless setting
310
310
  end
311
311
  end
312
312
  end
@@ -405,7 +405,7 @@ module OpenC3
405
405
  'ignored_items' => @ignored_items,
406
406
  'limits_groups' => @limits_groups,
407
407
  'cmd_tlm_files' => @cmd_tlm_files,
408
- 'cmd_unique_id_mode' => cmd_unique_id_mode,
408
+ 'cmd_unique_id_mode' => @cmd_unique_id_mode,
409
409
  'tlm_unique_id_mode' => @tlm_unique_id_mode,
410
410
  'id' => @id,
411
411
  'updated_at' => @updated_at,
@@ -101,6 +101,9 @@ module OpenC3
101
101
  # @return [Array<Array<Target Name, Packet Name, Item Name>>] Related items
102
102
  attr_accessor :related_items
103
103
 
104
+ # @return [Boolean] Whether to ignore overlapping items
105
+ attr_accessor :ignore_overlap
106
+
104
107
  # Valid format types
105
108
  VALUE_TYPES = [:RAW, :CONVERTED, :FORMATTED, :WITH_UNITS]
106
109
 
@@ -140,6 +143,7 @@ module OpenC3
140
143
  @cmd_or_tlm = nil
141
144
  @template = nil
142
145
  @packet_time = nil
146
+ @ignore_overlap = false
143
147
  end
144
148
 
145
149
  # Sets the target name this packet is associated with. Unidentified packets
@@ -151,12 +155,10 @@ module OpenC3
151
155
  if !(String === target_name)
152
156
  raise(ArgumentError, "target_name must be a String but is a #{target_name.class}")
153
157
  end
154
-
155
158
  @target_name = target_name.upcase.freeze
156
159
  else
157
160
  @target_name = nil
158
161
  end
159
- @target_name
160
162
  end
161
163
 
162
164
  # Sets the packet name. Unidentified packets will have packet name set to
@@ -168,12 +170,10 @@ module OpenC3
168
170
  if !(String === packet_name)
169
171
  raise(ArgumentError, "packet_name must be a String but is a #{packet_name.class}")
170
172
  end
171
-
172
173
  @packet_name = packet_name.upcase.freeze
173
174
  else
174
175
  @packet_name = nil
175
176
  end
176
- @packet_name
177
177
  end
178
178
 
179
179
  # Sets the description of the packet
@@ -184,12 +184,10 @@ module OpenC3
184
184
  if !(String === description)
185
185
  raise(ArgumentError, "description must be a String but is a #{description.class}")
186
186
  end
187
-
188
187
  @description = description.to_utf8.freeze
189
188
  else
190
189
  @description = nil
191
190
  end
192
- @description
193
191
  end
194
192
 
195
193
  # Sets the received time of the packet
@@ -200,13 +198,11 @@ module OpenC3
200
198
  if !(Time === received_time)
201
199
  raise(ArgumentError, "received_time must be a Time but is a #{received_time.class}")
202
200
  end
203
-
204
201
  @received_time = received_time.clone.freeze
205
202
  else
206
203
  @received_time = nil
207
204
  end
208
205
  @read_conversion_cache.clear if @read_conversion_cache
209
- @received_time
210
206
  end
211
207
 
212
208
  # Sets the received count of the packet
@@ -217,10 +213,8 @@ module OpenC3
217
213
  if !(Integer === received_count)
218
214
  raise(ArgumentError, "received_count must be an Integer but is a #{received_count.class}")
219
215
  end
220
-
221
216
  @received_count = received_count
222
217
  @read_conversion_cache.clear if @read_conversion_cache
223
- @received_count
224
218
  end
225
219
 
226
220
  end # if RUBY_ENGINE != 'ruby' or ENV['OPENC3_NO_EXT']
@@ -401,6 +395,10 @@ module OpenC3
401
395
  #
402
396
  # @return [Array<String>] Warning messages for big definition overlaps
403
397
  def check_bit_offsets
398
+ if @ignore_overlap
399
+ Logger.instance.info("#{@target_name} #{@packet_name} has IGNORE_OVERLAP so bit overlaps ignored")
400
+ return []
401
+ end
404
402
  expected_next_offset = nil
405
403
  previous_item = nil
406
404
  warnings = []
@@ -617,7 +615,7 @@ module OpenC3
617
615
 
618
616
  unless using_cached_value
619
617
  if item.array_size
620
- value.map! do |val, index|
618
+ value.map! do |val, _index|
621
619
  item.read_conversion.call(val, self, buffer)
622
620
  end
623
621
  else
@@ -646,7 +644,7 @@ module OpenC3
646
644
  # Convert from value to state if possible
647
645
  if item.states
648
646
  if Array === value
649
- value = value.map do |val, index|
647
+ value = value.map do |val, _index|
650
648
  if item.states.key(val)
651
649
  item.states.key(val)
652
650
  elsif item.states.values.include?(ANY_STATE)
@@ -667,7 +665,7 @@ module OpenC3
667
665
  end
668
666
  else
669
667
  if Array === value
670
- value = value.map do |val, index|
668
+ value = value.map do |val, _index|
671
669
  apply_format_string_and_units(item, val, value_type)
672
670
  end
673
671
  else
@@ -693,7 +691,7 @@ module OpenC3
693
691
  # @param value_type [Symbol] Value type to read for every item
694
692
  # @param buffer [String] The binary buffer to read the items from
695
693
  # @return Hash of read names and values
696
- def read_items(items, value_type = :RAW, buffer = @buffer, raw_value = nil)
694
+ def read_items(items, value_type = :RAW, buffer = @buffer, _raw_value = nil)
697
695
  buffer = allocate_buffer_if_needed() unless buffer
698
696
  if value_type == :RAW
699
697
  result = super(items, value_type, buffer)
@@ -735,11 +733,11 @@ module OpenC3
735
733
  end
736
734
  begin
737
735
  super(item, value, :RAW, buffer)
738
- rescue ArgumentError => err
739
- if item.states and String === value and err.message =~ /invalid value for/
736
+ rescue ArgumentError => e
737
+ if item.states and String === value and e.message =~ /invalid value for/
740
738
  raise "Unknown state #{value} for #{item.name}"
741
739
  else
742
- raise err
740
+ raise e
743
741
  end
744
742
  end
745
743
  when :FORMATTED, :WITH_UNITS
@@ -998,7 +996,7 @@ module OpenC3
998
996
  end
999
997
  return unless @processors
1000
998
 
1001
- @processors.each do |processor_name, processor|
999
+ @processors.each do |_processor_name, processor|
1002
1000
  processor.reset
1003
1001
  end
1004
1002
  end
@@ -1051,7 +1049,7 @@ module OpenC3
1051
1049
  config << "COMMAND #{@target_name.to_s.quote_if_necessary} #{@packet_name.to_s.quote_if_necessary} #{@default_endianness} \"#{@description}\"\n"
1052
1050
  end
1053
1051
  if @accessor.class.to_s != 'OpenC3::BinaryAccessor'
1054
- config << " ACCESSOR #{@accessor.class.to_s} #{@accessor.args.map { |a| a.to_s.quote_if_necessary }.join(" ")}\n"
1052
+ config << " ACCESSOR #{@accessor.class} #{@accessor.args.map { |a| a.to_s.quote_if_necessary }.join(" ")}\n"
1055
1053
  end
1056
1054
  # TODO: Add TEMPLATE_ENCODED so this can always be done inline regardless of content
1057
1055
  if @template
@@ -1067,7 +1065,7 @@ module OpenC3
1067
1065
  end
1068
1066
 
1069
1067
  if @processors
1070
- @processors.each do |processor_name, processor|
1068
+ @processors.each do |_processor_name, processor|
1071
1069
  config << processor.to_config
1072
1070
  end
1073
1071
  end
@@ -1106,6 +1104,9 @@ module OpenC3
1106
1104
  config << " RELATED_ITEM #{target_name.to_s.quote_if_necessary} #{packet_name.to_s.quote_if_necessary} #{item_name.to_s.quote_if_necessary}"
1107
1105
  end
1108
1106
  end
1107
+ if @ignore_overlap
1108
+ config << " IGNORE_OVERLAP"
1109
+ end
1109
1110
  config
1110
1111
  end
1111
1112
 
@@ -1128,7 +1129,7 @@ module OpenC3
1128
1129
 
1129
1130
  if @processors
1130
1131
  processors = []
1131
- @processors.each do |processor_name, processor|
1132
+ @processors.each do |_processor_name, processor|
1132
1133
  processors << processor.as_json(*a)
1133
1134
  end
1134
1135
  config['processors'] = processors
@@ -1161,6 +1162,7 @@ module OpenC3
1161
1162
  if @related_items
1162
1163
  config['related_items'] = @related_items
1163
1164
  end
1165
+ config['ignore_overlap'] = true if @ignore_overlap
1164
1166
 
1165
1167
  config
1166
1168
  end
@@ -1182,8 +1184,8 @@ module OpenC3
1182
1184
  else
1183
1185
  packet.accessor = accessor.new(packet)
1184
1186
  end
1185
- rescue => error
1186
- Logger.instance.error "#{packet.target_name} #{packet.packet_name} accessor of #{hash['accessor']} could not be found due to #{error}"
1187
+ rescue => e
1188
+ Logger.instance.error "#{packet.target_name} #{packet.packet_name} accessor of #{hash['accessor']} could not be found due to #{e}"
1187
1189
  end
1188
1190
  end
1189
1191
  packet.template = Base64.decode64(hash['template']) if hash['template']
@@ -1204,6 +1206,7 @@ module OpenC3
1204
1206
  if hash['related_items']
1205
1207
  packet.related_items = hash['related_items']
1206
1208
  end
1209
+ packet.ignore_overlap = hash['ignore_overlap']
1207
1210
 
1208
1211
  packet
1209
1212
  end
@@ -1237,7 +1240,7 @@ module OpenC3
1237
1240
  def process(buffer = @buffer)
1238
1241
  return unless @processors
1239
1242
 
1240
- @processors.each do |processor_name, processor|
1243
+ @processors.each do |_processor_name, processor|
1241
1244
  processor.call(self, buffer)
1242
1245
  end
1243
1246
  end
@@ -220,7 +220,7 @@ module OpenC3
220
220
  'APPEND_PARAMETER', 'APPEND_ID_ITEM', 'APPEND_ID_PARAMETER', 'APPEND_ARRAY_ITEM',\
221
221
  'APPEND_ARRAY_PARAMETER', 'ALLOW_SHORT', 'HAZARDOUS', 'PROCESSOR', 'META',\
222
222
  'DISABLE_MESSAGES', 'HIDDEN', 'DISABLED', 'ACCESSOR', 'TEMPLATE', 'TEMPLATE_FILE',\
223
- 'RESPONSE', 'ERROR_RESPONSE', 'SCREEN', 'RELATED_ITEM'
223
+ 'RESPONSE', 'ERROR_RESPONSE', 'SCREEN', 'RELATED_ITEM', 'IGNORE_OVERLAP'
224
224
  raise parser.error("No current packet for #{keyword}") unless @current_packet
225
225
 
226
226
  process_current_packet(parser, keyword, params)
@@ -509,24 +509,41 @@ module OpenC3
509
509
  when 'RESPONSE'
510
510
  usage = "#{keyword} <Target Name> <Packet Name>"
511
511
  parser.verify_num_parameters(2, 2, usage)
512
+ if @current_cmd_or_tlm == TELEMETRY
513
+ raise parser.error("#{keyword} only applies to command packets")
514
+ end
512
515
  @current_packet.response = [params[0].upcase, params[1].upcase]
513
516
 
514
517
  when 'ERROR_RESPONSE'
515
518
  usage = "#{keyword} <Target Name> <Packet Name>"
516
519
  parser.verify_num_parameters(2, 2, usage)
520
+ if @current_cmd_or_tlm == TELEMETRY
521
+ raise parser.error("#{keyword} only applies to command packets")
522
+ end
517
523
  @current_packet.error_response = [params[0].upcase, params[1].upcase]
518
524
 
519
525
  when 'SCREEN'
520
526
  usage = "#{keyword} <Target Name> <Screen Name>"
521
527
  parser.verify_num_parameters(2, 2, usage)
528
+ if @current_cmd_or_tlm == TELEMETRY
529
+ raise parser.error("#{keyword} only applies to command packets")
530
+ end
522
531
  @current_packet.screen = [params[0].upcase, params[1].upcase]
523
532
 
524
533
  when 'RELATED_ITEM'
525
534
  usage = "#{keyword} <Target Name> <Packet Name> <Item Name>"
526
535
  parser.verify_num_parameters(3, 3, usage)
536
+ if @current_cmd_or_tlm == TELEMETRY
537
+ raise parser.error("#{keyword} only applies to command packets")
538
+ end
527
539
  @current_packet.related_items ||= []
528
540
  @current_packet.related_items << [params[0].upcase, params[1].upcase, params[2].upcase]
529
541
 
542
+ when 'IGNORE_OVERLAP'
543
+ usage = "#{keyword}"
544
+ parser.verify_num_parameters(0, 0, usage)
545
+ @current_packet.ignore_overlap = true
546
+
530
547
  end
531
548
 
532
549
  end
@@ -173,6 +173,8 @@ module OpenC3
173
173
 
174
174
  index = append? ? 3 : 4
175
175
  data_type = get_data_type()
176
+ return [] if data_type == :ARRAY
177
+ return {} if data_type == :OBJECT
176
178
  if data_type == :STRING or data_type == :BLOCK
177
179
  # If the default value is 0x<data> (no quotes), it is treated as
178
180
  # binary data. Otherwise, the default value is considered to be a string.
@@ -241,13 +243,13 @@ module OpenC3
241
243
  def type_usage
242
244
  keyword = @parser.keyword
243
245
  # Item type usage is simple so just return it
244
- return "<TYPE: INT/UINT/FLOAT/STRING/BLOCK/DERIVED> " if keyword.include?("ITEM")
246
+ return "<TYPE: INT/UINT/FLOAT/STRING/BLOCK/DERIVED/ARRAY/OBJECT> " if keyword.include?("ITEM")
245
247
 
246
248
  # Build up the parameter type usage based on the keyword
247
249
  usage = "<TYPE: "
248
250
  # ARRAY types don't have min or max or default values
249
251
  if keyword.include?("ARRAY")
250
- usage << "INT/UINT/FLOAT/STRING/BLOCK> "
252
+ usage << "INT/UINT/FLOAT/STRING/BLOCK/OBJECT> "
251
253
  else
252
254
  begin
253
255
  data_type = get_data_type()
@@ -255,14 +257,16 @@ module OpenC3
255
257
  # If the data type could not be determined set something
256
258
  data_type = :INT
257
259
  end
258
- # STRING and BLOCK types do not have min or max values
260
+ # STRING, BLOCK, ARRAY, OBJECT types do not have min or max values
259
261
  if data_type == :STRING || data_type == :BLOCK
260
- usage << "STRING/BLOCK> "
262
+ usage << "STRING/BLOCK/ARRAY/OBJECT> "
261
263
  else
262
264
  usage << "INT/UINT/FLOAT> <MIN VALUE> <MAX VALUE> "
263
265
  end
264
- # ID Values do not have default values
265
- usage << "<DEFAULT_VALUE> " unless keyword.include?("ID")
266
+ # ID Values do not have default values (or ARRAY/OBJECT)
267
+ unless keyword.include?("ID") or data_type == :ARRAY or data_type == :OBJECT
268
+ usage << "<DEFAULT_VALUE> "
269
+ end
266
270
  end
267
271
  usage
268
272
  end