openc3 6.9.1 → 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/bin/openc3cli +289 -0
- 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/interfaces.rb +6 -4
- 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 +12 -80
- data/lib/openc3/microservices/queue_microservice.rb +30 -7
- data/lib/openc3/migrations/20251022000000_remove_unique_id.rb +23 -0
- data/lib/openc3/models/plugin_model.rb +69 -6
- data/lib/openc3/models/queue_model.rb +32 -5
- data/lib/openc3/models/reaction_model.rb +26 -10
- data/lib/openc3/models/target_model.rb +85 -13
- data/lib/openc3/models/trigger_model.rb +1 -1
- 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/script.rb +1 -1
- data/lib/openc3/script/suite_results.rb +2 -2
- data/lib/openc3/subpacketizers/subpacketizer.rb +18 -0
- data/lib/openc3/system/system.rb +3 -3
- 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/authentication.rb +25 -6
- data/lib/openc3/utilities/cli_generator.rb +347 -3
- 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/command_validator/command_validator.py +49 -0
- data/templates/command_validator/command_validator.rb +54 -0
- data/templates/tool_angular/package.json +48 -2
- data/templates/tool_react/package.json +51 -1
- data/templates/tool_svelte/package.json +48 -1
- data/templates/tool_vue/package.json +36 -3
- data/templates/widget/package.json +28 -2
- metadata +8 -5
- data/templates/tool_vue/.browserslistrc +0 -16
- data/templates/widget/.browserslistrc +0 -16
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
# if purchased from OpenC3, Inc.
|
|
18
18
|
|
|
19
19
|
require 'json'
|
|
20
|
+
require 'openc3/config/config_parser'
|
|
20
21
|
|
|
21
22
|
module OpenC3
|
|
22
23
|
class Accessor
|
|
@@ -28,11 +29,26 @@ module OpenC3
|
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def read_item(item, buffer)
|
|
31
|
-
|
|
32
|
+
if item.parent_item
|
|
33
|
+
# Structure is used to read items with parent, not accessor
|
|
34
|
+
structure_buffer = read_item(item.parent_item, buffer)
|
|
35
|
+
structure = item.parent_item.structure
|
|
36
|
+
structure.read(item.key, :RAW, structure_buffer)
|
|
37
|
+
else
|
|
38
|
+
self.class.read_item(item, buffer)
|
|
39
|
+
end
|
|
32
40
|
end
|
|
33
41
|
|
|
34
42
|
def write_item(item, value, buffer)
|
|
35
|
-
|
|
43
|
+
if item.parent_item
|
|
44
|
+
# Structure is used to write items with parent, not accessor
|
|
45
|
+
structure_buffer = read_item(item.parent_item, buffer)
|
|
46
|
+
structure = item.parent_item.structure
|
|
47
|
+
structure.write(item.key, value, :RAW, structure_buffer)
|
|
48
|
+
self.class.write_item(item.parent_item, structure_buffer, buffer)
|
|
49
|
+
else
|
|
50
|
+
self.class.write_item(item, value, buffer)
|
|
51
|
+
end
|
|
36
52
|
end
|
|
37
53
|
|
|
38
54
|
def read_items(items, buffer)
|
|
@@ -106,8 +122,16 @@ module OpenC3
|
|
|
106
122
|
def self.convert_to_type(value, item)
|
|
107
123
|
return value if value.nil?
|
|
108
124
|
case item.data_type
|
|
125
|
+
when :ANY
|
|
126
|
+
begin
|
|
127
|
+
value = JSON.parse(value) if value.is_a? String
|
|
128
|
+
rescue Exception
|
|
129
|
+
# Just leave value as is
|
|
130
|
+
end
|
|
131
|
+
when :BOOL
|
|
132
|
+
value = ConfigParser.handle_true_false(value) if value.is_a? String
|
|
109
133
|
when :OBJECT, :ARRAY
|
|
110
|
-
|
|
134
|
+
value = JSON.parse(value) if value.is_a? String
|
|
111
135
|
when :STRING, :BLOCK
|
|
112
136
|
if item.array_size
|
|
113
137
|
value = JSON.parse(value) if value.is_a? String
|
|
@@ -137,8 +137,16 @@ module OpenC3
|
|
|
137
137
|
|
|
138
138
|
def read_item(item, buffer)
|
|
139
139
|
return nil if item.data_type == :DERIVED
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
if item.parent_item
|
|
141
|
+
handle_read_variable_bit_size(item.parent_item, buffer) if item.parent_item.variable_bit_size
|
|
142
|
+
# Structure is used to read items with parent, not accessor
|
|
143
|
+
structure_buffer = read_item(item.parent_item, buffer)
|
|
144
|
+
structure = item.parent_item.structure
|
|
145
|
+
structure.read(item.key, :RAW, structure_buffer)
|
|
146
|
+
else
|
|
147
|
+
handle_read_variable_bit_size(item, buffer) if item.variable_bit_size
|
|
148
|
+
self.class.read_item(item, buffer)
|
|
149
|
+
end
|
|
142
150
|
end
|
|
143
151
|
|
|
144
152
|
def handle_write_variable_bit_size(item, value, buffer)
|
|
@@ -270,8 +278,17 @@ module OpenC3
|
|
|
270
278
|
|
|
271
279
|
def write_item(item, value, buffer)
|
|
272
280
|
return nil if item.data_type == :DERIVED
|
|
273
|
-
|
|
274
|
-
|
|
281
|
+
if item.parent_item
|
|
282
|
+
# Structure is used to write items with parent, not accessor
|
|
283
|
+
structure_buffer = read_item(item.parent_item, buffer)
|
|
284
|
+
structure = item.parent_item.structure
|
|
285
|
+
structure.write(item.key, value, :RAW, structure_buffer)
|
|
286
|
+
handle_write_variable_bit_size(item.parent_item, structure_buffer, buffer) if item.parent_item.variable_bit_size
|
|
287
|
+
self.class.write_item(item.parent_item, structure_buffer, buffer)
|
|
288
|
+
else
|
|
289
|
+
handle_write_variable_bit_size(item, value, buffer) if item.variable_bit_size
|
|
290
|
+
self.class.write_item(item, value, buffer)
|
|
291
|
+
end
|
|
275
292
|
end
|
|
276
293
|
|
|
277
294
|
# Note: do not use directly - use instance read_item
|
|
@@ -25,6 +25,7 @@ module OpenC3
|
|
|
25
25
|
@left_char = left_char
|
|
26
26
|
@right_char = right_char
|
|
27
27
|
@configured = false
|
|
28
|
+
@args = [left_char, right_char]
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def configure
|
|
@@ -65,7 +66,7 @@ module OpenC3
|
|
|
65
66
|
values.each_with_index do |value, i|
|
|
66
67
|
item_key = @item_keys[i]
|
|
67
68
|
if item_key == item.key
|
|
68
|
-
return
|
|
69
|
+
return self.class.convert_to_type(value, item)
|
|
69
70
|
end
|
|
70
71
|
end
|
|
71
72
|
end
|
|
@@ -89,7 +90,7 @@ module OpenC3
|
|
|
89
90
|
end
|
|
90
91
|
index = @item_keys.index(item.key)
|
|
91
92
|
if index
|
|
92
|
-
result[item.name] =
|
|
93
|
+
result[item.name] = self.class.convert_to_type(values[index], item)
|
|
93
94
|
else
|
|
94
95
|
raise "Unknown item with key #{item.key} requested"
|
|
95
96
|
end
|
data/lib/openc3/api/cmd_api.rb
CHANGED
|
@@ -545,9 +545,13 @@ module OpenC3
|
|
|
545
545
|
queue = ENV['OPENC3_DEFAULT_QUEUE']
|
|
546
546
|
end
|
|
547
547
|
if queue
|
|
548
|
-
#
|
|
549
|
-
|
|
550
|
-
|
|
548
|
+
# Pass the command components separately for the queue microservice to use the 3-parameter cmd() method
|
|
549
|
+
QueueModel.queue_command(queue,
|
|
550
|
+
target_name: target_name,
|
|
551
|
+
cmd_name: cmd_name,
|
|
552
|
+
cmd_params: cmd_params,
|
|
553
|
+
username: username,
|
|
554
|
+
scope: scope)
|
|
551
555
|
else
|
|
552
556
|
CommandTopic.send_command(command, timeout: timeout, scope: scope)
|
|
553
557
|
end
|
data/lib/openc3/api/tlm_api.rb
CHANGED
|
@@ -221,15 +221,21 @@ module OpenC3
|
|
|
221
221
|
# @param target_name [String] Name of the target
|
|
222
222
|
# @param packet_name [String] Name of the packet
|
|
223
223
|
# @return [Hash] telemetry hash with last telemetry buffer
|
|
224
|
-
def get_tlm_buffer(*args, manual: false, scope: $openc3_scope, token: $openc3_token)
|
|
224
|
+
def get_tlm_buffer(*args, manual: false, scope: $openc3_scope, token: $openc3_token, timeout: 5)
|
|
225
225
|
target_name, packet_name = _extract_target_packet_names('get_tlm_buffer', *args)
|
|
226
226
|
authorize(permission: 'tlm', target_name: target_name, packet_name: packet_name, manual: manual, scope: scope, token: token)
|
|
227
|
-
TargetModel.packet(target_name, packet_name, scope: scope)
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if msg_id
|
|
227
|
+
model = TargetModel.packet(target_name, packet_name, scope: scope)
|
|
228
|
+
if model["subpacket"]
|
|
229
|
+
msg_hash = DecomInterfaceTopic.get_tlm_buffer(target_name, packet_name, timeout: timeout, scope: scope)
|
|
231
230
|
msg_hash['buffer'] = msg_hash['buffer'].b
|
|
232
231
|
return msg_hash
|
|
232
|
+
else
|
|
233
|
+
topic = "#{scope}__TELEMETRY__{#{target_name}}__#{packet_name}"
|
|
234
|
+
msg_id, msg_hash = Topic.get_newest_message(topic)
|
|
235
|
+
if msg_id
|
|
236
|
+
msg_hash['buffer'] = msg_hash['buffer'].b
|
|
237
|
+
return msg_hash
|
|
238
|
+
end
|
|
233
239
|
end
|
|
234
240
|
return nil
|
|
235
241
|
end
|
|
@@ -249,7 +255,8 @@ module OpenC3
|
|
|
249
255
|
packet = TargetModel.packet(target_name, packet_name, scope: scope)
|
|
250
256
|
t = _validate_tlm_type(type)
|
|
251
257
|
raise ArgumentError, "Unknown type '#{type}' for #{target_name} #{packet_name}" if t.nil?
|
|
252
|
-
items = packet[
|
|
258
|
+
items = packet["items"].reject { | item | item["hidden"] }
|
|
259
|
+
items = items.map { | item | item['name'].upcase }
|
|
253
260
|
cvt_items = items.map { | item | [target_name, packet_name, item, type] }
|
|
254
261
|
current_values = CvtModel.get_tlm_values(cvt_items, stale_time: stale_time, cache_timeout: cache_timeout, scope: scope)
|
|
255
262
|
items.zip(current_values).map { | item , values | [item, values[0], values[1]]}
|
|
@@ -437,8 +444,11 @@ module OpenC3
|
|
|
437
444
|
# @param packet_name [String] Name of the packet
|
|
438
445
|
# @param item_name [String] Name of the packet
|
|
439
446
|
# @return [Hash] Telemetry packet item hash
|
|
440
|
-
def get_item(*args, manual: false, scope: $openc3_scope, token: $openc3_token)
|
|
447
|
+
def get_item(*args, manual: false, scope: $openc3_scope, token: $openc3_token, cache_timeout: nil)
|
|
441
448
|
target_name, packet_name, item_name = _extract_target_packet_item_names('get_item', *args)
|
|
449
|
+
if packet_name == 'LATEST'
|
|
450
|
+
packet_name = CvtModel.determine_latest_packet_for_item(target_name, item_name, cache_timeout: cache_timeout, scope: scope)
|
|
451
|
+
end
|
|
442
452
|
authorize(permission: 'tlm', target_name: target_name, packet_name: packet_name, manual: manual, scope: scope, token: token)
|
|
443
453
|
TargetModel.packet_item(target_name, packet_name, item_name, scope: scope)
|
|
444
454
|
end
|
|
@@ -89,12 +89,10 @@ module OpenC3
|
|
|
89
89
|
begin
|
|
90
90
|
if @telemetry
|
|
91
91
|
target_packets = System.telemetry.packets(target_name)
|
|
92
|
-
|
|
93
|
-
unique_id_mode = target.tlm_unique_id_mode if target
|
|
92
|
+
unique_id_mode = System.telemetry.tlm_unique_id_mode(target_name)
|
|
94
93
|
else
|
|
95
94
|
target_packets = System.commands.packets(target_name)
|
|
96
|
-
|
|
97
|
-
unique_id_mode = target.cmd_unique_id_mode if target
|
|
95
|
+
unique_id_mode = System.commands.cmd_unique_id_mode(target_name)
|
|
98
96
|
end
|
|
99
97
|
rescue RuntimeError
|
|
100
98
|
# No commands/telemetry for this target
|
|
@@ -103,7 +101,7 @@ module OpenC3
|
|
|
103
101
|
|
|
104
102
|
if unique_id_mode
|
|
105
103
|
target_packets.each do |_packet_name, packet|
|
|
106
|
-
if packet.identify?(@data[@discard_leading_bytes..-1])
|
|
104
|
+
if not packet.subpacket and packet.identify?(@data[@discard_leading_bytes..-1]) # identify? handles virtual
|
|
107
105
|
identified_packet = packet
|
|
108
106
|
break
|
|
109
107
|
end
|
|
@@ -111,15 +109,23 @@ module OpenC3
|
|
|
111
109
|
else
|
|
112
110
|
# Do a hash lookup to quickly identify the packet
|
|
113
111
|
if target_packets.length > 0
|
|
114
|
-
packet =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
packet = nil
|
|
113
|
+
target_packets.each do |_packet_name, target_packet|
|
|
114
|
+
next if target_packet.virtual
|
|
115
|
+
next if target_packet.subpacket
|
|
116
|
+
packet = target_packet
|
|
117
|
+
break
|
|
118
|
+
end
|
|
119
|
+
if packet
|
|
120
|
+
key = packet.read_id_values(@data[@discard_leading_bytes..-1])
|
|
121
|
+
if @telemetry
|
|
122
|
+
hash = System.telemetry.config.tlm_id_value_hash[target_name]
|
|
123
|
+
else
|
|
124
|
+
hash = System.commands.config.cmd_id_value_hash[target_name]
|
|
125
|
+
end
|
|
126
|
+
identified_packet = hash[key]
|
|
127
|
+
identified_packet = hash['CATCHALL'.freeze] unless identified_packet
|
|
120
128
|
end
|
|
121
|
-
identified_packet = hash[key]
|
|
122
|
-
identified_packet = hash['CATCHALL'.freeze] unless identified_packet
|
|
123
129
|
end
|
|
124
130
|
end
|
|
125
131
|
|
data/lib/openc3/interfaces.rb
CHANGED
|
@@ -34,14 +34,16 @@ module OpenC3
|
|
|
34
34
|
autoload(:TcpipServerInterface, 'openc3/interfaces/tcpip_server_interface.rb')
|
|
35
35
|
autoload(:UdpInterface, 'openc3/interfaces/udp_interface.rb')
|
|
36
36
|
|
|
37
|
-
autoload(:Protocol, 'openc3/interfaces/protocols/protocol.rb')
|
|
38
37
|
autoload(:BurstProtocol, 'openc3/interfaces/protocols/burst_protocol.rb')
|
|
38
|
+
autoload(:CmdResponseProtocol, 'openc3/interfaces/protocols/cmd_response_protocol.rb')
|
|
39
|
+
autoload(:CobsProtocol, 'openc3/interfaces/protocols/cobs_protocol.rb')
|
|
40
|
+
autoload(:CrcProtocol, 'openc3/interfaces/protocols/crc_protocol.rb')
|
|
39
41
|
autoload(:FixedProtocol, 'openc3/interfaces/protocols/fixed_protocol.rb')
|
|
42
|
+
autoload(:IgnorePacketProtocol, 'openc3/interfaces/protocols/ignore_packet_protocol.rb')
|
|
40
43
|
autoload(:LengthProtocol, 'openc3/interfaces/protocols/length_protocol.rb')
|
|
41
44
|
autoload(:PreidentifiedProtocol, 'openc3/interfaces/protocols/preidentified_protocol.rb')
|
|
45
|
+
autoload(:Protocol, 'openc3/interfaces/protocols/protocol.rb')
|
|
46
|
+
autoload(:SlipProtocol, 'openc3/interfaces/protocols/slip_protocol.rb')
|
|
42
47
|
autoload(:TemplateProtocol, 'openc3/interfaces/protocols/template_protocol.rb')
|
|
43
48
|
autoload(:TerminatedProtocol, 'openc3/interfaces/protocols/terminated_protocol.rb')
|
|
44
|
-
autoload(:CmdResponseProtocol, 'openc3/interfaces/protocols/cmd_response_protocol.rb')
|
|
45
|
-
autoload(:CrcProtocol, 'openc3/interfaces/protocols/crc_protocol.rb')
|
|
46
|
-
autoload(:IgnorePacketProtocol, 'openc3/interfaces/protocols/ignore_packet_protocol.rb')
|
|
47
49
|
end
|
data/lib/openc3/io/json_rpc.rb
CHANGED
|
@@ -59,6 +59,12 @@ class String
|
|
|
59
59
|
NON_ASCII_PRINTABLE = /[^\x21-\x7e\s]/
|
|
60
60
|
NON_UTF8_PRINTABLE = /[\x00-\x08\x0E-\x1F\x7F]/
|
|
61
61
|
def as_json(_options = nil)
|
|
62
|
+
# If string is ASCII-8BIT (binary) and has non-ASCII bytes (> 127), encode as binary
|
|
63
|
+
# This handles data from hex_to_byte_string and other binary sources
|
|
64
|
+
if self.encoding == Encoding::ASCII_8BIT && self.bytes.any? { |b| b > 127 }
|
|
65
|
+
return self.to_json_raw_object
|
|
66
|
+
end
|
|
67
|
+
|
|
62
68
|
as_utf8 = self.dup.force_encoding('UTF-8')
|
|
63
69
|
if as_utf8.valid_encoding?
|
|
64
70
|
if as_utf8 =~ NON_UTF8_PRINTABLE
|
|
@@ -24,6 +24,7 @@ require 'time'
|
|
|
24
24
|
require 'thread'
|
|
25
25
|
require 'openc3/microservices/microservice'
|
|
26
26
|
require 'openc3/microservices/interface_decom_common'
|
|
27
|
+
require 'openc3/microservices/interface_microservice'
|
|
27
28
|
require 'openc3/topics/telemetry_decom_topic'
|
|
28
29
|
require 'openc3/topics/limits_event_topic'
|
|
29
30
|
|
|
@@ -127,6 +128,10 @@ module OpenC3
|
|
|
127
128
|
handle_build_cmd(msg_hash['build_cmd'], msg_id)
|
|
128
129
|
next
|
|
129
130
|
end
|
|
131
|
+
if msg_hash.key?('get_tlm_buffer')
|
|
132
|
+
handle_get_tlm_buffer(msg_hash['get_tlm_buffer'], msg_id)
|
|
133
|
+
next
|
|
134
|
+
end
|
|
130
135
|
else
|
|
131
136
|
decom_packet(topic, msg_id, msg_hash, redis)
|
|
132
137
|
@metric.set(name: 'decom_total', value: @count, type: 'counter')
|
|
@@ -154,9 +159,12 @@ module OpenC3
|
|
|
154
159
|
@metric.set(name: 'decom_topic_delta_seconds', value: delta, type: 'gauge', unit: 'seconds', help: 'Delta time between data written to stream and decom start')
|
|
155
160
|
|
|
156
161
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
162
|
+
|
|
163
|
+
#######################################
|
|
164
|
+
# Build packet object from topic data
|
|
165
|
+
#######################################
|
|
157
166
|
target_name = msg_hash["target_name"]
|
|
158
167
|
packet_name = msg_hash["packet_name"]
|
|
159
|
-
|
|
160
168
|
packet = System.telemetry.packet(target_name, packet_name)
|
|
161
169
|
packet.stored = ConfigParser.handle_true_false(msg_hash["stored"])
|
|
162
170
|
# Note: Packet time will be recalculated as part of decom so not setting
|
|
@@ -168,29 +176,101 @@ module OpenC3
|
|
|
168
176
|
packet.extra = extra
|
|
169
177
|
end
|
|
170
178
|
packet.buffer = msg_hash["buffer"]
|
|
171
|
-
# Processors are user code points which must be rescued
|
|
172
|
-
# so the TelemetryDecomTopic can write the packet
|
|
173
|
-
begin
|
|
174
|
-
packet.process # Run processors
|
|
175
|
-
rescue Exception => e
|
|
176
|
-
@error_count += 1
|
|
177
|
-
@metric.set(name: 'decom_error_total', value: @error_count, type: 'counter')
|
|
178
|
-
@error = e
|
|
179
|
-
@logger.error e.message
|
|
180
|
-
end
|
|
181
|
-
# Process all the limits and call the limits_change_callback (as necessary)
|
|
182
|
-
# check_limits also can call user code in the limits response
|
|
183
|
-
# but that is rescued separately in the limits_change_callback
|
|
184
|
-
packet.check_limits(System.limits_set)
|
|
185
179
|
|
|
186
|
-
|
|
187
|
-
|
|
180
|
+
################################################################################
|
|
181
|
+
# Break packet into subpackets (if necessary)
|
|
182
|
+
# Subpackets are typically channelized data
|
|
183
|
+
################################################################################
|
|
184
|
+
packet_and_subpackets = packet.subpacketize
|
|
185
|
+
|
|
186
|
+
packet_and_subpackets.each do |packet_or_subpacket|
|
|
187
|
+
if packet_or_subpacket.subpacket
|
|
188
|
+
packet_or_subpacket = handle_subpacket(packet, packet_or_subpacket)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
#####################################################################################
|
|
192
|
+
# Run Processors
|
|
193
|
+
# This must be before the full decom so that processor derived values are available
|
|
194
|
+
#####################################################################################
|
|
195
|
+
begin
|
|
196
|
+
packet_or_subpacket.process # Run processors
|
|
197
|
+
rescue Exception => e
|
|
198
|
+
@error_count += 1
|
|
199
|
+
@metric.set(name: 'decom_error_total', value: @error_count, type: 'counter')
|
|
200
|
+
@error = e
|
|
201
|
+
@logger.error e.message
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
#############################################################################
|
|
205
|
+
# Process all the limits and call the limits_change_callback (as necessary)
|
|
206
|
+
# This must be before the full decom so that limits states are available
|
|
207
|
+
#############################################################################
|
|
208
|
+
packet_or_subpacket.check_limits(System.limits_set)
|
|
209
|
+
|
|
210
|
+
# This is what actually decommutates the packet and updates the CVT
|
|
211
|
+
TelemetryDecomTopic.write_packet(packet_or_subpacket, scope: @scope)
|
|
212
|
+
end
|
|
188
213
|
|
|
189
214
|
diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
|
|
190
215
|
@metric.set(name: 'decom_duration_seconds', value: diff, type: 'gauge', unit: 'seconds')
|
|
191
216
|
end
|
|
192
217
|
end
|
|
193
218
|
|
|
219
|
+
def handle_subpacket(packet, subpacket)
|
|
220
|
+
# Subpacket received time always = packet.received_time
|
|
221
|
+
# Use packet_time appropriately if another timestamp is needed
|
|
222
|
+
subpacket.received_time = packet.received_time
|
|
223
|
+
subpacket.stored = packet.stored
|
|
224
|
+
subpacket.extra = packet.extra
|
|
225
|
+
|
|
226
|
+
if subpacket.stored
|
|
227
|
+
# Stored telemetry does not update the current value table
|
|
228
|
+
identified_subpacket = System.telemetry.identify_and_define_packet(subpacket, @target_names, subpackets: true)
|
|
229
|
+
else
|
|
230
|
+
# Identify and update subpacket
|
|
231
|
+
if subpacket.identified?
|
|
232
|
+
begin
|
|
233
|
+
# Preidentifed subpacket - place it into the current value table
|
|
234
|
+
identified_subpacket = System.telemetry.update!(subpacket.target_name,
|
|
235
|
+
subpacket.packet_name,
|
|
236
|
+
subpacket.buffer)
|
|
237
|
+
rescue RuntimeError
|
|
238
|
+
# Subpacket identified but we don't know about it
|
|
239
|
+
# Clear packet_name and target_name and try to identify
|
|
240
|
+
@logger.warn "#{@name}: Received unknown identified subpacket: #{subpacket.target_name} #{subpacket.packet_name}"
|
|
241
|
+
subpacket.target_name = nil
|
|
242
|
+
subpacket.packet_name = nil
|
|
243
|
+
identified_subpacket = System.telemetry.identify!(subpacket.buffer,
|
|
244
|
+
@target_names, subpackets: true)
|
|
245
|
+
end
|
|
246
|
+
else
|
|
247
|
+
# Packet needs to be identified
|
|
248
|
+
identified_subpacket = System.telemetry.identify!(subpacket.buffer,
|
|
249
|
+
@target_names, subpackets: true)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
if identified_subpacket
|
|
254
|
+
identified_subpacket.received_time = subpacket.received_time
|
|
255
|
+
identified_subpacket.stored = subpacket.stored
|
|
256
|
+
identified_subpacket.extra = subpacket.extra
|
|
257
|
+
subpacket = identified_subpacket
|
|
258
|
+
else
|
|
259
|
+
unknown_subpacket = System.telemetry.update!('UNKNOWN', 'UNKNOWN', subpacket.buffer)
|
|
260
|
+
unknown_subpacket.received_time = subpacket.received_time
|
|
261
|
+
unknown_subpacket.stored = subpacket.stored
|
|
262
|
+
unknown_subpacket.extra = subpacket.extra
|
|
263
|
+
subpacket = unknown_subpacket
|
|
264
|
+
num_bytes_to_print = [InterfaceMicroservice::UNKNOWN_BYTES_TO_PRINT, subpacket.length].min
|
|
265
|
+
data = subpacket.buffer(false)[0..(num_bytes_to_print - 1)]
|
|
266
|
+
prefix = data.each_byte.map { | byte | sprintf("%02X", byte) }.join()
|
|
267
|
+
@logger.warn "#{@name} #{subpacket.target_name} packet length: #{subpacket.length} starting with: #{prefix}"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
TargetModel.sync_tlm_packet_counts(subpacket, @target_names, scope: @scope)
|
|
271
|
+
return subpacket
|
|
272
|
+
end
|
|
273
|
+
|
|
194
274
|
# Called when an item in any packet changes limits states.
|
|
195
275
|
#
|
|
196
276
|
# @param packet [Packet] Packet which has had an item change limits state
|
|
@@ -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
|
|
@@ -231,6 +231,8 @@ module OpenC3
|
|
|
231
231
|
cmd_name = msg_hash['cmd_name']
|
|
232
232
|
manual = ConfigParser.handle_true_false(msg_hash['manual'])
|
|
233
233
|
cmd_params = nil
|
|
234
|
+
range_check = true
|
|
235
|
+
raw = false
|
|
234
236
|
cmd_buffer = nil
|
|
235
237
|
hazardous_check = nil
|
|
236
238
|
if msg_hash['cmd_params']
|
|
@@ -288,14 +290,13 @@ module OpenC3
|
|
|
288
290
|
if @critical_commanding and @critical_commanding != 'OFF' and not release_critical
|
|
289
291
|
restricted = command.restricted
|
|
290
292
|
if hazardous or restricted or (@critical_commanding == 'ALL' and manual)
|
|
293
|
+
cmd_type = 'NORMAL'
|
|
291
294
|
if hazardous
|
|
292
|
-
|
|
295
|
+
cmd_type = 'HAZARDOUS'
|
|
293
296
|
elsif restricted
|
|
294
|
-
|
|
295
|
-
else
|
|
296
|
-
type = 'NORMAL'
|
|
297
|
+
cmd_type = 'RESTRICTED'
|
|
297
298
|
end
|
|
298
|
-
model = CriticalCmdModel.new(name: SecureRandom.uuid, type:
|
|
299
|
+
model = CriticalCmdModel.new(name: SecureRandom.uuid, type: cmd_type, interface_name: @interface.name, username: msg_hash['username'], cmd_hash: msg_hash, scope: @scope)
|
|
299
300
|
model.create
|
|
300
301
|
@logger.info("Critical Cmd Pending: #{msg_hash['cmd_string']}", user: msg_hash['username'], scope: @scope)
|
|
301
302
|
next "CriticalCmdError\n#{model.name}"
|
|
@@ -428,10 +429,12 @@ module OpenC3
|
|
|
428
429
|
params = JSON.parse(msg_hash['params'], allow_nan: true, create_additions: true)
|
|
429
430
|
end
|
|
430
431
|
@router = @tlm.attempting(*params)
|
|
432
|
+
next 'SUCCESS'
|
|
431
433
|
end
|
|
432
434
|
if msg_hash['disconnect']
|
|
433
435
|
@logger.info "#{@router.name}: Disconnect requested"
|
|
434
436
|
@tlm.disconnect(false)
|
|
437
|
+
next 'SUCCESS'
|
|
435
438
|
end
|
|
436
439
|
if msg_hash.key?('log_stream')
|
|
437
440
|
if msg_hash['log_stream'] == 'true'
|
|
@@ -441,6 +444,7 @@ module OpenC3
|
|
|
441
444
|
@logger.info "#{@router.name}: Disable stream logging"
|
|
442
445
|
@router.stop_raw_logging
|
|
443
446
|
end
|
|
447
|
+
next 'SUCCESS'
|
|
444
448
|
end
|
|
445
449
|
if msg_hash.key?('router_cmd')
|
|
446
450
|
params = JSON.parse(msg_hash['router_cmd'], allow_nan: true, create_additions: true)
|
|
@@ -550,23 +554,7 @@ module OpenC3
|
|
|
550
554
|
target = System.targets[target_name]
|
|
551
555
|
target.interface = @interface
|
|
552
556
|
end
|
|
553
|
-
@interface.tlm_target_names
|
|
554
|
-
# Initialize the target's packet counters based on the Topic stream
|
|
555
|
-
# Prevents packet count resetting to 0 when interface restarts
|
|
556
|
-
begin
|
|
557
|
-
System.telemetry.packets(target_name).each do |packet_name, packet|
|
|
558
|
-
topic = "#{@scope}__TELEMETRY__{#{target_name}}__#{packet_name}"
|
|
559
|
-
msg_id, msg_hash = Topic.get_newest_message(topic)
|
|
560
|
-
if msg_id
|
|
561
|
-
packet.received_count = msg_hash['received_count'].to_i
|
|
562
|
-
else
|
|
563
|
-
packet.received_count = 0
|
|
564
|
-
end
|
|
565
|
-
end
|
|
566
|
-
rescue
|
|
567
|
-
# Handle targets without telemetry
|
|
568
|
-
end
|
|
569
|
-
end
|
|
557
|
+
TargetModel.init_tlm_packet_counts(@interface.tlm_target_names, scope: @scope)
|
|
570
558
|
if @interface.connect_on_startup
|
|
571
559
|
@interface.state = 'ATTEMPTING'
|
|
572
560
|
else
|
|
@@ -579,9 +567,6 @@ module OpenC3
|
|
|
579
567
|
end
|
|
580
568
|
|
|
581
569
|
@queued = false
|
|
582
|
-
@sync_packet_count_data = {}
|
|
583
|
-
@sync_packet_count_time = nil
|
|
584
|
-
@sync_packet_count_delay_seconds = 1.0 # Sync packet counts every second
|
|
585
570
|
@interface.options.each do |option_name, option_values|
|
|
586
571
|
if option_name.upcase == 'OPTIMIZE_THROUGHPUT'
|
|
587
572
|
@queued = true
|
|
@@ -590,7 +575,7 @@ module OpenC3
|
|
|
590
575
|
StoreQueued.instance.set_update_interval(update_interval)
|
|
591
576
|
end
|
|
592
577
|
if option_name.upcase == 'SYNC_PACKET_COUNT_DELAY_SECONDS'
|
|
593
|
-
|
|
578
|
+
TargetModel.sync_packet_count_delay_seconds = option_values[0].to_f
|
|
594
579
|
end
|
|
595
580
|
end
|
|
596
581
|
|
|
@@ -773,7 +758,7 @@ module OpenC3
|
|
|
773
758
|
|
|
774
759
|
# Write to stream
|
|
775
760
|
if @interface.tlm_target_enabled[packet.target_name]
|
|
776
|
-
sync_tlm_packet_counts(packet)
|
|
761
|
+
TargetModel.sync_tlm_packet_counts(packet, @interface.tlm_target_names, scope: @scope)
|
|
777
762
|
TelemetryTopic.write_packet(packet, queued: @queued, scope: @scope)
|
|
778
763
|
end
|
|
779
764
|
end
|
|
@@ -908,59 +893,6 @@ module OpenC3
|
|
|
908
893
|
def graceful_kill
|
|
909
894
|
# Just to avoid warning
|
|
910
895
|
end
|
|
911
|
-
|
|
912
|
-
def sync_tlm_packet_counts(packet)
|
|
913
|
-
if @sync_packet_count_delay_seconds <= 0 or $openc3_redis_cluster
|
|
914
|
-
# Perfect but slow method
|
|
915
|
-
packet.received_count = TargetModel.increment_telemetry_count(packet.target_name, packet.packet_name, 1, scope: @scope)
|
|
916
|
-
else
|
|
917
|
-
# Eventually consistent method
|
|
918
|
-
# Only sync every period (default 1 second) to avoid hammering Redis
|
|
919
|
-
# This is a trade off between speed and accuracy
|
|
920
|
-
# The packet count is eventually consistent
|
|
921
|
-
@sync_packet_count_data[packet.target_name] ||= {}
|
|
922
|
-
@sync_packet_count_data[packet.target_name][packet.packet_name] ||= 0
|
|
923
|
-
@sync_packet_count_data[packet.target_name][packet.packet_name] += 1
|
|
924
|
-
|
|
925
|
-
# Ensures counters change between syncs
|
|
926
|
-
packet.received_count += 1
|
|
927
|
-
|
|
928
|
-
# Check if we need to sync the packet counts
|
|
929
|
-
if @sync_packet_count_time.nil? or (Time.now - @sync_packet_count_time) > @sync_packet_count_delay_seconds
|
|
930
|
-
@sync_packet_count_time = Time.now
|
|
931
|
-
|
|
932
|
-
inc_count = 0
|
|
933
|
-
# Use pipeline to make this one transaction
|
|
934
|
-
result = Store.redis_pool.pipelined do
|
|
935
|
-
# Increment global counters for packets received
|
|
936
|
-
@sync_packet_count_data.each do |target_name, packet_data|
|
|
937
|
-
packet_data.each do |packet_name, count|
|
|
938
|
-
TargetModel.increment_telemetry_count(target_name, packet_name, count, scope: @scope)
|
|
939
|
-
inc_count += 1
|
|
940
|
-
end
|
|
941
|
-
end
|
|
942
|
-
@sync_packet_count_data = {}
|
|
943
|
-
|
|
944
|
-
# Get all the packet counts with the global counters
|
|
945
|
-
@interface.tlm_target_names.each do |target_name|
|
|
946
|
-
TargetModel.get_all_telemetry_counts(target_name, scope: @scope)
|
|
947
|
-
end
|
|
948
|
-
TargetModel.get_all_telemetry_counts('UNKNOWN', scope: @scope)
|
|
949
|
-
end
|
|
950
|
-
@interface.tlm_target_names.each do |target_name|
|
|
951
|
-
result[inc_count].each do |packet_name, count|
|
|
952
|
-
update_packet = System.telemetry.packet(target_name, packet_name)
|
|
953
|
-
update_packet.received_count = count.to_i
|
|
954
|
-
end
|
|
955
|
-
inc_count += 1
|
|
956
|
-
end
|
|
957
|
-
result[inc_count].each do |packet_name, count|
|
|
958
|
-
update_packet = System.telemetry.packet('UNKNOWN', packet_name)
|
|
959
|
-
update_packet.received_count = count.to_i
|
|
960
|
-
end
|
|
961
|
-
end
|
|
962
|
-
end
|
|
963
|
-
end
|
|
964
896
|
end
|
|
965
897
|
end
|
|
966
898
|
|