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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<?xml version='1.0'?>
|
|
2
|
+
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">
|
|
3
|
+
|
|
4
|
+
<xs:annotation>
|
|
5
|
+
<xs:documentation>
|
|
6
|
+
See http://www.w3.org/XML/1998/namespace.html and
|
|
7
|
+
http://www.w3.org/TR/REC-xml for information about this namespace.
|
|
8
|
+
|
|
9
|
+
This schema document describes the XML namespace, in a form
|
|
10
|
+
suitable for import by other schema documents.
|
|
11
|
+
|
|
12
|
+
Note that local names in this namespace are intended to be defined
|
|
13
|
+
only by the World Wide Web Consortium or its subgroups. The
|
|
14
|
+
following names are currently defined in this namespace and should
|
|
15
|
+
not be used with conflicting semantics by any Working Group,
|
|
16
|
+
specification, or document instance:
|
|
17
|
+
|
|
18
|
+
base (as an attribute name): denotes an attribute whose value
|
|
19
|
+
provides a URI to be used as the base for interpreting any
|
|
20
|
+
relative URIs in the scope of the element on which it
|
|
21
|
+
appears; its value is inherited. This name is reserved
|
|
22
|
+
by virtue of its definition in the XML Base specification.
|
|
23
|
+
|
|
24
|
+
lang (as an attribute name): denotes an attribute whose value
|
|
25
|
+
is a language code for the natural language of the content of
|
|
26
|
+
any element; its value is inherited. This name is reserved
|
|
27
|
+
by virtue of its definition in the XML specification.
|
|
28
|
+
|
|
29
|
+
space (as an attribute name): denotes an attribute whose
|
|
30
|
+
value is a keyword indicating what whitespace processing
|
|
31
|
+
discipline is intended for the content of the element; its
|
|
32
|
+
value is inherited. This name is reserved by virtue of its
|
|
33
|
+
definition in the XML specification.
|
|
34
|
+
|
|
35
|
+
Father (in any context at all): denotes Jon Bosak, the chair of
|
|
36
|
+
the original XML Working Group. This name is reserved by
|
|
37
|
+
the following decision of the W3C XML Plenary and
|
|
38
|
+
XML Coordination groups:
|
|
39
|
+
|
|
40
|
+
In appreciation for his vision, leadership and dedication
|
|
41
|
+
the W3C XML Plenary on this 10th day of February, 2000
|
|
42
|
+
reserves for Jon Bosak in perpetuity the XML name
|
|
43
|
+
xml:Father
|
|
44
|
+
</xs:documentation>
|
|
45
|
+
</xs:annotation>
|
|
46
|
+
|
|
47
|
+
<xs:annotation>
|
|
48
|
+
<xs:documentation>This schema defines attributes and an attribute group
|
|
49
|
+
suitable for use by
|
|
50
|
+
schemas wishing to allow xml:base, xml:lang or xml:space attributes
|
|
51
|
+
on elements they define.
|
|
52
|
+
|
|
53
|
+
To enable this, such a schema must import this schema
|
|
54
|
+
for the XML namespace, e.g. as follows:
|
|
55
|
+
<schema . . .>
|
|
56
|
+
. . .
|
|
57
|
+
<import namespace="http://www.w3.org/XML/1998/namespace"
|
|
58
|
+
schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>
|
|
59
|
+
|
|
60
|
+
Subsequently, qualified reference to any of the attributes
|
|
61
|
+
or the group defined below will have the desired effect, e.g.
|
|
62
|
+
|
|
63
|
+
<type . . .>
|
|
64
|
+
. . .
|
|
65
|
+
<attributeGroup ref="xml:specialAttrs"/>
|
|
66
|
+
|
|
67
|
+
will define a type which will schema-validate an instance
|
|
68
|
+
element with any of those attributes</xs:documentation>
|
|
69
|
+
</xs:annotation>
|
|
70
|
+
|
|
71
|
+
<xs:annotation>
|
|
72
|
+
<xs:documentation>In keeping with the XML Schema WG's standard versioning
|
|
73
|
+
policy, this schema document will persist at
|
|
74
|
+
http://www.w3.org/2001/03/xml.xsd.
|
|
75
|
+
At the date of issue it can also be found at
|
|
76
|
+
http://www.w3.org/2001/xml.xsd.
|
|
77
|
+
The schema document at that URI may however change in the future,
|
|
78
|
+
in order to remain compatible with the latest version of XML Schema
|
|
79
|
+
itself. In other words, if the XML Schema namespace changes, the version
|
|
80
|
+
of this document at
|
|
81
|
+
http://www.w3.org/2001/xml.xsd will change
|
|
82
|
+
accordingly; the version at
|
|
83
|
+
http://www.w3.org/2001/03/xml.xsd will not change.
|
|
84
|
+
</xs:documentation>
|
|
85
|
+
</xs:annotation>
|
|
86
|
+
|
|
87
|
+
<xs:attribute name="lang" type="xs:language">
|
|
88
|
+
<xs:annotation>
|
|
89
|
+
<xs:documentation>In due course, we should install the relevant ISO 2- and 3-letter
|
|
90
|
+
codes as the enumerated possible values . . .</xs:documentation>
|
|
91
|
+
</xs:annotation>
|
|
92
|
+
</xs:attribute>
|
|
93
|
+
|
|
94
|
+
<xs:attribute name="space" default="preserve">
|
|
95
|
+
<xs:simpleType>
|
|
96
|
+
<xs:restriction base="xs:NCName">
|
|
97
|
+
<xs:enumeration value="default"/>
|
|
98
|
+
<xs:enumeration value="preserve"/>
|
|
99
|
+
</xs:restriction>
|
|
100
|
+
</xs:simpleType>
|
|
101
|
+
</xs:attribute>
|
|
102
|
+
|
|
103
|
+
<xs:attribute name="base" type="xs:anyURI">
|
|
104
|
+
<xs:annotation>
|
|
105
|
+
<xs:documentation>See http://www.w3.org/TR/xmlbase/ for
|
|
106
|
+
information about this attribute.</xs:documentation>
|
|
107
|
+
</xs:annotation>
|
|
108
|
+
</xs:attribute>
|
|
109
|
+
|
|
110
|
+
<xs:attributeGroup name="specialAttrs">
|
|
111
|
+
<xs:attribute ref="xml:base"/>
|
|
112
|
+
<xs:attribute ref="xml:lang"/>
|
|
113
|
+
<xs:attribute ref="xml:space"/>
|
|
114
|
+
</xs:attributeGroup>
|
|
115
|
+
|
|
116
|
+
</xs:schema>
|
|
@@ -56,7 +56,7 @@ static VALUE packets(VALUE self, VALUE target_name)
|
|
|
56
56
|
|
|
57
57
|
if (!(RTEST(target_packets)))
|
|
58
58
|
{
|
|
59
|
-
rb_raise(rb_eRuntimeError, "Telemetry target '%s' does not exist", RSTRING_PTR(upcase_target_name));
|
|
59
|
+
rb_raise(rb_eRuntimeError, "Telemetry target '%s' does not exist (packets lookup)", RSTRING_PTR(upcase_target_name));
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
return target_packets;
|
|
@@ -108,6 +108,10 @@ module OpenC3
|
|
|
108
108
|
|
|
109
109
|
def handle_read_variable_bit_size(item, _buffer)
|
|
110
110
|
length_value = @packet.read(item.variable_bit_size['length_item_name'], :CONVERTED)
|
|
111
|
+
# length_value can be nil when reading an undersized packet where the length
|
|
112
|
+
# item falls outside the buffer bounds
|
|
113
|
+
raise "Length value #{item.variable_bit_size['length_item_name']} for item #{item.name} is nil" if length_value.nil?
|
|
114
|
+
|
|
111
115
|
if item.array_size
|
|
112
116
|
item.array_size = (length_value * item.variable_bit_size['length_bits_per_count']) + item.variable_bit_size['length_value_bit_offset']
|
|
113
117
|
else
|
|
@@ -109,7 +109,7 @@ module OpenC3
|
|
|
109
109
|
configure()
|
|
110
110
|
|
|
111
111
|
success = buffer.gsub!("#{@left_char}#{item.key}#{@right_char}", value.to_s)
|
|
112
|
-
raise "Key #{item.key} not found in template" unless success
|
|
112
|
+
raise "Key '#{item.key}' not found in template (write_item)" unless success
|
|
113
113
|
return value
|
|
114
114
|
end
|
|
115
115
|
|
|
@@ -118,7 +118,7 @@ module OpenC3
|
|
|
118
118
|
items.each_with_index do |item, index|
|
|
119
119
|
next if item.data_type == :DERIVED
|
|
120
120
|
success = buffer.gsub!("#{@left_char}#{item.key}#{@right_char}", values[index].to_s)
|
|
121
|
-
raise "Key #{item.key} not found in template" unless success
|
|
121
|
+
raise "Key '#{item.key}' not found in template (write_items)" unless success
|
|
122
122
|
end
|
|
123
123
|
return values
|
|
124
124
|
end
|
data/lib/openc3/api/cmd_api.rb
CHANGED
|
@@ -450,7 +450,7 @@ module OpenC3
|
|
|
450
450
|
|
|
451
451
|
# NOTE: When adding new keywords to this method, make sure to update script/commands.rb
|
|
452
452
|
def _cmd_implementation(method_name, *args, range_check:, hazardous_check:, raw:, timeout: nil, log_message: nil, manual: false, validate: true, queue: nil,
|
|
453
|
-
scope: $openc3_scope, token: $openc3_token, **kwargs)
|
|
453
|
+
queue_username: nil, scope: $openc3_scope, token: $openc3_token, **kwargs)
|
|
454
454
|
extract_string_kwargs_to_args(args, kwargs)
|
|
455
455
|
unless [nil, true, false].include?(log_message)
|
|
456
456
|
raise "Invalid log_message parameter: #{log_message}. Must be true or false."
|
|
@@ -535,6 +535,10 @@ module OpenC3
|
|
|
535
535
|
'log_message' => log_message.to_s,
|
|
536
536
|
'obfuscated_items' => packet['obfuscated_items'].to_s
|
|
537
537
|
}
|
|
538
|
+
# Record the original queuing user (author) separately from 'username' (the
|
|
539
|
+
# user or process that actually executed the command). Command History shows
|
|
540
|
+
# 'username' as "Executed By" and queue_username as "Queued By".
|
|
541
|
+
command['queue_username'] = queue_username if queue_username
|
|
538
542
|
# Users have to explicitly opt into a default queue by setting the OPENC3_DEFAULT_QUEUE
|
|
539
543
|
# At which point ALL commands will go to that queue unless they specifically opt out with queue: false
|
|
540
544
|
if ENV['OPENC3_DEFAULT_QUEUE'] && queue.nil?
|
|
@@ -47,7 +47,7 @@ module OpenC3
|
|
|
47
47
|
def get_interface(interface_name, manual: false, scope: $openc3_scope, token: $openc3_token)
|
|
48
48
|
authorize(permission: 'system', interface_name: interface_name, manual: manual, scope: scope, token: token)
|
|
49
49
|
interface = InterfaceModel.get(name: interface_name, scope: scope)
|
|
50
|
-
raise "Interface '#{interface_name}' does not exist" unless interface
|
|
50
|
+
raise "Interface '#{interface_name}' does not exist (get_interface)" unless interface
|
|
51
51
|
|
|
52
52
|
interface.merge(InterfaceStatusModel.get(name: interface_name, scope: scope))
|
|
53
53
|
end
|
|
@@ -160,7 +160,7 @@ module OpenC3
|
|
|
160
160
|
# TODO: Check if they have command authority for the targets mapped to this interface
|
|
161
161
|
authorize(permission: 'system_set', interface_name: interface_name, manual: manual, scope: scope, token: token)
|
|
162
162
|
interface = InterfaceModel.get_model(name: interface_name, scope: scope)
|
|
163
|
-
raise "Interface '#{interface_name}' does not exist" unless interface
|
|
163
|
+
raise "Interface '#{interface_name}' does not exist (unmap_target_from_interface)" unless interface
|
|
164
164
|
|
|
165
165
|
if Array === target_name
|
|
166
166
|
target_names = target_name
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
require 'openc3/api/target_api'
|
|
19
19
|
require 'openc3/models/cvt_model'
|
|
20
|
+
require 'openc3/packets/packet_item'
|
|
20
21
|
|
|
21
22
|
module OpenC3
|
|
22
23
|
module Api
|
|
@@ -29,12 +30,14 @@ module OpenC3
|
|
|
29
30
|
'disable_limits',
|
|
30
31
|
'get_limits',
|
|
31
32
|
'set_limits',
|
|
33
|
+
'set_state_color',
|
|
32
34
|
'get_limits_groups',
|
|
33
35
|
'enable_limits_group',
|
|
34
36
|
'disable_limits_group',
|
|
35
37
|
'get_limits_sets',
|
|
36
38
|
'set_limits_set',
|
|
37
39
|
'get_limits_set',
|
|
40
|
+
'delete_limits_set',
|
|
38
41
|
'get_limits_events',
|
|
39
42
|
])
|
|
40
43
|
|
|
@@ -131,7 +134,7 @@ module OpenC3
|
|
|
131
134
|
break
|
|
132
135
|
end
|
|
133
136
|
end
|
|
134
|
-
raise "Item '#{target_name} #{packet_name} #{item_name}' does not exist" unless found_item
|
|
137
|
+
raise "Item '#{target_name} #{packet_name} #{item_name}' does not exist while enabling limits" unless found_item
|
|
135
138
|
|
|
136
139
|
TargetModel.set_packet(target_name, packet_name, packet, scope: scope)
|
|
137
140
|
|
|
@@ -164,7 +167,7 @@ module OpenC3
|
|
|
164
167
|
break
|
|
165
168
|
end
|
|
166
169
|
end
|
|
167
|
-
raise "Item '#{target_name} #{packet_name} #{item_name}' does not exist" unless found_item
|
|
170
|
+
raise "Item '#{target_name} #{packet_name} #{item_name}' does not exist while disabling limits" unless found_item
|
|
168
171
|
|
|
169
172
|
TargetModel.set_packet(target_name, packet_name, packet, scope: scope)
|
|
170
173
|
|
|
@@ -236,7 +239,7 @@ module OpenC3
|
|
|
236
239
|
end
|
|
237
240
|
end
|
|
238
241
|
end
|
|
239
|
-
raise "Item '#{target_name} #{packet_name} #{item_name}' does not exist" unless found_item
|
|
242
|
+
raise "Item '#{target_name} #{packet_name} #{item_name}' does not exist while setting limits" unless found_item
|
|
240
243
|
message = "Setting '#{target_name} #{packet_name} #{item_name}' limits to #{red_low} #{yellow_low} #{yellow_high} #{red_high}"
|
|
241
244
|
message << " #{green_low} #{green_high}" if green_low && green_high
|
|
242
245
|
message << " in set #{limits_set} with persistence #{persistence} as enabled #{enabled}"
|
|
@@ -251,6 +254,64 @@ module OpenC3
|
|
|
251
254
|
LimitsEventTopic.write(event, scope: scope)
|
|
252
255
|
end
|
|
253
256
|
|
|
257
|
+
# Change the color associated with a telemetry item state in realtime.
|
|
258
|
+
# Items with states use the state color (GREEN, YELLOW, RED) to determine
|
|
259
|
+
# their limits state rather than numeric red/yellow/green limits.
|
|
260
|
+
#
|
|
261
|
+
# @param target_name [String] Target name
|
|
262
|
+
# @param packet_name [String] Packet name
|
|
263
|
+
# @param item_name [String] Item name
|
|
264
|
+
# @param state_name [String] Name of the state to change (e.g. 'CONNECTED')
|
|
265
|
+
# @param color [String, nil] New color for the state. Must be GREEN, YELLOW,
|
|
266
|
+
# or RED. Pass nil to clear (remove) the state color. Setting a color also
|
|
267
|
+
# enables limits for the item, but clearing the color (nil) does not change
|
|
268
|
+
# whether limits are enabled.
|
|
269
|
+
def set_state_color(target_name, packet_name, item_name, state_name, color,
|
|
270
|
+
manual: false, scope: $openc3_scope, token: $openc3_token)
|
|
271
|
+
authorize(permission: 'tlm_set', target_name: target_name, packet_name: packet_name, manual: manual, scope: scope, token: token)
|
|
272
|
+
state_name = state_name.to_s.upcase
|
|
273
|
+
unless color.nil?
|
|
274
|
+
color = color.to_s.upcase
|
|
275
|
+
unless PacketItem::STATE_COLORS.include?(color.intern)
|
|
276
|
+
raise "Invalid state color '#{color}'. Must be one of #{PacketItem::STATE_COLORS.join(', ')}."
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
packet = TargetModel.packet(target_name, packet_name, scope: scope)
|
|
280
|
+
found_item = nil
|
|
281
|
+
packet['items'].each do |item|
|
|
282
|
+
if item['name'] == item_name
|
|
283
|
+
unless item['states'] && item['states'][state_name]
|
|
284
|
+
raise "State '#{state_name}' does not exist for item '#{target_name} #{packet_name} #{item_name}'"
|
|
285
|
+
end
|
|
286
|
+
if color.nil?
|
|
287
|
+
# Clear the state color
|
|
288
|
+
item['states'][state_name].delete('color')
|
|
289
|
+
else
|
|
290
|
+
item['states'][state_name]['color'] = color
|
|
291
|
+
# Defining a state color implies limits are enabled for the item
|
|
292
|
+
item['limits'] ||= {}
|
|
293
|
+
item['limits']['enabled'] = true
|
|
294
|
+
end
|
|
295
|
+
found_item = item
|
|
296
|
+
break
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
raise "Item '#{target_name} #{packet_name} #{item_name}' does not exist while setting state color" unless found_item
|
|
300
|
+
if color.nil?
|
|
301
|
+
message = "Clearing '#{target_name} #{packet_name} #{item_name}' state #{state_name} color"
|
|
302
|
+
else
|
|
303
|
+
message = "Setting '#{target_name} #{packet_name} #{item_name}' state #{state_name} color to #{color}"
|
|
304
|
+
end
|
|
305
|
+
Logger.info(message, scope: scope)
|
|
306
|
+
|
|
307
|
+
TargetModel.set_packet(target_name, packet_name, packet, scope: scope)
|
|
308
|
+
|
|
309
|
+
event = { type: :LIMITS_STATE_COLOR, target_name: target_name, packet_name: packet_name,
|
|
310
|
+
item_name: item_name, state_name: state_name, color: color,
|
|
311
|
+
time_nsec: Time.now.to_nsec_from_epoch, message: message }
|
|
312
|
+
LimitsEventTopic.write(event, scope: scope)
|
|
313
|
+
end
|
|
314
|
+
|
|
254
315
|
# Returns all limits_groups and their members
|
|
255
316
|
# @since 5.0.0 Returns hash with values
|
|
256
317
|
# @return [Hash{String => Array<Array<String, String, String>>]
|
|
@@ -292,6 +353,31 @@ module OpenC3
|
|
|
292
353
|
time_nsec: Time.now.to_nsec_from_epoch, message: message }, scope: scope)
|
|
293
354
|
end
|
|
294
355
|
|
|
356
|
+
# Deletes a limits set. The DEFAULT limits set and the currently active
|
|
357
|
+
# limits set cannot be deleted. Use set_limits_set to change the active set
|
|
358
|
+
# before deleting it. Note that the limits set is not removed from the
|
|
359
|
+
# TargetModel packet definitions; that is cleaned up on the next plugin
|
|
360
|
+
# install.
|
|
361
|
+
#
|
|
362
|
+
# @param limits_set [String] The name of the limits set to delete
|
|
363
|
+
def delete_limits_set(limits_set, manual: false, scope: $openc3_scope, token: $openc3_token)
|
|
364
|
+
authorize(permission: 'tlm_set', manual: manual, scope: scope, token: token)
|
|
365
|
+
limits_set = limits_set.to_s
|
|
366
|
+
raise "Cannot delete the DEFAULT limits set" if limits_set == 'DEFAULT'
|
|
367
|
+
if limits_set == LimitsEventTopic.current_set(scope: scope)
|
|
368
|
+
raise "Cannot delete the current limits set '#{limits_set}'. Use set_limits_set to change the current set first."
|
|
369
|
+
end
|
|
370
|
+
unless LimitsEventTopic.sets(scope: scope).key?(limits_set)
|
|
371
|
+
raise "Limits set '#{limits_set}' does not exist"
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Remove the limits set from Redis (limits_sets and current_limits_settings)
|
|
375
|
+
LimitsEventTopic.delete_set(limits_set, scope: scope)
|
|
376
|
+
|
|
377
|
+
message = "Deleting Limits Set: #{limits_set}"
|
|
378
|
+
Logger.info(message, scope: scope)
|
|
379
|
+
end
|
|
380
|
+
|
|
295
381
|
# Returns the active limits set that applies to all telemetry
|
|
296
382
|
#
|
|
297
383
|
# @return [String] The current limits set
|
|
@@ -46,7 +46,7 @@ module OpenC3
|
|
|
46
46
|
def get_router(router_name, manual: false, scope: $openc3_scope, token: $openc3_token)
|
|
47
47
|
authorize(permission: 'system', router_name: router_name, manual: manual, scope: scope, token: token)
|
|
48
48
|
router = RouterModel.get(name: router_name, scope: scope)
|
|
49
|
-
raise "Router '#{router_name}' does not exist" unless router
|
|
49
|
+
raise "Router '#{router_name}' does not exist (get_router)" unless router
|
|
50
50
|
|
|
51
51
|
router.merge(RouterStatusModel.get(name: router_name, scope: scope))
|
|
52
52
|
end
|
|
@@ -144,7 +144,7 @@ module OpenC3
|
|
|
144
144
|
# TODO: Check if they have command authority for the targets mapped to this router
|
|
145
145
|
authorize(permission: 'system_set', router_name: router_name, manual: manual, scope: scope, token: token)
|
|
146
146
|
router = RouterModel.get_model(name: router_name, scope: scope)
|
|
147
|
-
raise "Router '#{router_name}' does not exist" unless router
|
|
147
|
+
raise "Router '#{router_name}' does not exist (map_target_to_router)" unless router
|
|
148
148
|
|
|
149
149
|
if Array === target_name
|
|
150
150
|
target_names = target_name
|
|
@@ -167,7 +167,7 @@ module OpenC3
|
|
|
167
167
|
# TODO: Check if they have command authority for the targets mapped to this router
|
|
168
168
|
authorize(permission: 'system_set', router_name: router_name, manual: manual, scope: scope, token: token)
|
|
169
169
|
router = RouterModel.get_model(name: router_name, scope: scope)
|
|
170
|
-
raise "Router '#{router_name}' does not exist" unless router
|
|
170
|
+
raise "Router '#{router_name}' does not exist (unmap_target_from_router)" unless router
|
|
171
171
|
|
|
172
172
|
if Array === target_name
|
|
173
173
|
target_names = target_name
|
data/lib/openc3/api/tlm_api.rb
CHANGED
|
@@ -123,7 +123,7 @@ module OpenC3
|
|
|
123
123
|
target_name = target_name.upcase
|
|
124
124
|
packet_name = packet_name.upcase
|
|
125
125
|
unless CvtModel::VALUE_TYPES.include?(type)
|
|
126
|
-
raise "Unknown type '#{type}' for #{target_name} #{packet_name}"
|
|
126
|
+
raise "Unknown type '#{type}' for #{target_name} #{packet_name} (inject_tlm)"
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
if item_hash
|
|
@@ -237,7 +237,7 @@ module OpenC3
|
|
|
237
237
|
result['read_timeout'] = @read_timeout
|
|
238
238
|
result['connect_timeout'] = @connect_timeout
|
|
239
239
|
result['include_request_in_response'] = @include_request_in_response
|
|
240
|
-
result['
|
|
240
|
+
result['response_queue_length'] = @response_queue.length
|
|
241
241
|
return result
|
|
242
242
|
end
|
|
243
243
|
end
|
data/lib/openc3/io/json_drb.rb
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
require 'socket'
|
|
19
19
|
require 'json'
|
|
20
|
+
require 'set'
|
|
20
21
|
# require 'drb/acl'
|
|
21
22
|
require 'drb/drb'
|
|
22
23
|
require 'openc3/io/json_rpc'
|
|
@@ -47,8 +48,9 @@ module OpenC3
|
|
|
47
48
|
# JsonDRb implements the JSON-RPC 2.0 Specification to provide an interface
|
|
48
49
|
# for both internal and external tools to access the OpenC3 server. It
|
|
49
50
|
# provides methods to install an access control list to control access to the
|
|
50
|
-
# API.
|
|
51
|
-
#
|
|
51
|
+
# API. Access is controlled by a required method_whitelist which names every
|
|
52
|
+
# method that may be invoked. There is no permissive fallback: any method not
|
|
53
|
+
# explicitly listed is rejected.
|
|
52
54
|
class JsonDRb
|
|
53
55
|
# Minimum amount of time in seconds to receive the JSON request,
|
|
54
56
|
# process it, and send the response. Requests for less than this amount
|
|
@@ -62,18 +64,21 @@ module OpenC3
|
|
|
62
64
|
|
|
63
65
|
# @return [Integer] The number of JSON-RPC requests processed
|
|
64
66
|
attr_accessor :request_count
|
|
65
|
-
# @return [
|
|
66
|
-
|
|
67
|
+
# @return [Set<String>] Set of downcased method names which may be called
|
|
68
|
+
attr_reader :method_whitelist
|
|
67
69
|
# @return [ACL] The access control list
|
|
68
70
|
# attr_accessor :acl
|
|
69
71
|
|
|
70
72
|
attr_accessor :object
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
# @param method_whitelist [Enumerable<String>] Every method name which may
|
|
75
|
+
# be called over JSON-RPC. Required and must not be empty since there is
|
|
76
|
+
# no permissive fallback.
|
|
77
|
+
def initialize(method_whitelist:)
|
|
78
|
+
self.method_whitelist = method_whitelist
|
|
73
79
|
@thread = nil
|
|
74
80
|
# @acl = nil
|
|
75
81
|
@object = nil
|
|
76
|
-
@method_whitelist = nil
|
|
77
82
|
@request_count = 0
|
|
78
83
|
@request_times = []
|
|
79
84
|
@request_times_index = 0
|
|
@@ -82,6 +87,18 @@ module OpenC3
|
|
|
82
87
|
@server_mutex = Mutex.new
|
|
83
88
|
end
|
|
84
89
|
|
|
90
|
+
# Replaces the set of methods which may be called over JSON-RPC. Names are
|
|
91
|
+
# downcased to match the dispatch in {#process_request}.
|
|
92
|
+
#
|
|
93
|
+
# @param whitelist [Enumerable<String>] Every method name which may be called
|
|
94
|
+
def method_whitelist=(whitelist)
|
|
95
|
+
if whitelist.nil? or whitelist.to_a.empty?
|
|
96
|
+
raise ArgumentError, "method_whitelist is required and must not be empty"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
@method_whitelist = whitelist.map { |name| name.to_s.downcase }.to_set
|
|
100
|
+
end
|
|
101
|
+
|
|
85
102
|
# Returns the number of connected clients
|
|
86
103
|
# @return [Integer] The number of connected clients
|
|
87
104
|
def num_clients
|
|
@@ -254,8 +271,7 @@ module OpenC3
|
|
|
254
271
|
error_code = nil
|
|
255
272
|
response_data = nil
|
|
256
273
|
|
|
257
|
-
if
|
|
258
|
-
(!@method_whitelist and !JsonRpcRequest::DANGEROUS_METHODS.include?(request.method.downcase()))
|
|
274
|
+
if @method_whitelist.include?(request.method.downcase())
|
|
259
275
|
begin
|
|
260
276
|
if request.keyword_params
|
|
261
277
|
result = @object.public_send(request.method.downcase().intern, *request.params, **request.keyword_params)
|
data/lib/openc3/io/json_rpc.rb
CHANGED
|
@@ -227,8 +227,6 @@ module OpenC3
|
|
|
227
227
|
|
|
228
228
|
# Represents a JSON Remote Procedure Call Request
|
|
229
229
|
class JsonRpcRequest < JsonRpc
|
|
230
|
-
DANGEROUS_METHODS = Set['__send__', 'send', 'instance_eval', 'instance_exec']
|
|
231
|
-
|
|
232
230
|
# @param method_name [String] The name of the method to call
|
|
233
231
|
# @param method_params [Array<String>] Array of strings which represent the
|
|
234
232
|
# parameters to send to the method
|
|
@@ -113,6 +113,7 @@ module OpenC3
|
|
|
113
113
|
@cycle_minute = Integer(@cycle_minute) if @cycle_minute
|
|
114
114
|
@enforce_time_order = ConfigParser.handle_true_false(enforce_time_order)
|
|
115
115
|
@out_of_order = false
|
|
116
|
+
@bad_time = false
|
|
116
117
|
@mutex = Mutex.new
|
|
117
118
|
@file = nil
|
|
118
119
|
@file_size = 0
|
|
@@ -125,6 +126,13 @@ module OpenC3
|
|
|
125
126
|
@cleanup_offsets = []
|
|
126
127
|
@cleanup_times = []
|
|
127
128
|
@previous_time_nsec_since_epoch = nil
|
|
129
|
+
# Stored packets older than 20 years are treated as invalid (e.g. 1970 before
|
|
130
|
+
# clock/GPS sync). They never drive file segmentation nor become the ordering
|
|
131
|
+
# baseline, so a late-syncing clock doesn't thrash log files.
|
|
132
|
+
@min_valid_time_nsec = (@start_time - (20 * 365 * 24 * 60 * 60)).to_nsec_from_epoch
|
|
133
|
+
# Validity (valid/invalid time) of the packets in the currently open file. Used to
|
|
134
|
+
# segregate invalid-time (1970) packets into their own file. nil = no packets yet.
|
|
135
|
+
@current_file_valid = nil
|
|
128
136
|
@tmp_dir = Dir.mktmpdir
|
|
129
137
|
@wait_threads = []
|
|
130
138
|
|
|
@@ -280,6 +288,7 @@ module OpenC3
|
|
|
280
288
|
@first_time = nil
|
|
281
289
|
@last_time = nil
|
|
282
290
|
@previous_time_nsec_since_epoch = nil
|
|
291
|
+
@current_file_valid = nil
|
|
283
292
|
Logger.debug "Log File Opened : #{@filename}"
|
|
284
293
|
rescue => e
|
|
285
294
|
Logger.error "Error starting new log file: #{e.formatted}"
|
|
@@ -289,7 +298,14 @@ module OpenC3
|
|
|
289
298
|
|
|
290
299
|
# @enforce_time_order requires the timestamps on each write to be greater than the previous
|
|
291
300
|
# process_out_of_order ignores the timestamps for the current entry (used to ignore timestamps on metadata entries, vs actual packets)
|
|
292
|
-
|
|
301
|
+
# stored: stored packets roll a new file when GOOD times move backward (a new /
|
|
302
|
+
# overlapping replay run). Packets with an invalid time (e.g. 1970 before clock/GPS
|
|
303
|
+
# sync) - realtime or stored - are segregated into their own file: the first valid
|
|
304
|
+
# packet after a run of 1970s rolls a fresh, properly-named file (and vice-versa).
|
|
305
|
+
def prepare_write(time_nsec_since_epoch, data_length, redis_topic = nil, redis_offset = nil, allow_new_file: true, process_out_of_order: true, stored: false)
|
|
306
|
+
# An invalid time is a packet from before the clock/GPS synced (e.g. 1970). This
|
|
307
|
+
# applies to both realtime and stored packets so neither taints a file's filename.
|
|
308
|
+
packet_valid = !(process_out_of_order and time_nsec_since_epoch < @min_valid_time_nsec)
|
|
293
309
|
# This check includes logging_enabled again because it might have changed since we acquired the mutex
|
|
294
310
|
# Ensures new files based on size, and ensures always increasing time order in files
|
|
295
311
|
if @logging_enabled
|
|
@@ -299,17 +315,37 @@ module OpenC3
|
|
|
299
315
|
elsif @cycle_size and ((@file_size + data_length) > @cycle_size)
|
|
300
316
|
Logger.debug("Log writer start new file due to cycle size #{@cycle_size}")
|
|
301
317
|
start_new_file() if allow_new_file
|
|
318
|
+
elsif process_out_of_order and !@current_file_valid.nil? and packet_valid != @current_file_valid and allow_new_file
|
|
319
|
+
# Time validity changed (1970 <-> valid): segregate invalid-time packets into
|
|
320
|
+
# their own file so valid files keep a clean, correctly-timed filename.
|
|
321
|
+
Logger.debug("Log writer start new file due to time validity change")
|
|
322
|
+
start_new_file()
|
|
302
323
|
elsif process_out_of_order and @enforce_time_order and @previous_time_nsec_since_epoch and (@previous_time_nsec_since_epoch > time_nsec_since_epoch)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
Logger.
|
|
307
|
-
|
|
324
|
+
if stored and packet_valid and allow_new_file
|
|
325
|
+
# Good stored time went backward => new/overlapping replay run. Roll a new
|
|
326
|
+
# file so each stored file stays monotonic (start_new_file resets @previous_time).
|
|
327
|
+
Logger.debug("Log writer start new file due to out of order stored time")
|
|
328
|
+
start_new_file()
|
|
329
|
+
elsif !stored
|
|
330
|
+
# Warning: Creating new files here can cause lots of files to be created if packets make it through out of order
|
|
331
|
+
# Changed to just a warning to prevent file thrashing
|
|
332
|
+
unless @out_of_order
|
|
333
|
+
Logger.warn("Log writer out of order time detected (increase buffer depth?): #{Time.from_nsec_from_epoch(@previous_time_nsec_since_epoch)} #{Time.from_nsec_from_epoch(time_nsec_since_epoch)}")
|
|
334
|
+
@out_of_order = true
|
|
335
|
+
end
|
|
308
336
|
end
|
|
309
337
|
end
|
|
310
338
|
end
|
|
311
339
|
@last_offsets[redis_topic] = redis_offset if redis_topic and redis_offset # This is needed for the redis offset marker entry at the end of the log file
|
|
312
|
-
|
|
340
|
+
if process_out_of_order
|
|
341
|
+
@current_file_valid = packet_valid
|
|
342
|
+
if packet_valid
|
|
343
|
+
@previous_time_nsec_since_epoch = time_nsec_since_epoch
|
|
344
|
+
elsif !@bad_time
|
|
345
|
+
Logger.warn("Log writer segregating invalid packet time (before clock sync?): #{Time.from_nsec_from_epoch(time_nsec_since_epoch)}")
|
|
346
|
+
@bad_time = true
|
|
347
|
+
end
|
|
348
|
+
end
|
|
313
349
|
end
|
|
314
350
|
|
|
315
351
|
# Closing a log file isn't critical so we just log an error. NOTE: This also trims the Redis stream
|
|
@@ -96,13 +96,12 @@ module OpenC3
|
|
|
96
96
|
@mutex.lock if take_mutex
|
|
97
97
|
begin
|
|
98
98
|
if entry_type == :RAW_PACKET or entry_type == :JSON_PACKET
|
|
99
|
-
# Only care about the timestamps on the real packets being in order
|
|
100
99
|
process_out_of_order = true
|
|
101
100
|
else
|
|
102
101
|
# Metadata timestamps don't matter
|
|
103
102
|
process_out_of_order = false
|
|
104
103
|
end
|
|
105
|
-
prepare_write(time_nsec_since_epoch, data.length, redis_topic, redis_offset, allow_new_file: allow_new_file, process_out_of_order: process_out_of_order)
|
|
104
|
+
prepare_write(time_nsec_since_epoch, data.length, redis_topic, redis_offset, allow_new_file: allow_new_file, process_out_of_order: process_out_of_order, stored: stored)
|
|
106
105
|
write_entry(entry_type, cmd_or_tlm, target_name, packet_name, time_nsec_since_epoch, stored, data, id, received_time_nsec_since_epoch: received_time_nsec_since_epoch, extra: extra) if @file
|
|
107
106
|
ensure
|
|
108
107
|
@mutex.unlock if take_mutex
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# encoding:
|
|
1
|
+
# encoding: utf-8
|
|
2
2
|
|
|
3
3
|
# Copyright 2026 OpenC3, Inc.
|
|
4
4
|
# All Rights Reserved.
|
|
@@ -35,13 +35,19 @@ module OpenC3
|
|
|
35
35
|
# (microservice name, or "REINGEST:<job_id>").
|
|
36
36
|
# @param check_limits [Boolean] When false, skips the Packet#check_limits call.
|
|
37
37
|
# Reingest passes false so historical data does not re-fire limits events.
|
|
38
|
+
# @param stored_limits_mode [String] Controls limits handling for stored packets.
|
|
39
|
+
# 'PROCESS' (default) processes limits normally.
|
|
40
|
+
# 'LOG' still evaluates limits (callback handles suppressing reactions).
|
|
41
|
+
# 'DISABLE' skips limits evaluation entirely for stored packets and omits
|
|
42
|
+
# limits states from the decommutated output.
|
|
38
43
|
# @param metric [Metric, nil] Optional; when set, records decom_duration_seconds.
|
|
39
44
|
# @param error_callback [Proc, nil] Called as error_callback.call(exception) when
|
|
40
45
|
# Packet#process or Packet#check_limits raises. The microservice uses this to
|
|
41
46
|
# bump its decom_error_total metric.
|
|
42
47
|
# @return [Integer] Number of (sub)packets published.
|
|
43
48
|
def decom_and_publish(packet, scope:, target_names:, logger:, name:,
|
|
44
|
-
check_limits: true,
|
|
49
|
+
check_limits: true, stored_limits_mode: 'PROCESS',
|
|
50
|
+
metric: nil, error_callback: nil)
|
|
45
51
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC) if metric
|
|
46
52
|
published = 0
|
|
47
53
|
|
|
@@ -62,9 +68,14 @@ module OpenC3
|
|
|
62
68
|
logger.error e.message
|
|
63
69
|
end
|
|
64
70
|
|
|
65
|
-
packet_or_subpacket.
|
|
71
|
+
disable_stored_limits = packet_or_subpacket.stored && stored_limits_mode == 'DISABLE'
|
|
72
|
+
if check_limits && !disable_stored_limits
|
|
73
|
+
packet_or_subpacket.check_limits(System.limits_set)
|
|
74
|
+
end
|
|
66
75
|
|
|
67
|
-
TelemetryDecomTopic.write_packet(packet_or_subpacket,
|
|
76
|
+
TelemetryDecomTopic.write_packet(packet_or_subpacket,
|
|
77
|
+
include_limits_states: !disable_stored_limits,
|
|
78
|
+
scope: scope)
|
|
68
79
|
published += 1
|
|
69
80
|
end
|
|
70
81
|
|
|
@@ -100,6 +100,8 @@ module OpenC3
|
|
|
100
100
|
@limits_response_thread = nil
|
|
101
101
|
System.telemetry.limits_change_callback = method(:limits_change_callback)
|
|
102
102
|
LimitsEventTopic.sync_system(scope: @scope)
|
|
103
|
+
target_model = TargetModel.get_model(name: @target_names[0], scope: @scope)
|
|
104
|
+
@stored_limits_mode = target_model ? (target_model.stored_limits_mode || 'PROCESS') : 'PROCESS'
|
|
103
105
|
@error_count = 0
|
|
104
106
|
@metric.set(name: 'decom_total', value: @count, type: 'counter')
|
|
105
107
|
@metric.set(name: 'decom_error_total', value: @error_count, type: 'counter')
|
|
@@ -184,6 +186,7 @@ module OpenC3
|
|
|
184
186
|
logger: @logger,
|
|
185
187
|
name: @name,
|
|
186
188
|
check_limits: true,
|
|
189
|
+
stored_limits_mode: @stored_limits_mode,
|
|
187
190
|
metric: @metric,
|
|
188
191
|
error_callback: ->(e) {
|
|
189
192
|
@error_count += 1
|
|
@@ -253,9 +256,11 @@ module OpenC3
|
|
|
253
256
|
event = { type: :LIMITS_CHANGE, target_name: packet.target_name, packet_name: packet.packet_name,
|
|
254
257
|
item_name: item.name, old_limits_state: old_limits_state.to_s, new_limits_state: item.limits.state.to_s,
|
|
255
258
|
time_nsec: packet_time.to_nsec_from_epoch, message: message.to_s }
|
|
259
|
+
suppress_stored = packet.stored && @stored_limits_mode != 'PROCESS'
|
|
260
|
+
event[:suppress_stored] = true if suppress_stored
|
|
256
261
|
LimitsEventTopic.write(event, scope: @scope)
|
|
257
262
|
|
|
258
|
-
if item.limits.response
|
|
263
|
+
if item.limits.response && !suppress_stored
|
|
259
264
|
copied_packet = packet.deep_copy
|
|
260
265
|
copied_item = packet.items[item.name]
|
|
261
266
|
@limits_response_queue << [copied_packet, copied_item, old_limits_state]
|