openc3 6.9.2 → 6.10.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 +4 -4
- data/data/config/command_modifiers.yaml +79 -0
- data/data/config/item_modifiers.yaml +5 -0
- data/data/config/parameter_modifiers.yaml +5 -0
- data/data/config/telemetry_modifiers.yaml +79 -0
- data/ext/openc3/ext/packet/packet.c +9 -0
- data/lib/openc3/accessors/accessor.rb +27 -3
- data/lib/openc3/accessors/binary_accessor.rb +21 -4
- data/lib/openc3/accessors/template_accessor.rb +3 -2
- data/lib/openc3/api/cmd_api.rb +7 -3
- data/lib/openc3/api/tlm_api.rb +17 -7
- data/lib/openc3/interfaces/protocols/fixed_protocol.rb +19 -13
- data/lib/openc3/io/json_rpc.rb +6 -0
- data/lib/openc3/microservices/decom_microservice.rb +97 -17
- data/lib/openc3/microservices/interface_decom_common.rb +32 -0
- data/lib/openc3/microservices/interface_microservice.rb +3 -75
- data/lib/openc3/microservices/queue_microservice.rb +16 -1
- data/lib/openc3/migrations/20251022000000_remove_unique_id.rb +23 -0
- data/lib/openc3/models/plugin_model.rb +7 -1
- data/lib/openc3/models/queue_model.rb +32 -5
- data/lib/openc3/models/reaction_model.rb +25 -9
- data/lib/openc3/models/target_model.rb +78 -10
- data/lib/openc3/packets/commands.rb +33 -7
- data/lib/openc3/packets/packet.rb +75 -71
- data/lib/openc3/packets/packet_config.rb +78 -29
- data/lib/openc3/packets/packet_item.rb +11 -103
- data/lib/openc3/packets/parsers/packet_item_parser.rb +177 -34
- data/lib/openc3/packets/parsers/xtce_converter.rb +2 -2
- data/lib/openc3/packets/structure.rb +29 -21
- data/lib/openc3/packets/structure_item.rb +31 -19
- data/lib/openc3/packets/telemetry.rb +37 -11
- data/lib/openc3/script/suite_results.rb +2 -2
- data/lib/openc3/subpacketizers/subpacketizer.rb +18 -0
- data/lib/openc3/system/target.rb +3 -32
- data/lib/openc3/tools/table_manager/table_config.rb +9 -1
- data/lib/openc3/tools/table_manager/table_item_parser.rb +2 -2
- data/lib/openc3/top_level.rb +45 -19
- data/lib/openc3/topics/decom_interface_topic.rb +31 -0
- data/lib/openc3/utilities/env_helper.rb +10 -0
- data/lib/openc3/utilities/logger.rb +7 -11
- data/lib/openc3/version.rb +6 -6
- data/tasks/spec.rake +2 -1
- data/templates/tool_angular/package.json +2 -2
- data/templates/tool_react/package.json +1 -1
- data/templates/tool_svelte/package.json +1 -1
- data/templates/tool_vue/package.json +3 -3
- data/templates/widget/package.json +2 -2
- metadata +4 -1
|
@@ -76,5 +76,37 @@ module OpenC3
|
|
|
76
76
|
end
|
|
77
77
|
Topic.write_topic(ack_topic, msg_hash)
|
|
78
78
|
end
|
|
79
|
+
|
|
80
|
+
def handle_get_tlm_buffer(get_tlm_buffer_json, msg_id)
|
|
81
|
+
get_tlm_buffer_hash = JSON.parse(get_tlm_buffer_json, allow_nan: true, create_additions: true)
|
|
82
|
+
target_name = get_tlm_buffer_hash['target_name']
|
|
83
|
+
packet_name = get_tlm_buffer_hash['packet_name']
|
|
84
|
+
ack_topic = "{#{@scope}__ACKCMD}TARGET__#{target_name}"
|
|
85
|
+
begin
|
|
86
|
+
packet = System.telemetry.packet(target_name, packet_name)
|
|
87
|
+
msg_hash = {
|
|
88
|
+
id: msg_id,
|
|
89
|
+
result: 'SUCCESS',
|
|
90
|
+
time: packet.packet_time.to_nsec_from_epoch,
|
|
91
|
+
received_time: packet.received_time.to_nsec_from_epoch,
|
|
92
|
+
target_name: packet.target_name,
|
|
93
|
+
packet_name: packet.packet_name,
|
|
94
|
+
received_count: packet.received_count,
|
|
95
|
+
stored: packet.stored.to_s,
|
|
96
|
+
buffer: packet.buffer(false)
|
|
97
|
+
}
|
|
98
|
+
msg_hash[:extra] = JSON.generate(packet.extra.as_json, allow_nan: true) if packet.extra
|
|
99
|
+
|
|
100
|
+
# If there is an error due to parameter out of range, etc, we rescue it so we can
|
|
101
|
+
# write the ACKCMD}TARGET topic and allow the source to return
|
|
102
|
+
rescue => error
|
|
103
|
+
msg_hash = {
|
|
104
|
+
id: msg_id,
|
|
105
|
+
result: 'ERROR',
|
|
106
|
+
message: error.message
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
Topic.write_topic(ack_topic, msg_hash)
|
|
110
|
+
end
|
|
79
111
|
end
|
|
80
112
|
end
|
|
@@ -554,23 +554,7 @@ module OpenC3
|
|
|
554
554
|
target = System.targets[target_name]
|
|
555
555
|
target.interface = @interface
|
|
556
556
|
end
|
|
557
|
-
@interface.tlm_target_names
|
|
558
|
-
# Initialize the target's packet counters based on the Topic stream
|
|
559
|
-
# Prevents packet count resetting to 0 when interface restarts
|
|
560
|
-
begin
|
|
561
|
-
System.telemetry.packets(target_name).each do |packet_name, packet|
|
|
562
|
-
topic = "#{@scope}__TELEMETRY__{#{target_name}}__#{packet_name}"
|
|
563
|
-
msg_id, msg_hash = Topic.get_newest_message(topic)
|
|
564
|
-
if msg_id
|
|
565
|
-
packet.received_count = msg_hash['received_count'].to_i
|
|
566
|
-
else
|
|
567
|
-
packet.received_count = 0
|
|
568
|
-
end
|
|
569
|
-
end
|
|
570
|
-
rescue
|
|
571
|
-
# Handle targets without telemetry
|
|
572
|
-
end
|
|
573
|
-
end
|
|
557
|
+
TargetModel.init_tlm_packet_counts(@interface.tlm_target_names, scope: @scope)
|
|
574
558
|
if @interface.connect_on_startup
|
|
575
559
|
@interface.state = 'ATTEMPTING'
|
|
576
560
|
else
|
|
@@ -583,9 +567,6 @@ module OpenC3
|
|
|
583
567
|
end
|
|
584
568
|
|
|
585
569
|
@queued = false
|
|
586
|
-
@sync_packet_count_data = {}
|
|
587
|
-
@sync_packet_count_time = nil
|
|
588
|
-
@sync_packet_count_delay_seconds = 1.0 # Sync packet counts every second
|
|
589
570
|
@interface.options.each do |option_name, option_values|
|
|
590
571
|
if option_name.upcase == 'OPTIMIZE_THROUGHPUT'
|
|
591
572
|
@queued = true
|
|
@@ -594,7 +575,7 @@ module OpenC3
|
|
|
594
575
|
StoreQueued.instance.set_update_interval(update_interval)
|
|
595
576
|
end
|
|
596
577
|
if option_name.upcase == 'SYNC_PACKET_COUNT_DELAY_SECONDS'
|
|
597
|
-
|
|
578
|
+
TargetModel.sync_packet_count_delay_seconds = option_values[0].to_f
|
|
598
579
|
end
|
|
599
580
|
end
|
|
600
581
|
|
|
@@ -777,7 +758,7 @@ module OpenC3
|
|
|
777
758
|
|
|
778
759
|
# Write to stream
|
|
779
760
|
if @interface.tlm_target_enabled[packet.target_name]
|
|
780
|
-
sync_tlm_packet_counts(packet)
|
|
761
|
+
TargetModel.sync_tlm_packet_counts(packet, @interface.tlm_target_names, scope: @scope)
|
|
781
762
|
TelemetryTopic.write_packet(packet, queued: @queued, scope: @scope)
|
|
782
763
|
end
|
|
783
764
|
end
|
|
@@ -912,59 +893,6 @@ module OpenC3
|
|
|
912
893
|
def graceful_kill
|
|
913
894
|
# Just to avoid warning
|
|
914
895
|
end
|
|
915
|
-
|
|
916
|
-
def sync_tlm_packet_counts(packet)
|
|
917
|
-
if @sync_packet_count_delay_seconds <= 0 or $openc3_redis_cluster
|
|
918
|
-
# Perfect but slow method
|
|
919
|
-
packet.received_count = TargetModel.increment_telemetry_count(packet.target_name, packet.packet_name, 1, scope: @scope)
|
|
920
|
-
else
|
|
921
|
-
# Eventually consistent method
|
|
922
|
-
# Only sync every period (default 1 second) to avoid hammering Redis
|
|
923
|
-
# This is a trade off between speed and accuracy
|
|
924
|
-
# The packet count is eventually consistent
|
|
925
|
-
@sync_packet_count_data[packet.target_name] ||= {}
|
|
926
|
-
@sync_packet_count_data[packet.target_name][packet.packet_name] ||= 0
|
|
927
|
-
@sync_packet_count_data[packet.target_name][packet.packet_name] += 1
|
|
928
|
-
|
|
929
|
-
# Ensures counters change between syncs
|
|
930
|
-
packet.received_count += 1
|
|
931
|
-
|
|
932
|
-
# Check if we need to sync the packet counts
|
|
933
|
-
if @sync_packet_count_time.nil? or (Time.now - @sync_packet_count_time) > @sync_packet_count_delay_seconds
|
|
934
|
-
@sync_packet_count_time = Time.now
|
|
935
|
-
|
|
936
|
-
inc_count = 0
|
|
937
|
-
# Use pipeline to make this one transaction
|
|
938
|
-
result = Store.redis_pool.pipelined do
|
|
939
|
-
# Increment global counters for packets received
|
|
940
|
-
@sync_packet_count_data.each do |target_name, packet_data|
|
|
941
|
-
packet_data.each do |packet_name, count|
|
|
942
|
-
TargetModel.increment_telemetry_count(target_name, packet_name, count, scope: @scope)
|
|
943
|
-
inc_count += 1
|
|
944
|
-
end
|
|
945
|
-
end
|
|
946
|
-
@sync_packet_count_data = {}
|
|
947
|
-
|
|
948
|
-
# Get all the packet counts with the global counters
|
|
949
|
-
@interface.tlm_target_names.each do |target_name|
|
|
950
|
-
TargetModel.get_all_telemetry_counts(target_name, scope: @scope)
|
|
951
|
-
end
|
|
952
|
-
TargetModel.get_all_telemetry_counts('UNKNOWN', scope: @scope)
|
|
953
|
-
end
|
|
954
|
-
@interface.tlm_target_names.each do |target_name|
|
|
955
|
-
result[inc_count].each do |packet_name, count|
|
|
956
|
-
update_packet = System.telemetry.packet(target_name, packet_name)
|
|
957
|
-
update_packet.received_count = count.to_i
|
|
958
|
-
end
|
|
959
|
-
inc_count += 1
|
|
960
|
-
end
|
|
961
|
-
result[inc_count].each do |packet_name, count|
|
|
962
|
-
update_packet = System.telemetry.packet('UNKNOWN', packet_name)
|
|
963
|
-
update_packet.received_count = count.to_i
|
|
964
|
-
end
|
|
965
|
-
end
|
|
966
|
-
end
|
|
967
|
-
end
|
|
968
896
|
end
|
|
969
897
|
end
|
|
970
898
|
|
|
@@ -67,7 +67,22 @@ module OpenC3
|
|
|
67
67
|
# OPENC3_DEFAULT_QUEUE is set because commands would be re-queued to the default queue
|
|
68
68
|
# NOTE: cmd() via script rescues hazardous errors and calls prompt_for_hazardous()
|
|
69
69
|
# but we've overridden it to always return true and go straight to cmd_no_hazardous_check()
|
|
70
|
-
|
|
70
|
+
|
|
71
|
+
# Support both new format (target_name, cmd_name, cmd_params) and legacy format (command string)
|
|
72
|
+
if command['target_name'] && command['cmd_name']
|
|
73
|
+
# New format: use 3-parameter cmd() method
|
|
74
|
+
if command['cmd_params']
|
|
75
|
+
cmd_params = JSON.parse(command['cmd_params'], allow_nan: true, create_additions: true)
|
|
76
|
+
else
|
|
77
|
+
cmd_params = {}
|
|
78
|
+
end
|
|
79
|
+
cmd(command['target_name'], command['cmd_name'], cmd_params, queue: false, scope: @scope)
|
|
80
|
+
elsif command['value']
|
|
81
|
+
# Legacy format: use single string parameter for backwards compatibility
|
|
82
|
+
cmd(command['value'], queue: false, scope: @scope)
|
|
83
|
+
else
|
|
84
|
+
@logger.error "QueueProcessor: Invalid command format, missing required fields"
|
|
85
|
+
end
|
|
71
86
|
end
|
|
72
87
|
rescue StandardError => e
|
|
73
88
|
@logger.error "QueueProcessor failed to process command from queue #{@name}\n#{e.message}"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'openc3/utilities/migration'
|
|
2
|
+
require 'openc3/models/scope_model'
|
|
3
|
+
require 'openc3/models/microservice_model'
|
|
4
|
+
|
|
5
|
+
module OpenC3
|
|
6
|
+
class RemoveUniqueId < Migration
|
|
7
|
+
def self.run
|
|
8
|
+
ScopeModel.get_all_models(scope: nil).each do |scope, scope_model|
|
|
9
|
+
target_models = TargetModel.all(scope: scope)
|
|
10
|
+
target_models.each do |name, target_model|
|
|
11
|
+
target_model.delete("cmd_unique_id_mode")
|
|
12
|
+
target_model.delete("tlm_unique_id_mode")
|
|
13
|
+
model = TargetModel.from_json(target_model, scope: scope)
|
|
14
|
+
model.update()
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
unless ENV['OPENC3_NO_MIGRATE']
|
|
22
|
+
OpenC3::RemoveUniqueId.run
|
|
23
|
+
end
|
|
@@ -41,6 +41,8 @@ require 'tempfile'
|
|
|
41
41
|
require 'fileutils'
|
|
42
42
|
|
|
43
43
|
module OpenC3
|
|
44
|
+
class EmptyGemFileError < StandardError; end
|
|
45
|
+
|
|
44
46
|
# Represents a OpenC3 plugin that can consist of targets, interfaces, routers
|
|
45
47
|
# microservices and tools. The PluginModel installs all these pieces as well
|
|
46
48
|
# as destroys them all when the plugin is removed.
|
|
@@ -87,6 +89,10 @@ module OpenC3
|
|
|
87
89
|
tf = nil
|
|
88
90
|
begin
|
|
89
91
|
if File.exist?(gem_file_path)
|
|
92
|
+
if File.zero?(gem_file_path)
|
|
93
|
+
raise EmptyGemFileError, "Gem file is empty: #{gem_file_path}"
|
|
94
|
+
end
|
|
95
|
+
|
|
90
96
|
# Load gem to internal gem server
|
|
91
97
|
OpenC3::GemModel.put(gem_file_path, gem_install: false, scope: scope) unless validate_only
|
|
92
98
|
else
|
|
@@ -209,7 +215,7 @@ module OpenC3
|
|
|
209
215
|
# Handle python dependencies (pyproject.toml or requirements.txt)
|
|
210
216
|
pyproject_path = File.join(gem_path, 'pyproject.toml')
|
|
211
217
|
requirements_path = File.join(gem_path, 'requirements.txt')
|
|
212
|
-
|
|
218
|
+
|
|
213
219
|
if File.exist?(pyproject_path) || File.exist?(requirements_path)
|
|
214
220
|
begin
|
|
215
221
|
pypi_url = get_setting('pypi_url', scope: scope)
|
|
@@ -20,6 +20,7 @@ require 'openc3/models/model'
|
|
|
20
20
|
require 'openc3/models/microservice_model'
|
|
21
21
|
require 'openc3/topics/queue_topic'
|
|
22
22
|
require 'openc3/utilities/logger'
|
|
23
|
+
require 'openc3/io/json_rpc'
|
|
23
24
|
|
|
24
25
|
module OpenC3
|
|
25
26
|
class QueueError < StandardError; end
|
|
@@ -44,7 +45,7 @@ module OpenC3
|
|
|
44
45
|
end
|
|
45
46
|
# END NOTE
|
|
46
47
|
|
|
47
|
-
def self.queue_command(name, command
|
|
48
|
+
def self.queue_command(name, command: nil, target_name: nil, cmd_name: nil, cmd_params: nil, username:, scope:)
|
|
48
49
|
model = get_model(name: name, scope: scope)
|
|
49
50
|
raise QueueError, "Queue '#{name}' not found in scope '#{scope}'" unless model
|
|
50
51
|
|
|
@@ -55,10 +56,26 @@ module OpenC3
|
|
|
55
56
|
else
|
|
56
57
|
id = result[0][1].to_f + 1
|
|
57
58
|
end
|
|
58
|
-
|
|
59
|
+
|
|
60
|
+
# Build command data with support for both formats
|
|
61
|
+
command_data = { username: username, timestamp: Time.now.to_nsec_from_epoch }
|
|
62
|
+
if target_name && cmd_name
|
|
63
|
+
# New format: store target_name, cmd_name, and cmd_params separately
|
|
64
|
+
command_data[:target_name] = target_name
|
|
65
|
+
command_data[:cmd_name] = cmd_name
|
|
66
|
+
command_data[:cmd_params] = JSON.generate(cmd_params.as_json, allow_nan: true) if cmd_params
|
|
67
|
+
elsif command
|
|
68
|
+
# Legacy format: store command string for backwards compatibility
|
|
69
|
+
command_data[:value] = command
|
|
70
|
+
else
|
|
71
|
+
raise QueueError, "Must provide either command string or target_name/cmd_name parameters"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
Store.zadd("#{scope}:#{name}", id, command_data.to_json)
|
|
59
75
|
model.notify(kind: 'command')
|
|
60
76
|
else
|
|
61
|
-
|
|
77
|
+
error_msg = command || "#{target_name} #{cmd_name}"
|
|
78
|
+
raise QueueError, "Queue '#{name}' is disabled. Command '#{error_msg}' not queued."
|
|
62
79
|
end
|
|
63
80
|
end
|
|
64
81
|
|
|
@@ -105,7 +122,12 @@ module OpenC3
|
|
|
105
122
|
|
|
106
123
|
def insert_command(id, command_data)
|
|
107
124
|
if @state == 'DISABLE'
|
|
108
|
-
|
|
125
|
+
if command_data['value']
|
|
126
|
+
command_name = command_data['value']
|
|
127
|
+
else
|
|
128
|
+
command_name = "#{command_data['target_name']} #{command_data['cmd_name']}"
|
|
129
|
+
end
|
|
130
|
+
raise QueueError, "Queue '#{@name}' is disabled. Command '#{command_name}' not queued."
|
|
109
131
|
end
|
|
110
132
|
|
|
111
133
|
unless id
|
|
@@ -116,6 +138,11 @@ module OpenC3
|
|
|
116
138
|
id = result[0][1].to_f + 1
|
|
117
139
|
end
|
|
118
140
|
end
|
|
141
|
+
|
|
142
|
+
# Convert cmd_params values to JSON-safe format if present
|
|
143
|
+
if command_data['cmd_params']
|
|
144
|
+
command_data['cmd_params'] = JSON.generate(command_data['cmd_params'].as_json, allow_nan: true)
|
|
145
|
+
end
|
|
119
146
|
Store.zadd("#{@scope}:#{@name}", id, command_data.to_json)
|
|
120
147
|
notify(kind: 'command')
|
|
121
148
|
end
|
|
@@ -163,7 +190,7 @@ module OpenC3
|
|
|
163
190
|
else
|
|
164
191
|
score = result[0][1]
|
|
165
192
|
Store.zremrangebyscore("#{@scope}:#{@name}", score, score)
|
|
166
|
-
command_data = JSON.parse(result[0][0])
|
|
193
|
+
command_data = JSON.parse(result[0][0], allow_nan: true)
|
|
167
194
|
command_data['id'] = score.to_f
|
|
168
195
|
notify(kind: 'command')
|
|
169
196
|
return command_data
|
|
@@ -183,20 +183,17 @@ module OpenC3
|
|
|
183
183
|
end
|
|
184
184
|
|
|
185
185
|
def verify_triggers
|
|
186
|
-
|
|
186
|
+
if @triggers.empty?
|
|
187
|
+
raise ReactionInputError.new "reaction must contain at least one valid trigger: #{@triggers}"
|
|
188
|
+
end
|
|
189
|
+
|
|
187
190
|
@triggers.each do | trigger |
|
|
188
191
|
model = TriggerModel.get(name: trigger['name'], group: trigger['group'], scope: @scope)
|
|
189
192
|
if model.nil?
|
|
190
193
|
raise ReactionInputError.new "failed to find trigger: #{trigger}"
|
|
191
194
|
end
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if trigger_models.empty?
|
|
195
|
-
raise ReactionInputError.new "reaction must contain at least one valid trigger: #{@triggers}"
|
|
196
|
-
end
|
|
197
|
-
trigger_models.each do | trigger_model |
|
|
198
|
-
trigger_model.update_dependents(dependent: @name)
|
|
199
|
-
trigger_model.update()
|
|
195
|
+
model.update_dependents(dependent: @name)
|
|
196
|
+
model.update()
|
|
200
197
|
end
|
|
201
198
|
end
|
|
202
199
|
|
|
@@ -211,6 +208,25 @@ module OpenC3
|
|
|
211
208
|
end
|
|
212
209
|
|
|
213
210
|
def update
|
|
211
|
+
old_reaction = ReactionModel.get(name: @name, scope: @scope)
|
|
212
|
+
|
|
213
|
+
if old_reaction
|
|
214
|
+
# Find triggers that are being removed (in old but not in new)
|
|
215
|
+
old_trigger_keys = old_reaction.triggers.map { |t| "#{t['group']}:#{t['name']}" }
|
|
216
|
+
new_trigger_keys = @triggers.map { |t| "#{t['group']}:#{t['name']}" }
|
|
217
|
+
removed_trigger_keys = old_trigger_keys - new_trigger_keys
|
|
218
|
+
|
|
219
|
+
# Remove this reaction from old triggers' dependents
|
|
220
|
+
removed_trigger_keys.each do |trigger_key|
|
|
221
|
+
group, name = trigger_key.split(':', 2)
|
|
222
|
+
trigger_model = TriggerModel.get(name: name, group: group, scope: @scope)
|
|
223
|
+
if trigger_model
|
|
224
|
+
trigger_model.update_dependents(dependent: @name, remove: true)
|
|
225
|
+
trigger_model.update()
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
214
230
|
verify_triggers()
|
|
215
231
|
@updated_at = Time.now.to_nsec_from_epoch
|
|
216
232
|
Store.hset(@primary_key, @name, JSON.generate(as_json, allow_nan: true))
|
|
@@ -54,6 +54,9 @@ module OpenC3
|
|
|
54
54
|
ERB_EXTENSIONS = %w(.txt .rb .py .json .yaml .yml)
|
|
55
55
|
ITEM_MAP_CACHE_TIMEOUT = 10.0
|
|
56
56
|
@@item_map_cache = {}
|
|
57
|
+
@@sync_packet_count_data = {}
|
|
58
|
+
@@sync_packet_count_time = nil
|
|
59
|
+
@@sync_packet_count_delay_seconds = 1.0 # Sync packet counts every second
|
|
57
60
|
|
|
58
61
|
attr_accessor :folder_name
|
|
59
62
|
attr_accessor :requires
|
|
@@ -61,8 +64,6 @@ module OpenC3
|
|
|
61
64
|
attr_accessor :ignored_items
|
|
62
65
|
attr_accessor :limits_groups
|
|
63
66
|
attr_accessor :cmd_tlm_files
|
|
64
|
-
attr_accessor :cmd_unique_id_mode
|
|
65
|
-
attr_accessor :tlm_unique_id_mode
|
|
66
67
|
attr_accessor :id
|
|
67
68
|
attr_accessor :cmd_buffer_depth
|
|
68
69
|
attr_accessor :cmd_log_cycle_time
|
|
@@ -351,8 +352,6 @@ module OpenC3
|
|
|
351
352
|
ignored_items: [],
|
|
352
353
|
limits_groups: [],
|
|
353
354
|
cmd_tlm_files: [],
|
|
354
|
-
cmd_unique_id_mode: false,
|
|
355
|
-
tlm_unique_id_mode: false,
|
|
356
355
|
id: nil,
|
|
357
356
|
updated_at: nil,
|
|
358
357
|
plugin: nil,
|
|
@@ -402,8 +401,6 @@ module OpenC3
|
|
|
402
401
|
@ignored_items = ignored_items
|
|
403
402
|
@limits_groups = limits_groups
|
|
404
403
|
@cmd_tlm_files = cmd_tlm_files
|
|
405
|
-
@cmd_unique_id_mode = cmd_unique_id_mode
|
|
406
|
-
@tlm_unique_id_mode = tlm_unique_id_mode
|
|
407
404
|
@id = id
|
|
408
405
|
@cmd_buffer_depth = cmd_buffer_depth
|
|
409
406
|
@cmd_log_cycle_time = cmd_log_cycle_time
|
|
@@ -442,8 +439,6 @@ module OpenC3
|
|
|
442
439
|
'ignored_items' => @ignored_items,
|
|
443
440
|
'limits_groups' => @limits_groups,
|
|
444
441
|
'cmd_tlm_files' => @cmd_tlm_files,
|
|
445
|
-
'cmd_unique_id_mode' => @cmd_unique_id_mode,
|
|
446
|
-
'tlm_unique_id_mode' => @tlm_unique_id_mode,
|
|
447
442
|
'id' => @id,
|
|
448
443
|
'updated_at' => @updated_at,
|
|
449
444
|
'plugin' => @plugin,
|
|
@@ -790,8 +785,6 @@ module OpenC3
|
|
|
790
785
|
@ignored_parameters = target.ignored_parameters
|
|
791
786
|
@ignored_items = target.ignored_items
|
|
792
787
|
@cmd_tlm_files = target.cmd_tlm_files
|
|
793
|
-
@cmd_unique_id_mode = target.cmd_unique_id_mode
|
|
794
|
-
@tlm_unique_id_mode = target.tlm_unique_id_mode
|
|
795
788
|
@limits_groups = system.limits.groups.keys
|
|
796
789
|
update()
|
|
797
790
|
end
|
|
@@ -1365,6 +1358,81 @@ module OpenC3
|
|
|
1365
1358
|
return counts
|
|
1366
1359
|
end
|
|
1367
1360
|
|
|
1361
|
+
def self.sync_packet_count_delay_seconds=(value)
|
|
1362
|
+
@@sync_packet_count_delay_seconds = value
|
|
1363
|
+
end
|
|
1364
|
+
|
|
1365
|
+
def self.init_tlm_packet_counts(tlm_target_names, scope:)
|
|
1366
|
+
@@sync_packet_count_time = Time.now
|
|
1367
|
+
|
|
1368
|
+
# Get all the packet counts with the global counters
|
|
1369
|
+
tlm_target_names.each do |target_name|
|
|
1370
|
+
get_all_telemetry_counts(target_name, scope: scope).each do |packet_name, count|
|
|
1371
|
+
update_packet = System.telemetry.packet(target_name, packet_name)
|
|
1372
|
+
update_packet.received_count = count.to_i
|
|
1373
|
+
end
|
|
1374
|
+
end
|
|
1375
|
+
get_all_telemetry_counts('UNKNOWN', scope: scope).each do |packet_name, count|
|
|
1376
|
+
update_packet = System.telemetry.packet('UNKNOWN', packet_name)
|
|
1377
|
+
update_packet.received_count = count.to_i
|
|
1378
|
+
end
|
|
1379
|
+
end
|
|
1380
|
+
|
|
1381
|
+
def self.sync_tlm_packet_counts(packet, tlm_target_names, scope:)
|
|
1382
|
+
if @@sync_packet_count_delay_seconds <= 0 or $openc3_redis_cluster
|
|
1383
|
+
# Perfect but slow method
|
|
1384
|
+
packet.received_count = increment_telemetry_count(packet.target_name, packet.packet_name, 1, scope: scope)
|
|
1385
|
+
else
|
|
1386
|
+
# Eventually consistent method
|
|
1387
|
+
# Only sync every period (default 1 second) to avoid hammering Redis
|
|
1388
|
+
# This is a trade off between speed and accuracy
|
|
1389
|
+
# The packet count is eventually consistent
|
|
1390
|
+
@@sync_packet_count_data[packet.target_name] ||= {}
|
|
1391
|
+
@@sync_packet_count_data[packet.target_name][packet.packet_name] ||= 0
|
|
1392
|
+
@@sync_packet_count_data[packet.target_name][packet.packet_name] += 1
|
|
1393
|
+
|
|
1394
|
+
# Ensures counters change between syncs
|
|
1395
|
+
update_packet = System.telemetry.packet(packet.target_name, packet.packet_name)
|
|
1396
|
+
update_packet.received_count += 1
|
|
1397
|
+
packet.received_count = update_packet.received_count
|
|
1398
|
+
|
|
1399
|
+
# Check if we need to sync the packet counts
|
|
1400
|
+
if @@sync_packet_count_time.nil? or (Time.now - @@sync_packet_count_time) > @@sync_packet_count_delay_seconds
|
|
1401
|
+
@@sync_packet_count_time = Time.now
|
|
1402
|
+
|
|
1403
|
+
inc_count = 0
|
|
1404
|
+
# Use pipeline to make this one transaction
|
|
1405
|
+
result = Store.redis_pool.pipelined do
|
|
1406
|
+
# Increment global counters for packets received
|
|
1407
|
+
@@sync_packet_count_data.each do |target_name, packet_data|
|
|
1408
|
+
packet_data.each do |packet_name, count|
|
|
1409
|
+
increment_telemetry_count(target_name, packet_name, count, scope: scope)
|
|
1410
|
+
inc_count += 1
|
|
1411
|
+
end
|
|
1412
|
+
end
|
|
1413
|
+
@@sync_packet_count_data = {}
|
|
1414
|
+
|
|
1415
|
+
# Get all the packet counts with the global counters
|
|
1416
|
+
tlm_target_names.each do |target_name|
|
|
1417
|
+
get_all_telemetry_counts(target_name, scope: scope)
|
|
1418
|
+
end
|
|
1419
|
+
get_all_telemetry_counts('UNKNOWN', scope: scope)
|
|
1420
|
+
end
|
|
1421
|
+
tlm_target_names.each do |target_name|
|
|
1422
|
+
result[inc_count].each do |packet_name, count|
|
|
1423
|
+
update_packet = System.telemetry.packet(target_name, packet_name)
|
|
1424
|
+
update_packet.received_count = count.to_i
|
|
1425
|
+
end
|
|
1426
|
+
inc_count += 1
|
|
1427
|
+
end
|
|
1428
|
+
result[inc_count].each do |packet_name, count|
|
|
1429
|
+
update_packet = System.telemetry.packet('UNKNOWN', packet_name)
|
|
1430
|
+
update_packet.received_count = count.to_i
|
|
1431
|
+
end
|
|
1432
|
+
end
|
|
1433
|
+
end
|
|
1434
|
+
end
|
|
1435
|
+
|
|
1368
1436
|
def self.increment_command_count(target_name, packet_name, count, scope:)
|
|
1369
1437
|
result = Store.hincrby("#{scope}__COMMANDCNTS__{#{target_name}}", packet_name, count)
|
|
1370
1438
|
if String === result
|
|
@@ -105,7 +105,7 @@ module OpenC3
|
|
|
105
105
|
#
|
|
106
106
|
# @param (see #identify_tlm!)
|
|
107
107
|
# @return (see #identify_tlm!)
|
|
108
|
-
def identify(packet_data, target_names = nil)
|
|
108
|
+
def identify(packet_data, target_names = nil, subpackets: false)
|
|
109
109
|
identified_packet = nil
|
|
110
110
|
|
|
111
111
|
target_names = target_names() unless target_names
|
|
@@ -120,21 +120,39 @@ module OpenC3
|
|
|
120
120
|
next
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
if target and target.cmd_unique_id_mode
|
|
123
|
+
if (not subpackets and System.commands.cmd_unique_id_mode(target_name)) or (subpackets and System.commands.cmd_subpacket_unique_id_mode(target_name))
|
|
125
124
|
# Iterate through the packets and see if any represent the buffer
|
|
126
125
|
target_packets.each do |_packet_name, packet|
|
|
127
|
-
if
|
|
126
|
+
if subpackets
|
|
127
|
+
next unless packet.subpacket
|
|
128
|
+
else
|
|
129
|
+
next if packet.subpacket
|
|
130
|
+
end
|
|
131
|
+
if packet.identify?(packet_data) # Handles virtual
|
|
128
132
|
identified_packet = packet
|
|
129
133
|
break
|
|
130
134
|
end
|
|
131
135
|
end
|
|
132
136
|
else
|
|
133
137
|
# Do a hash lookup to quickly identify the packet
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
packet = nil
|
|
139
|
+
target_packets.each do |_packet_name, target_packet|
|
|
140
|
+
next if target_packet.virtual
|
|
141
|
+
if subpackets
|
|
142
|
+
next unless target_packet.subpacket
|
|
143
|
+
else
|
|
144
|
+
next if target_packet.subpacket
|
|
145
|
+
end
|
|
146
|
+
packet = target_packet
|
|
147
|
+
break
|
|
148
|
+
end
|
|
149
|
+
if packet
|
|
136
150
|
key = packet.read_id_values(packet_data)
|
|
137
|
-
|
|
151
|
+
if subpackets
|
|
152
|
+
hash = @config.cmd_subpacket_id_value_hash[target_name]
|
|
153
|
+
else
|
|
154
|
+
hash = @config.cmd_id_value_hash[target_name]
|
|
155
|
+
end
|
|
138
156
|
identified_packet = hash[key]
|
|
139
157
|
identified_packet = hash['CATCHALL'.freeze] unless identified_packet
|
|
140
158
|
end
|
|
@@ -264,6 +282,14 @@ module OpenC3
|
|
|
264
282
|
@config.dynamic_add_packet(packet, :COMMAND, affect_ids: affect_ids)
|
|
265
283
|
end
|
|
266
284
|
|
|
285
|
+
def cmd_unique_id_mode(target_name)
|
|
286
|
+
return @config.cmd_unique_id_mode[target_name.upcase]
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def cmd_subpacket_unique_id_mode(target_name)
|
|
290
|
+
return @config.cmd_subpacket_unique_id_mode[target_name.upcase]
|
|
291
|
+
end
|
|
292
|
+
|
|
267
293
|
protected
|
|
268
294
|
|
|
269
295
|
def set_parameters(command, params, range_checking)
|