openc3 6.10.2 → 6.10.4
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 +15 -4
- data/data/config/item_modifiers.yaml +2 -2
- data/data/config/parameter_modifiers.yaml +2 -2
- data/data/config/plugins.yaml +38 -0
- data/data/config/screen.yaml +23 -0
- data/data/config/target.yaml +8 -3
- data/data/config/widgets.yaml +30 -0
- data/ext/openc3/ext/config_parser/config_parser.c +49 -37
- data/lib/openc3/accessors/accessor.rb +2 -2
- data/lib/openc3/accessors/json_accessor.rb +1 -1
- data/lib/openc3/api/tlm_api.rb +1 -6
- data/lib/openc3/config/config_parser.rb +45 -25
- data/lib/openc3/io/json_api_object.rb +38 -14
- data/lib/openc3/io/json_drb_object.rb +29 -11
- data/lib/openc3/io/json_rpc.rb +20 -9
- data/lib/openc3/microservices/interface_microservice.rb +8 -3
- data/lib/openc3/models/plugin_model.rb +40 -9
- data/lib/openc3/models/target_model.rb +2 -1
- data/lib/openc3/packets/packet.rb +5 -2
- data/lib/openc3/packets/packet_config.rb +4 -2
- data/lib/openc3/packets/parsers/packet_item_parser.rb +9 -0
- data/lib/openc3/packets/parsers/xtce_converter.rb +464 -100
- data/lib/openc3/script/web_socket_api.rb +1 -1
- data/lib/openc3/topics/command_decom_topic.rb +3 -2
- data/lib/openc3/utilities/cli_generator.rb +1 -1
- data/lib/openc3/version.rb +5 -5
- data/templates/plugin/plugin.gemspec +1 -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 +15 -1
data/lib/openc3/io/json_rpc.rb
CHANGED
|
@@ -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
|
|
17
|
+
# All changes Copyright 2026, OpenC3, Inc.
|
|
18
18
|
# All Rights Reserved
|
|
19
19
|
#
|
|
20
20
|
# This file may also be used under the terms of a commercial license
|
|
@@ -58,21 +58,32 @@ end
|
|
|
58
58
|
class String
|
|
59
59
|
NON_ASCII_PRINTABLE = /[^\x21-\x7e\s]/
|
|
60
60
|
NON_UTF8_PRINTABLE = /[\x00-\x08\x0E-\x1F\x7F]/
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return self.to_json_raw_object
|
|
66
|
-
end
|
|
61
|
+
# Matches characters outside the Latin range (U+0000-U+00FF) or C1 control characters (U+0080-U+009F)
|
|
62
|
+
# Latin range covers Basic Latin (U+0000-U+007F) and Latin-1 Supplement (U+00A0-U+00FF)
|
|
63
|
+
# This includes common characters like µ (U+00B5), ° (U+00B0), ñ (U+00F1), etc.
|
|
64
|
+
OUTSIDE_LATIN_RANGE = /[^\u0000-\u007F\u00A0-\u00FF]/
|
|
67
65
|
|
|
66
|
+
def as_json(_options = nil)
|
|
67
|
+
# Try to interpret the string as UTF-8
|
|
68
|
+
# This handles both:
|
|
69
|
+
# 1. Unicode text in ASCII-8BIT strings (e.g., "µA" for micro-Ampères from config files)
|
|
70
|
+
# 2. Binary data from hex_to_byte_string (e.g., \xDE\xAD\xBE\xEF) which will fail valid_encoding?
|
|
68
71
|
as_utf8 = self.dup.force_encoding('UTF-8')
|
|
69
72
|
if as_utf8.valid_encoding?
|
|
73
|
+
# Valid UTF-8 - check for non-printable control characters
|
|
70
74
|
if as_utf8 =~ NON_UTF8_PRINTABLE
|
|
71
75
|
return self.to_json_raw_object
|
|
72
|
-
else
|
|
73
|
-
return as_utf8
|
|
74
76
|
end
|
|
77
|
+
# Check if all characters are in the expected Latin range (U+0000-U+00FF)
|
|
78
|
+
# This prevents binary data that happens to be valid UTF-8 from being treated as text
|
|
79
|
+
# For example, \xDE\xAD decodes to U+07AD (Thaana script) which should be treated as binary
|
|
80
|
+
# Also reject C1 control characters (U+0080-U+009F) which are non-printable
|
|
81
|
+
if as_utf8 =~ OUTSIDE_LATIN_RANGE
|
|
82
|
+
return self.to_json_raw_object
|
|
83
|
+
end
|
|
84
|
+
return as_utf8
|
|
75
85
|
else
|
|
86
|
+
# Invalid UTF-8 means this is truly binary data, encode as raw object
|
|
76
87
|
return self.to_json_raw_object
|
|
77
88
|
end
|
|
78
89
|
end #:nodoc:
|
|
@@ -89,6 +89,7 @@ module OpenC3
|
|
|
89
89
|
InterfaceTopic.receive_commands(@interface, scope: @scope) do |topic, msg_id, msg_hash, _redis|
|
|
90
90
|
OpenC3.with_context(msg_hash) do
|
|
91
91
|
release_critical = false
|
|
92
|
+
critical_model = nil
|
|
92
93
|
msgid_seconds_from_epoch = msg_id.split('-')[0].to_i / 1000.0
|
|
93
94
|
delta = Time.now.to_f - msgid_seconds_from_epoch
|
|
94
95
|
@metric.set(name: 'interface_topic_delta_seconds', value: delta, type: 'gauge', unit: 'seconds', help: 'Delta time between data written to stream and interface cmd start') if @metric
|
|
@@ -188,9 +189,9 @@ module OpenC3
|
|
|
188
189
|
end
|
|
189
190
|
if msg_hash.key?('release_critical')
|
|
190
191
|
# Note: intentional fall through below this point
|
|
191
|
-
|
|
192
|
-
if
|
|
193
|
-
msg_hash =
|
|
192
|
+
critical_model = CriticalCmdModel.get_model(name: msg_hash['release_critical'], scope: @scope)
|
|
193
|
+
if critical_model
|
|
194
|
+
msg_hash = critical_model.cmd_hash
|
|
194
195
|
release_critical = true
|
|
195
196
|
else
|
|
196
197
|
next "Critical command #{msg_hash['release_critical']} not found"
|
|
@@ -279,6 +280,10 @@ module OpenC3
|
|
|
279
280
|
command.extra ||= {}
|
|
280
281
|
command.extra['cmd_string'] = msg_hash['cmd_string']
|
|
281
282
|
command.extra['username'] = msg_hash['username']
|
|
283
|
+
# Add approver info if this was a critical command that was approved
|
|
284
|
+
if critical_model
|
|
285
|
+
command.extra['approver'] = critical_model.approver
|
|
286
|
+
end
|
|
282
287
|
hazardous, hazardous_description = System.commands.cmd_pkt_hazardous?(command)
|
|
283
288
|
|
|
284
289
|
# Initial Are you sure? Hazardous check
|
|
@@ -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
|
|
17
|
+
# All changes Copyright 2026, OpenC3, Inc.
|
|
18
18
|
# All Rights Reserved
|
|
19
19
|
#
|
|
20
20
|
# This file may also be used under the terms of a commercial license
|
|
@@ -125,11 +125,13 @@ module OpenC3
|
|
|
125
125
|
|
|
126
126
|
# Phase 1 Gather Variables
|
|
127
127
|
variables = {}
|
|
128
|
+
current_variable_name = nil
|
|
128
129
|
parser.parse_file(plugin_txt_path,
|
|
129
130
|
false,
|
|
130
131
|
true,
|
|
131
132
|
false) do |keyword, params|
|
|
132
|
-
|
|
133
|
+
case keyword
|
|
134
|
+
when 'VARIABLE'
|
|
133
135
|
usage = "#{keyword} <Variable Name> <Default Value>"
|
|
134
136
|
parser.verify_num_parameters(2, nil, usage)
|
|
135
137
|
variable_name = params[0]
|
|
@@ -137,10 +139,33 @@ module OpenC3
|
|
|
137
139
|
raise "VARIABLE name '#{variable_name}' is reserved"
|
|
138
140
|
end
|
|
139
141
|
value = params[1..-1].join(" ")
|
|
140
|
-
variables[variable_name] = value
|
|
142
|
+
variables[variable_name] = { 'value' => value }
|
|
143
|
+
current_variable_name = variable_name
|
|
141
144
|
if existing_variables && existing_variables.key?(variable_name)
|
|
142
|
-
|
|
145
|
+
existing = existing_variables[variable_name]
|
|
146
|
+
# Handle both old format (string) and new format (hash)
|
|
147
|
+
if existing.is_a?(Hash)
|
|
148
|
+
variables[variable_name]['value'] = existing['value']
|
|
149
|
+
else
|
|
150
|
+
variables[variable_name]['value'] = existing
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
when 'VARIABLE_DESCRIPTION'
|
|
154
|
+
usage = "#{keyword} <Description>"
|
|
155
|
+
parser.verify_num_parameters(1, 1, usage)
|
|
156
|
+
unless current_variable_name
|
|
157
|
+
raise "VARIABLE_DESCRIPTION must follow a VARIABLE definition"
|
|
143
158
|
end
|
|
159
|
+
variables[current_variable_name]['description'] = params[0]
|
|
160
|
+
when 'VARIABLE_STATE'
|
|
161
|
+
usage = "#{keyword} <Display Text> <Value>"
|
|
162
|
+
parser.verify_num_parameters(2, 2, usage)
|
|
163
|
+
unless current_variable_name
|
|
164
|
+
raise "VARIABLE_STATE must follow a VARIABLE definition"
|
|
165
|
+
end
|
|
166
|
+
variables[current_variable_name]['options'] ||= []
|
|
167
|
+
option = { 'value' => params[1], 'text' => params[0] }
|
|
168
|
+
variables[current_variable_name]['options'] << option
|
|
144
169
|
end
|
|
145
170
|
end
|
|
146
171
|
|
|
@@ -285,20 +310,26 @@ module OpenC3
|
|
|
285
310
|
plugin_txt_path = tf.path
|
|
286
311
|
variables = plugin_hash['variables']
|
|
287
312
|
variables ||= {}
|
|
288
|
-
|
|
313
|
+
# Extract simple key-value pairs for ERB substitution
|
|
314
|
+
# Variables can be either new format (hash with 'value' key) or old format (string)
|
|
315
|
+
erb_variables = {}
|
|
316
|
+
variables.each do |name, var|
|
|
317
|
+
erb_variables[name] = var.is_a?(Hash) ? var['value'] : var
|
|
318
|
+
end
|
|
319
|
+
erb_variables['scope'] = scope
|
|
289
320
|
if File.exist?(plugin_txt_path)
|
|
290
321
|
parser = OpenC3::ConfigParser.new("https://openc3.com")
|
|
291
322
|
|
|
292
323
|
current_model = nil
|
|
293
|
-
parser.parse_file(plugin_txt_path, false, true, true,
|
|
324
|
+
parser.parse_file(plugin_txt_path, false, true, true, erb_variables) do |keyword, params|
|
|
294
325
|
case keyword
|
|
295
|
-
when 'VARIABLE', 'NEEDS_DEPENDENCIES'
|
|
326
|
+
when 'VARIABLE', 'VARIABLE_DESCRIPTION', 'VARIABLE_STATE', 'NEEDS_DEPENDENCIES'
|
|
296
327
|
# Ignore during phase 2
|
|
297
328
|
when 'TARGET', 'INTERFACE', 'ROUTER', 'MICROSERVICE', 'TOOL', 'WIDGET', 'SCRIPT_ENGINE'
|
|
298
329
|
begin
|
|
299
330
|
if current_model
|
|
300
331
|
current_model.create unless validate_only
|
|
301
|
-
current_model.deploy(gem_path,
|
|
332
|
+
current_model.deploy(gem_path, erb_variables, validate_only: validate_only)
|
|
302
333
|
end
|
|
303
334
|
# If something goes wrong in create, or more likely in deploy,
|
|
304
335
|
# we want to clear the current_model and try to instantiate the next
|
|
@@ -318,7 +349,7 @@ module OpenC3
|
|
|
318
349
|
end
|
|
319
350
|
if current_model
|
|
320
351
|
current_model.create unless validate_only
|
|
321
|
-
current_model.deploy(gem_path,
|
|
352
|
+
current_model.deploy(gem_path, erb_variables, validate_only: validate_only)
|
|
322
353
|
current_model = nil
|
|
323
354
|
end
|
|
324
355
|
end
|
|
@@ -638,7 +638,8 @@ module OpenC3
|
|
|
638
638
|
system = System.new([@name], temp_dir)
|
|
639
639
|
if variables["xtce_output"]
|
|
640
640
|
puts "Converting target #{@name} to .xtce files in #{variables["xtce_output"]}/#{@name}"
|
|
641
|
-
|
|
641
|
+
puts " Using mnemonic '#{variables['time_association_name']}' as the packet time item."
|
|
642
|
+
system.packet_config.to_xtce(variables["xtce_output"], variables['time_association_name'])
|
|
642
643
|
end
|
|
643
644
|
unless validate_only
|
|
644
645
|
build_target_archive(temp_dir, target_folder)
|
|
@@ -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
|
|
17
|
+
# All changes Copyright 2026, OpenC3, Inc.
|
|
18
18
|
# All Rights Reserved
|
|
19
19
|
#
|
|
20
20
|
# This file may also be used under the terms of a commercial license
|
|
@@ -433,6 +433,9 @@ module OpenC3
|
|
|
433
433
|
previous_item = nil
|
|
434
434
|
warnings = []
|
|
435
435
|
@sorted_items.each do |item|
|
|
436
|
+
# Skip items with a parent_item since those are accessor-based items within a structure
|
|
437
|
+
# (e.g., JSON, CBOR) that don't have meaningful bit positions - they share the parent's bit_offset
|
|
438
|
+
next if item.parent_item
|
|
436
439
|
if expected_next_offset and (item.bit_offset < expected_next_offset) and !item.overlap
|
|
437
440
|
msg = "Bit definition overlap at bit offset #{item.bit_offset} for packet #{@target_name} #{@packet_name} items #{item.name} and #{previous_item.name}"
|
|
438
441
|
Logger.instance.warn(msg)
|
|
@@ -1524,7 +1527,7 @@ module OpenC3
|
|
|
1524
1527
|
value = value.to_s
|
|
1525
1528
|
end
|
|
1526
1529
|
end
|
|
1527
|
-
value
|
|
1530
|
+
value = "#{value} #{item.units}" if value_type == :WITH_UNITS and item.units
|
|
1528
1531
|
value
|
|
1529
1532
|
end
|
|
1530
1533
|
|
|
@@ -180,6 +180,7 @@ module OpenC3
|
|
|
180
180
|
@converted_type,
|
|
181
181
|
@converted_bit_size) if keyword.include? "WRITE"
|
|
182
182
|
@building_generic_conversion = false
|
|
183
|
+
parser.set_preserve_lines(false)
|
|
183
184
|
# Add the current config.line to the conversion being built
|
|
184
185
|
else
|
|
185
186
|
@proc_text << parser.line << "\n"
|
|
@@ -331,8 +332,8 @@ module OpenC3
|
|
|
331
332
|
end
|
|
332
333
|
end # def to_config
|
|
333
334
|
|
|
334
|
-
def to_xtce(output_dir)
|
|
335
|
-
XtceConverter.convert(@commands, @telemetry, output_dir)
|
|
335
|
+
def to_xtce(output_dir, time_association_name)
|
|
336
|
+
XtceConverter.convert(@commands, @telemetry, output_dir, time_association_name)
|
|
336
337
|
end
|
|
337
338
|
|
|
338
339
|
# Add current packet into hash if it exists
|
|
@@ -712,6 +713,7 @@ module OpenC3
|
|
|
712
713
|
parser.verify_num_parameters(0, 2, usage)
|
|
713
714
|
@proc_text = ''
|
|
714
715
|
@building_generic_conversion = true
|
|
716
|
+
parser.set_preserve_lines(true)
|
|
715
717
|
@converted_type = nil
|
|
716
718
|
@converted_bit_size = nil
|
|
717
719
|
if params[0]
|
|
@@ -65,6 +65,15 @@ module OpenC3
|
|
|
65
65
|
@parser.verify_num_parameters(max_options - 2, max_options, @usage)
|
|
66
66
|
end
|
|
67
67
|
@parser.verify_parameter_naming(1) # Item name is the 1st parameter
|
|
68
|
+
|
|
69
|
+
# ARRAY items cannot have brackets in their name because brackets are used
|
|
70
|
+
# for array indexing in the UI and would cause confusion
|
|
71
|
+
if @parser.keyword.include?('ARRAY')
|
|
72
|
+
item_name = @parser.parameters[0]
|
|
73
|
+
if item_name.include?('[') || item_name.include?(']')
|
|
74
|
+
raise @parser.error("ARRAY items cannot have brackets in their name: #{item_name}", @usage)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
68
77
|
end
|
|
69
78
|
|
|
70
79
|
def create_packet_item(packet, cmd_or_tlm)
|