openc3 7.2.0 → 7.3.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 +108 -29
- data/bin/pipinstall +5 -4
- data/bin/pipuninstall +5 -1
- data/bin/uvinstall +149 -0
- data/data/config/interface_modifiers.yaml +15 -0
- data/data/config/item_modifiers.yaml +2 -1
- data/data/config/microservice.yaml +5 -1
- data/data/config/screen.yaml +2 -1
- data/data/config/target.yaml +38 -0
- data/data/config/tool.yaml +3 -1
- data/data/config/widgets.yaml +43 -3
- data/data/xtce_schemas/SpaceSystem_20180204.xsd +5918 -0
- data/data/xtce_schemas/xml.xsd +116 -0
- data/ext/openc3/ext/telemetry/telemetry.c +1 -1
- data/lib/openc3/accessors/binary_accessor.rb +4 -0
- data/lib/openc3/accessors/template_accessor.rb +2 -2
- data/lib/openc3/api/cmd_api.rb +5 -1
- data/lib/openc3/api/interface_api.rb +2 -2
- data/lib/openc3/api/limits_api.rb +89 -3
- data/lib/openc3/api/router_api.rb +3 -3
- data/lib/openc3/api/tlm_api.rb +1 -1
- data/lib/openc3/interfaces/http_client_interface.rb +1 -1
- data/lib/openc3/io/json_drb.rb +24 -8
- data/lib/openc3/io/json_rpc.rb +0 -2
- data/lib/openc3/logs/log_writer.rb +43 -7
- data/lib/openc3/logs/packet_log_writer.rb +1 -2
- data/lib/openc3/microservices/decom_common.rb +15 -4
- data/lib/openc3/microservices/decom_microservice.rb +6 -1
- data/lib/openc3/microservices/interface_microservice.rb +26 -12
- data/lib/openc3/microservices/queue_microservice.rb +4 -2
- data/lib/openc3/migrations/20260701000000_updated_at_to_int.rb +55 -0
- data/lib/openc3/migrations/20260731000000_system_health_json.rb +41 -0
- data/lib/openc3/models/cvt_model.rb +7 -7
- data/lib/openc3/models/db_sharded_model.rb +2 -2
- data/lib/openc3/models/gem_model.rb +1 -1
- data/lib/openc3/models/interface_model.rb +17 -11
- data/lib/openc3/models/interface_status_model.rb +2 -2
- data/lib/openc3/models/metric_model.rb +2 -2
- data/lib/openc3/models/microservice_model.rb +21 -0
- data/lib/openc3/models/microservice_status_model.rb +2 -2
- data/lib/openc3/models/model.rb +2 -6
- data/lib/openc3/models/plugin_model.rb +204 -49
- data/lib/openc3/models/python_package_model.rb +210 -12
- data/lib/openc3/models/reingest_job_model.rb +1 -1
- data/lib/openc3/models/scope_model.rb +34 -30
- data/lib/openc3/models/script_status_model.rb +73 -38
- data/lib/openc3/models/target_model.rb +193 -6
- data/lib/openc3/models/tool_model.rb +33 -20
- data/lib/openc3/operators/microservice_operator.rb +33 -4
- data/lib/openc3/operators/operator.rb +15 -5
- data/lib/openc3/packets/commands.rb +7 -7
- data/lib/openc3/packets/limits.rb +33 -2
- data/lib/openc3/packets/packet.rb +6 -4
- data/lib/openc3/packets/packet_config.rb +5 -1
- data/lib/openc3/packets/parsers/state_parser.rb +1 -1
- data/lib/openc3/packets/parsers/xtce_converter.rb +183 -103
- data/lib/openc3/packets/parsers/xtce_parser.rb +71 -23
- data/lib/openc3/packets/structure.rb +16 -12
- data/lib/openc3/packets/telemetry.rb +3 -3
- data/lib/openc3/script/commands.rb +1 -1
- data/lib/openc3/script/extract.rb +20 -25
- data/lib/openc3/script/limits.rb +1 -1
- data/lib/openc3/script/suite_runner.rb +43 -9
- data/lib/openc3/script/web_socket_api.rb +45 -5
- data/lib/openc3/system/target.rb +4 -1
- data/lib/openc3/tools/table_manager/table_config.rb +4 -1
- data/lib/openc3/top_level.rb +1 -1
- data/lib/openc3/topics/command_decom_topic.rb +16 -1
- data/lib/openc3/topics/limits_event_topic.rb +63 -4
- data/lib/openc3/topics/telemetry_decom_topic.rb +2 -2
- data/lib/openc3/utilities/aws_bucket.rb +10 -2
- data/lib/openc3/utilities/cli_generator.rb +5 -1
- data/lib/openc3/utilities/csv.rb +5 -5
- data/lib/openc3/utilities/env_helper.rb +13 -1
- data/lib/openc3/utilities/local_mode.rb +4 -1
- data/lib/openc3/utilities/pypi_url.rb +39 -0
- data/lib/openc3/utilities/reingest_job.rb +1 -1
- data/lib/openc3/utilities/ruby_lex_utils.rb +5 -1
- data/lib/openc3/utilities/running_script.rb +160 -100
- data/lib/openc3/utilities/script.rb +60 -13
- data/lib/openc3/utilities/store_autoload.rb +39 -1
- data/lib/openc3/utilities/store_queued.rb +5 -1
- data/lib/openc3/version.rb +5 -5
- data/lib/openc3.rb +6 -1
- 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 +7 -3
- data/templates/tool_vue/.nycrc +0 -3
- data/templates/widget/.nycrc +0 -3
|
@@ -25,6 +25,13 @@ module OpenC3
|
|
|
25
25
|
class ToolModel < Model
|
|
26
26
|
PRIMARY_KEY = 'openc3_tools'
|
|
27
27
|
|
|
28
|
+
# Tools are global to the entire COSMOS installation and are always stored
|
|
29
|
+
# in the DEFAULT scope. The frontend navigation always requests the tool
|
|
30
|
+
# list from DEFAULT, so a tool installed into any other scope would be
|
|
31
|
+
# inaccessible. Every scope parameter in this class is therefore ignored
|
|
32
|
+
# and replaced with TOOL_SCOPE.
|
|
33
|
+
TOOL_SCOPE = 'DEFAULT'
|
|
34
|
+
|
|
28
35
|
attr_accessor :folder_name
|
|
29
36
|
attr_accessor :icon
|
|
30
37
|
attr_accessor :url
|
|
@@ -39,21 +46,24 @@ module OpenC3
|
|
|
39
46
|
|
|
40
47
|
# NOTE: The following three class methods are used by the ModelController
|
|
41
48
|
# and are reimplemented to enable various Model class methods to work
|
|
49
|
+
# NOTE: scope is deprecated since 7.3.0 as tools always live in the DEFAULT scope, see TOOL_SCOPE
|
|
42
50
|
def self.get(name:, scope: nil)
|
|
43
|
-
super("#{
|
|
51
|
+
super("#{TOOL_SCOPE}__#{PRIMARY_KEY}", name: name)
|
|
44
52
|
end
|
|
45
53
|
|
|
54
|
+
# NOTE: scope is deprecated since 7.3.0 as tools always live in the DEFAULT scope, see TOOL_SCOPE
|
|
46
55
|
def self.names(scope: nil)
|
|
47
56
|
array = []
|
|
48
|
-
all(
|
|
57
|
+
all().each do |name, _tool|
|
|
49
58
|
array << name
|
|
50
59
|
end
|
|
51
60
|
array
|
|
52
61
|
end
|
|
53
62
|
|
|
63
|
+
# NOTE: scope is deprecated since 7.3.0 as tools always live in the DEFAULT scope, see TOOL_SCOPE
|
|
54
64
|
def self.all(scope: nil)
|
|
55
65
|
ordered_array = []
|
|
56
|
-
tools = unordered_all(
|
|
66
|
+
tools = unordered_all()
|
|
57
67
|
tools.each do |_name, tool|
|
|
58
68
|
ordered_array << tool
|
|
59
69
|
end
|
|
@@ -65,14 +75,10 @@ module OpenC3
|
|
|
65
75
|
ordered_hash
|
|
66
76
|
end
|
|
67
77
|
|
|
78
|
+
# Tools only exist in the DEFAULT scope. Kept for backwards compatibility
|
|
79
|
+
# with the ToolsController importmap endpoint.
|
|
68
80
|
def self.all_scopes
|
|
69
|
-
|
|
70
|
-
scopes = OpenC3::ScopeModel.all
|
|
71
|
-
scopes.each do |key, _scope|
|
|
72
|
-
tools = unordered_all(scope: key)
|
|
73
|
-
result.merge!(tools)
|
|
74
|
-
end
|
|
75
|
-
result
|
|
81
|
+
unordered_all()
|
|
76
82
|
end
|
|
77
83
|
|
|
78
84
|
# Called by the PluginModel to allow this class to validate it's top-level keyword: "TOOL"
|
|
@@ -80,7 +86,8 @@ module OpenC3
|
|
|
80
86
|
case keyword
|
|
81
87
|
when 'TOOL'
|
|
82
88
|
parser.verify_num_parameters(2, 2, "TOOL <Folder Name> <Name>")
|
|
83
|
-
|
|
89
|
+
# NOTE: scope is intentionally ignored, see TOOL_SCOPE
|
|
90
|
+
return self.new(folder_name: parameters[0], name: parameters[1], plugin: plugin, needs_dependencies: needs_dependencies, scope: TOOL_SCOPE)
|
|
84
91
|
else
|
|
85
92
|
raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Tool: #{keyword} #{parameters.join(" ")}")
|
|
86
93
|
end
|
|
@@ -89,8 +96,10 @@ module OpenC3
|
|
|
89
96
|
|
|
90
97
|
# The ToolsTab.vue calls the ToolsController which uses this method to reorder the tools
|
|
91
98
|
# Position is index in the list starting with 0 = first
|
|
92
|
-
|
|
93
|
-
|
|
99
|
+
# NOTE: scope is deprecated since 7.3.0 as tools always live in the DEFAULT scope, see TOOL_SCOPE
|
|
100
|
+
def self.set_position(name:, position:, scope: nil)
|
|
101
|
+
tool_scope = TOOL_SCOPE
|
|
102
|
+
moving = from_json(get(name: name, scope: tool_scope), scope: tool_scope)
|
|
94
103
|
old_pos = moving.position
|
|
95
104
|
new_pos = position
|
|
96
105
|
direction = :down
|
|
@@ -101,8 +110,8 @@ module OpenC3
|
|
|
101
110
|
end
|
|
102
111
|
|
|
103
112
|
# Go through all the tools and reorder
|
|
104
|
-
all(scope:
|
|
105
|
-
tool_model = from_json(tool, scope:
|
|
113
|
+
all(scope: tool_scope).each do |_tool_name, tool|
|
|
114
|
+
tool_model = from_json(tool, scope: tool_scope)
|
|
106
115
|
# Update the requested model to the new position
|
|
107
116
|
if tool_model.name == name
|
|
108
117
|
tool_model.position = position
|
|
@@ -134,9 +143,11 @@ module OpenC3
|
|
|
134
143
|
needs_dependencies: false,
|
|
135
144
|
disable_erb: nil,
|
|
136
145
|
import_map_items: nil,
|
|
137
|
-
scope:
|
|
146
|
+
scope: nil # NOTE: deprecated since 7.3.0 as tools are always created in the DEFAULT scope, see TOOL_SCOPE
|
|
138
147
|
)
|
|
139
|
-
|
|
148
|
+
# Tools are always created in the DEFAULT scope regardless of the scope
|
|
149
|
+
# the plugin is being installed into, see TOOL_SCOPE
|
|
150
|
+
super("#{TOOL_SCOPE}__#{PRIMARY_KEY}", name: name, plugin: plugin, updated_at: updated_at, scope: TOOL_SCOPE)
|
|
140
151
|
@folder_name = folder_name
|
|
141
152
|
@icon = icon
|
|
142
153
|
@url = url
|
|
@@ -156,14 +167,15 @@ module OpenC3
|
|
|
156
167
|
end
|
|
157
168
|
|
|
158
169
|
def create(update: false, force: false, queued: false)
|
|
159
|
-
tools = self.class.all(
|
|
170
|
+
tools = self.class.all()
|
|
160
171
|
|
|
161
172
|
# Make sure a tool with this folder_name doesn't already exist
|
|
173
|
+
# NOTE: Tools are global (TOOL_SCOPE) so this check is across all scopes
|
|
162
174
|
unless update
|
|
163
175
|
if @folder_name
|
|
164
176
|
tools.each do |_tool_name, tool|
|
|
165
177
|
if tool['folder_name'] == @folder_name
|
|
166
|
-
raise "Tool with folder_name #{@folder_name} already exists at create"
|
|
178
|
+
raise "Tool with folder_name #{@folder_name} already exists at create. Tools are global to the entire COSMOS installation and can only be installed once."
|
|
167
179
|
end
|
|
168
180
|
end
|
|
169
181
|
end
|
|
@@ -308,8 +320,9 @@ module OpenC3
|
|
|
308
320
|
##################################################
|
|
309
321
|
|
|
310
322
|
# Returns the list of tools or the default OpenC3 tool set if no tools have been created
|
|
323
|
+
# NOTE: scope is deprecated since 7.3.0 as tools always live in the DEFAULT scope, see TOOL_SCOPE
|
|
311
324
|
def self.unordered_all(scope: nil)
|
|
312
|
-
tools = Store.hgetall("#{
|
|
325
|
+
tools = Store.hgetall("#{TOOL_SCOPE}__#{PRIMARY_KEY}")
|
|
313
326
|
tools.each do |key, value|
|
|
314
327
|
tools[key] = JSON.parse(value, allow_nan: true, create_additions: true)
|
|
315
328
|
end
|
|
@@ -47,10 +47,39 @@ module OpenC3
|
|
|
47
47
|
env = microservice_config["env"].dup
|
|
48
48
|
if microservice_config["needs_dependencies"]
|
|
49
49
|
env['GEM_HOME'] = '/gems'
|
|
50
|
-
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
|
|
50
|
+
|
|
51
|
+
# Resolve the Python virtual environment for this microservice.
|
|
52
|
+
# If the microservice belongs to a plugin that has a per-plugin UV venv
|
|
53
|
+
# (created by uvinstall during plugin install), use that isolated venv.
|
|
54
|
+
# Otherwise fall back to the shared PYTHONUSERBASE for legacy plugins.
|
|
55
|
+
plugin_name = microservice_config["plugin"]
|
|
56
|
+
plugin_venv_dir = nil
|
|
57
|
+
if plugin_name
|
|
58
|
+
scope = microservice_name.split("__")[0]
|
|
59
|
+
sanitized_name = "#{scope}__#{plugin_name}".tr('^a-zA-Z0-9_-', '_')
|
|
60
|
+
candidate = "/gems/plugin_venvs/#{sanitized_name}/.venv"
|
|
61
|
+
plugin_venv_dir = candidate if File.directory?(candidate)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
if plugin_venv_dir
|
|
65
|
+
env['VIRTUAL_ENV'] = plugin_venv_dir
|
|
66
|
+
env['PATH'] = "#{plugin_venv_dir}/bin:#{ENV.fetch('PATH', '')}"
|
|
67
|
+
env['PYTHONUSERBASE'] = plugin_venv_dir
|
|
68
|
+
# Add the plugin venv's site-packages to PYTHONPATH so the base venv's
|
|
69
|
+
# Python binary can find plugin-specific packages. We keep the base binary
|
|
70
|
+
# because it has openc3 core installed; PYTHONPATH is always respected by
|
|
71
|
+
# CPython regardless of venv activation state.
|
|
72
|
+
site_packages = Dir.glob("#{plugin_venv_dir}/lib/python*/site-packages").first
|
|
73
|
+
existing_pythonpath = ENV.fetch('PYTHONPATH', '')
|
|
74
|
+
if site_packages
|
|
75
|
+
env['PYTHONPATH'] = existing_pythonpath.empty? ? site_packages : "#{site_packages}:#{existing_pythonpath}"
|
|
76
|
+
else
|
|
77
|
+
env['PYTHONPATH'] = existing_pythonpath.empty? ? nil : existing_pythonpath
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
env['PYTHONUSERBASE'] = '/gems/python_packages'
|
|
81
|
+
env['PYTHONPATH'] = ENV.fetch('PYTHONPATH', nil)
|
|
82
|
+
end
|
|
54
83
|
else
|
|
55
84
|
env['GEM_HOME'] = nil
|
|
56
85
|
env['PYTHONUSERBASE'] = nil
|
|
@@ -381,11 +381,21 @@ module OpenC3
|
|
|
381
381
|
# Monitor processes and respawn if died
|
|
382
382
|
Logger.info("#{self.class} Monitoring processes every #{@cycle_time} sec...")
|
|
383
383
|
loop do
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
384
|
+
# A single cycle must never be able to take down the operator process.
|
|
385
|
+
# update() in particular hits Redis every cycle and a transient network
|
|
386
|
+
# error (the kind that also makes targets reconnect) would otherwise
|
|
387
|
+
# unwind run() and exit the process, which in the container looks like a
|
|
388
|
+
# full operator restart. Catch, log, and keep cycling so the next cycle
|
|
389
|
+
# can recover once Redis is reachable again.
|
|
390
|
+
begin
|
|
391
|
+
update()
|
|
392
|
+
remove_old()
|
|
393
|
+
respawn_changed()
|
|
394
|
+
start_new()
|
|
395
|
+
respawn_dead()
|
|
396
|
+
rescue => e
|
|
397
|
+
Logger.error("#{self.class} cycle error, continuing: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}")
|
|
398
|
+
end
|
|
389
399
|
break if @shutdown
|
|
390
400
|
|
|
391
401
|
sleep(@cycle_time)
|
|
@@ -61,7 +61,7 @@ module OpenC3
|
|
|
61
61
|
# target name keyed by the packet name
|
|
62
62
|
def packets(target_name)
|
|
63
63
|
target_packets = @config.commands[target_name.to_s.upcase]
|
|
64
|
-
raise "Command target '#{target_name.to_s.upcase}' does not exist" unless target_packets
|
|
64
|
+
raise "Command target '#{target_name.to_s.upcase}' does not exist (packets lookup)" unless target_packets
|
|
65
65
|
|
|
66
66
|
target_packets
|
|
67
67
|
end
|
|
@@ -73,7 +73,7 @@ module OpenC3
|
|
|
73
73
|
def packet(target_name, packet_name)
|
|
74
74
|
target_packets = packets(target_name)
|
|
75
75
|
packet = target_packets[packet_name.to_s.upcase]
|
|
76
|
-
raise "Command packet '#{target_name.to_s.upcase} #{packet_name.to_s.upcase}' does not exist" unless packet
|
|
76
|
+
raise "Command packet '#{target_name.to_s.upcase} #{packet_name.to_s.upcase}' does not exist (packet lookup)" unless packet
|
|
77
77
|
|
|
78
78
|
packet
|
|
79
79
|
end
|
|
@@ -187,9 +187,9 @@ module OpenC3
|
|
|
187
187
|
|
|
188
188
|
# Lookup the command directly - avoid redundant upcase in packet()/packets()
|
|
189
189
|
target_packets = @config.commands[target_upcase]
|
|
190
|
-
raise "Command target '#{target_upcase}' does not exist" unless target_packets
|
|
190
|
+
raise "Command target '#{target_upcase}' does not exist (build_cmd)" unless target_packets
|
|
191
191
|
pkt = target_packets[packet_upcase]
|
|
192
|
-
raise "Command packet '#{target_upcase} #{packet_upcase}' does not exist" unless pkt
|
|
192
|
+
raise "Command packet '#{target_upcase} #{packet_upcase}' does not exist (build_cmd)" unless pkt
|
|
193
193
|
# Use deep_copy to avoid shared item modifications affecting the template
|
|
194
194
|
# This is critical for variable_bit_size items where handle_write_variable_bit_size
|
|
195
195
|
# modifies item.bit_offset and item.array_size during writes
|
|
@@ -325,10 +325,10 @@ module OpenC3
|
|
|
325
325
|
unless item.states.values.include?(value)
|
|
326
326
|
if command.raw
|
|
327
327
|
# Raw commands report missing value maps
|
|
328
|
-
raise "Command parameter '#{command.target_name} #{command.packet_name} #{item_upcase}' = #{value.to_s.upcase} not one of #{item.states.values.join(', ')}"
|
|
328
|
+
raise RangeError, "Command parameter '#{command.target_name} #{command.packet_name} #{item_upcase}' = #{value.to_s.upcase} not one of #{item.states.values.join(', ')}"
|
|
329
329
|
else
|
|
330
330
|
# Normal commands report missing state maps
|
|
331
|
-
raise "Command parameter '#{command.target_name} #{command.packet_name} #{item_upcase}' = #{value.to_s.upcase} not one of #{item.states.keys.join(', ')}"
|
|
331
|
+
raise RangeError, "Command parameter '#{command.target_name} #{command.packet_name} #{item_upcase}' = #{value.to_s.upcase} not one of #{item.states.keys.join(', ')}"
|
|
332
332
|
end
|
|
333
333
|
end
|
|
334
334
|
end
|
|
@@ -340,7 +340,7 @@ module OpenC3
|
|
|
340
340
|
# Perform Range Check on command parameter
|
|
341
341
|
if not range.include?(range_check_value)
|
|
342
342
|
range_check_value = "'#{range_check_value}'" if String === range_check_value
|
|
343
|
-
raise "Command parameter '#{command.target_name} #{command.packet_name} #{item_upcase}' = #{range_check_value} not in valid range of #{range.first} to #{range.last}"
|
|
343
|
+
raise RangeError, "Command parameter '#{command.target_name} #{command.packet_name} #{item_upcase}' = #{range_check_value} not in valid range of #{range.first} to #{range.last}"
|
|
344
344
|
end
|
|
345
345
|
end
|
|
346
346
|
end
|
|
@@ -153,16 +153,47 @@ module OpenC3
|
|
|
153
153
|
return [limits_set, limits.persistence_setting, limits.enabled, limits_for_set[0], limits_for_set[1], limits_for_set[2], limits_for_set[3], limits_for_set[4], limits_for_set[5]]
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
+
# Set the color for a telemetry item state
|
|
157
|
+
#
|
|
158
|
+
# @param target_name [String] Target Name
|
|
159
|
+
# @param packet_name [String] Packet Name
|
|
160
|
+
# @param item_name [String] Item Name
|
|
161
|
+
# @param state_name [String] State Name (e.g. 'CONNECTED')
|
|
162
|
+
# @param color [String or Symbol, nil] New color: GREEN, YELLOW, or RED.
|
|
163
|
+
# Pass nil to clear (remove) the state color.
|
|
164
|
+
# @return [Symbol, nil] The color that was set, or nil if the color was cleared
|
|
165
|
+
def set_state_color(target_name, packet_name, item_name, state_name, color)
|
|
166
|
+
packet = _get_packet(target_name, packet_name)
|
|
167
|
+
item = packet.get_item(item_name)
|
|
168
|
+
raise "Item '#{target_name} #{packet_name} #{item_name}' does not have any states" unless item.states
|
|
169
|
+
state_name = state_name.to_s.upcase
|
|
170
|
+
raise "State '#{state_name}' does not exist for item '#{target_name} #{packet_name} #{item_name}'" unless item.states.key?(state_name)
|
|
171
|
+
if color.nil?
|
|
172
|
+
# Clear the state color while keeping state_colors as a Hash so limits
|
|
173
|
+
# checking (which indexes state_colors by value) continues to work.
|
|
174
|
+
item.state_colors.delete(state_name) if item.state_colors
|
|
175
|
+
packet.update_limits_items_cache(item)
|
|
176
|
+
return nil
|
|
177
|
+
end
|
|
178
|
+
color = color.to_s.upcase.intern
|
|
179
|
+
raise "Invalid state color '#{color}'. Must be one of #{PacketItem::STATE_COLORS.join(', ')}." unless PacketItem::STATE_COLORS.include?(color)
|
|
180
|
+
item.state_colors ||= {}
|
|
181
|
+
item.state_colors[state_name] = color
|
|
182
|
+
item.limits.enabled = true
|
|
183
|
+
packet.update_limits_items_cache(item)
|
|
184
|
+
return color
|
|
185
|
+
end
|
|
186
|
+
|
|
156
187
|
protected
|
|
157
188
|
|
|
158
189
|
def _get_packet(target_name, packet_name)
|
|
159
190
|
raise "LATEST packet not valid" if packet_name.upcase == LATEST_PACKET_NAME
|
|
160
191
|
|
|
161
192
|
packets = @config.telemetry[target_name.to_s.upcase]
|
|
162
|
-
raise "Telemetry target '#{target_name.to_s.upcase}' does not exist" unless packets
|
|
193
|
+
raise "Telemetry target '#{target_name.to_s.upcase}' does not exist (limits lookup)" unless packets
|
|
163
194
|
|
|
164
195
|
packet = packets[packet_name.to_s.upcase]
|
|
165
|
-
raise "Telemetry packet '#{target_name.to_s.upcase} #{packet_name.to_s.upcase}' does not exist" unless packet
|
|
196
|
+
raise "Telemetry packet '#{target_name.to_s.upcase} #{packet_name.to_s.upcase}' does not exist (limits lookup)" unless packet
|
|
166
197
|
|
|
167
198
|
return packet
|
|
168
199
|
end
|
|
@@ -616,7 +616,7 @@ module OpenC3
|
|
|
616
616
|
def get_item(name)
|
|
617
617
|
return super(name)
|
|
618
618
|
rescue ArgumentError
|
|
619
|
-
raise "
|
|
619
|
+
raise "Item '#{@target_name} #{@packet_name} #{name.upcase}' does not exist (Packet)"
|
|
620
620
|
end
|
|
621
621
|
|
|
622
622
|
# Read an item in the packet
|
|
@@ -1285,7 +1285,7 @@ module OpenC3
|
|
|
1285
1285
|
config
|
|
1286
1286
|
end
|
|
1287
1287
|
|
|
1288
|
-
def decom
|
|
1288
|
+
def decom(include_limits_states: true)
|
|
1289
1289
|
# Read all the RAW at once because this could be optimized by the accessor
|
|
1290
1290
|
json_hash = read_items(@sorted_items)
|
|
1291
1291
|
|
|
@@ -1301,7 +1301,9 @@ module OpenC3
|
|
|
1301
1301
|
if item.format_string or item.units
|
|
1302
1302
|
json_hash["#{item.name}__F"] = read_item(item, :FORMATTED, @buffer, given_raw)
|
|
1303
1303
|
end
|
|
1304
|
-
|
|
1304
|
+
if include_limits_states
|
|
1305
|
+
limits_state = item.limits.state
|
|
1306
|
+
end
|
|
1305
1307
|
if limits_state
|
|
1306
1308
|
json_hash["#{item.name}__L"] = limits_state
|
|
1307
1309
|
end
|
|
@@ -1551,4 +1553,4 @@ module OpenC3
|
|
|
1551
1553
|
item
|
|
1552
1554
|
end
|
|
1553
1555
|
end
|
|
1554
|
-
end
|
|
1556
|
+
end
|
|
@@ -161,7 +161,11 @@ module OpenC3
|
|
|
161
161
|
process_target_name = process_target_name.upcase
|
|
162
162
|
parser = ConfigParser.new("https://docs.openc3.com/docs")
|
|
163
163
|
parser.instance_variable_set(:@target_name, process_target_name)
|
|
164
|
-
|
|
164
|
+
# run_erb is false: ERB is only rendered once, at plugin install time
|
|
165
|
+
# (TargetModel#deploy), before files are written to the targets bucket. The
|
|
166
|
+
# targets_modified overlay (e.g. dynamically created packets) is never ERB
|
|
167
|
+
# rendered, so it is treated as data here rather than executed as code.
|
|
168
|
+
parser.parse_file(filename, false, true, false) do |keyword, params|
|
|
165
169
|
if @building_generic_conversion
|
|
166
170
|
case keyword
|
|
167
171
|
# Complete a generic conversion
|
|
@@ -124,7 +124,7 @@ module OpenC3
|
|
|
124
124
|
def get_state_colors(item)
|
|
125
125
|
color = @parser.parameters[2].upcase.to_sym
|
|
126
126
|
unless PacketItem::STATE_COLORS.include? color
|
|
127
|
-
raise @parser.error("Invalid state color #{color}. Must be one of #{PacketItem::STATE_COLORS.join(' ')}.", @usage)
|
|
127
|
+
raise @parser.error("Invalid state color '#{color}'. Must be one of #{PacketItem::STATE_COLORS.join(', ')}.", @usage)
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
item.limits.enabled = true
|