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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e6cc1e03ca8cd4ac75025fc7cd4758099c513957b012291e7b332e8c61c513cf
|
|
4
|
+
data.tar.gz: '058a71978241f50cc97a1ebefe70a7a6ecc69a9f1fb51e11690d50fd167ae60f'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a96529177ee05a9f04257e4668f245c7346692b039e000f970166fb311c80992401f19def70cbe63844510aa97b2a90ab89bafd835b377aecd14ef0505cb2feb
|
|
7
|
+
data.tar.gz: 3a1c5152b36c733009daaefb07786087b428bbf172dd3ecbee2cfb4bb82de88c84ac0af727c25287cd4af5d1038812e1f4a3d2566c935cf0c519b984163013f5
|
data/bin/openc3cli
CHANGED
|
@@ -33,6 +33,7 @@ require 'openc3/models/queue_model'
|
|
|
33
33
|
require 'openc3/models/scope_model'
|
|
34
34
|
require 'openc3/models/tool_model'
|
|
35
35
|
require 'openc3/packets/packet_config'
|
|
36
|
+
require 'openc3/packets/parsers/xtce_converter'
|
|
36
37
|
require 'openc3/utilities/bucket'
|
|
37
38
|
require 'openc3/utilities/cli_generator'
|
|
38
39
|
require 'openc3/utilities/local_mode'
|
|
@@ -107,7 +108,8 @@ def xtce_converter(args)
|
|
|
107
108
|
options = {}
|
|
108
109
|
option_parser = OptionParser.new do |opts|
|
|
109
110
|
opts.banner = "Usage: xtce_converter [options] --import input_xtce_filename --output output_dir\n"+
|
|
110
|
-
" xtce_converter [options] --plugin /PATH/FILENAME.gem --output output_dir
|
|
111
|
+
" xtce_converter [options] --plugin /PATH/FILENAME.gem --output output_dir "+
|
|
112
|
+
" --variables variables.txt --root_target root_target_name --time_association_name time_association_name"
|
|
111
113
|
opts.separator("")
|
|
112
114
|
opts.on("-h", "--help", "Show this message") do
|
|
113
115
|
puts opts
|
|
@@ -125,6 +127,12 @@ def xtce_converter(args)
|
|
|
125
127
|
opts.on("-v", "--variables", "Optional variables file to pass to the plugin") do |arg|
|
|
126
128
|
options[:variables] = arg
|
|
127
129
|
end
|
|
130
|
+
opts.on("-r ROOT_TARGET", "--root_target ROOT_TARGET", "Optional flag to set which target is at the root of an XTCE document. If not specified, each target will be placed under a generic 'root' spacesystem") do |arg|
|
|
131
|
+
options[:root_target_name] = arg
|
|
132
|
+
end
|
|
133
|
+
opts.on("-t TIME_ASSOCIATION_NAME", "--time_association_name TIME_ASSOCIATION_NAME", "Optional flag to set which target is at the root of an XTCE document. If not specified, each target will be placed under a generic 'root' spacesystem") do |arg|
|
|
134
|
+
options[:time_association_name] = "PACKET_TIME"
|
|
135
|
+
end
|
|
128
136
|
end
|
|
129
137
|
|
|
130
138
|
begin
|
|
@@ -156,8 +164,10 @@ def xtce_converter(args)
|
|
|
156
164
|
puts "Installing #{File.basename(options[:plugin])}"
|
|
157
165
|
plugin_hash = OpenC3::PluginModel.install_phase1(options[:plugin], existing_variables: variables, scope: 'DEFAULT', validate_only: true)
|
|
158
166
|
plugin_hash['variables']['xtce_output'] = options[:output]
|
|
167
|
+
plugin_hash['variables']['time_association_name'] = options[:time_association_name]
|
|
159
168
|
OpenC3::PluginModel.install_phase2(plugin_hash, scope: 'DEFAULT', validate_only: true,
|
|
160
169
|
gem_file_path: options[:plugin])
|
|
170
|
+
OpenC3::XtceConverter.combine_output_xtce(options[:output], options[:root_target_name])
|
|
161
171
|
result = 0 # bash and Windows consider 0 success
|
|
162
172
|
rescue => e
|
|
163
173
|
puts "Error: #{e.message}"
|
|
@@ -371,9 +381,10 @@ def load_plugin(plugin_file_path, scope:, plugin_hash_file: nil, force: false, v
|
|
|
371
381
|
gem_name = full_name.split('-')[0..-2].join('-')
|
|
372
382
|
if file_gem_name == gem_name
|
|
373
383
|
found = true
|
|
374
|
-
|
|
375
|
-
if
|
|
376
|
-
|
|
384
|
+
force_install = force || ENV['OPENC3_FORCE_INSTALL']
|
|
385
|
+
# Upgrade if version changed or force install is set
|
|
386
|
+
if file_full_name != full_name || force_install
|
|
387
|
+
update_plugin(plugin_file_path, plugin_name, scope: scope, existing_plugin_name: plugin_name, force: force_install)
|
|
377
388
|
else
|
|
378
389
|
puts "No version change detected for: #{plugin_name}"
|
|
379
390
|
end
|
|
@@ -79,12 +79,12 @@ GENERIC_READ_CONVERSION_START:
|
|
|
79
79
|
ruby_example: |
|
|
80
80
|
APPEND_ITEM ITEM1 32 UINT
|
|
81
81
|
GENERIC_READ_CONVERSION_START
|
|
82
|
-
|
|
82
|
+
(value * 1.5).to_i # Convert the value by a scale factor
|
|
83
83
|
GENERIC_READ_CONVERSION_END
|
|
84
84
|
python_example: |
|
|
85
85
|
APPEND_ITEM ITEM1 32 UINT
|
|
86
86
|
GENERIC_READ_CONVERSION_START
|
|
87
|
-
|
|
87
|
+
int(value * 1.5) # Convert the value by a scale factor
|
|
88
88
|
GENERIC_READ_CONVERSION_END
|
|
89
89
|
parameters:
|
|
90
90
|
- name: Converted Type
|
|
@@ -141,12 +141,12 @@ GENERIC_WRITE_CONVERSION_START:
|
|
|
141
141
|
ruby_example: |
|
|
142
142
|
APPEND_PARAMETER ITEM1 32 UINT 0 0xFFFFFFFF 0
|
|
143
143
|
GENERIC_WRITE_CONVERSION_START
|
|
144
|
-
|
|
144
|
+
(value * 1.5).to_i # Convert the value by a scale factor
|
|
145
145
|
GENERIC_WRITE_CONVERSION_END
|
|
146
146
|
python_example: |
|
|
147
147
|
APPEND_PARAMETER ITEM1 32 UINT 0 0xFFFFFFFF 0
|
|
148
148
|
GENERIC_WRITE_CONVERSION_START
|
|
149
|
-
|
|
149
|
+
int(value * 1.5) # Convert the value by a scale factor
|
|
150
150
|
GENERIC_WRITE_CONVERSION_END
|
|
151
151
|
GENERIC_WRITE_CONVERSION_END:
|
|
152
152
|
summary: Complete a generic write conversion
|
data/data/config/plugins.yaml
CHANGED
|
@@ -13,6 +13,44 @@ VARIABLE:
|
|
|
13
13
|
required: true
|
|
14
14
|
description: Default value of the variable
|
|
15
15
|
values: .+
|
|
16
|
+
VARIABLE_DESCRIPTION:
|
|
17
|
+
summary: Add a description to a plugin variable
|
|
18
|
+
description: The VARIABLE_DESCRIPTION keyword adds a human-readable description to a previously defined VARIABLE. This description appears as hint text below the variable input field during plugin installation. Must follow a VARIABLE definition.
|
|
19
|
+
since: 7.0.0
|
|
20
|
+
example: |
|
|
21
|
+
VARIABLE port 8080
|
|
22
|
+
VARIABLE_DESCRIPTION port "TCP port for the target connection"
|
|
23
|
+
parameters:
|
|
24
|
+
- name: Variable Name
|
|
25
|
+
required: true
|
|
26
|
+
description: Name of the variable to describe. Must match a previously defined VARIABLE name.
|
|
27
|
+
values: .+
|
|
28
|
+
- name: Description
|
|
29
|
+
required: true
|
|
30
|
+
description: Human-readable description of the variable's purpose
|
|
31
|
+
values: .+
|
|
32
|
+
VARIABLE_STATE:
|
|
33
|
+
summary: Add a selectable state for a plugin variable
|
|
34
|
+
description: The VARIABLE_STATE keyword defines a selectable state for a previously defined VARIABLE. When states are defined for a variable, it renders as a dropdown/combobox in the plugin installation dialog instead of a text field. Users can still type custom values if needed. Multiple VARIABLE_STATE keywords can be used to define multiple states. Must follow a VARIABLE definition.
|
|
35
|
+
since: 7.0.0
|
|
36
|
+
example: |
|
|
37
|
+
VARIABLE target_name INST
|
|
38
|
+
VARIABLE_DESCRIPTION target_name "Select the target name"
|
|
39
|
+
VARIABLE_STATE target_name INST "Primary instrument"
|
|
40
|
+
VARIABLE_STATE target_name INST2 "Secondary instrument"
|
|
41
|
+
parameters:
|
|
42
|
+
- name: Variable Name
|
|
43
|
+
required: true
|
|
44
|
+
description: Name of the variable this state belongs to. Must match a previously defined VARIABLE name.
|
|
45
|
+
values: .+
|
|
46
|
+
- name: State Value
|
|
47
|
+
required: true
|
|
48
|
+
description: The value that will be used when this state is selected
|
|
49
|
+
values: .+
|
|
50
|
+
- name: State Description
|
|
51
|
+
required: false
|
|
52
|
+
description: Human-readable description of what this state represents. Appears as subtitle in the dropdown.
|
|
53
|
+
values: .+
|
|
16
54
|
NEEDS_DEPENDENCIES:
|
|
17
55
|
summary: Indicates the plugin needs dependencies and sets the GEM_HOME environment variable
|
|
18
56
|
description: If the plugin has a top level lib folder or lists runtime dependencies in the gemspec,
|
data/data/config/screen.yaml
CHANGED
|
@@ -125,6 +125,29 @@ SUBSETTING:
|
|
|
125
125
|
LABELVALUELIMITSBAR INST HEALTH_STATUS TEMP1
|
|
126
126
|
SUBSETTING 0 TEXTCOLOR green # Change the label's text to green
|
|
127
127
|
END
|
|
128
|
+
TOOLTIP:
|
|
129
|
+
summary: Adds a tooltip to the previously defined widget
|
|
130
|
+
description: TOOLTIP applies a custom hover tooltip to the widget defined immediately before it.
|
|
131
|
+
This allows you to provide helpful descriptions, mnemonics, or other contextual information
|
|
132
|
+
that appears when the user hovers over a widget. The tooltip overrides any default tooltip
|
|
133
|
+
that the widget may have.
|
|
134
|
+
since: 6.10.3
|
|
135
|
+
parameters:
|
|
136
|
+
- name: Tooltip Text
|
|
137
|
+
required: true
|
|
138
|
+
description: The text to display in the tooltip when hovering over the widget.
|
|
139
|
+
values: .+
|
|
140
|
+
- name: Delay
|
|
141
|
+
required: false
|
|
142
|
+
description: The delay in milliseconds before the tooltip appears (default = 600).
|
|
143
|
+
values: \d+
|
|
144
|
+
example: |
|
|
145
|
+
LED INST PARAMS VALUE5 RAW 25 20
|
|
146
|
+
SETTING LED_COLOR 0 GREEN
|
|
147
|
+
SETTING LED_COLOR 1 RED
|
|
148
|
+
TOOLTIP "Mnemonic: ABCDEF. This is the Star Tracker On/Off Status"
|
|
149
|
+
VALUE INST HEALTH_STATUS TEMP1
|
|
150
|
+
TOOLTIP "Temperature sensor 1: Primary thermal control" 1000
|
|
128
151
|
NAMED_WIDGET:
|
|
129
152
|
summary: Name a widget to allow access to it via the getNamedWidget method
|
|
130
153
|
description: To programmatically access parts of a telemetry screen you need
|
data/data/config/target.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
TARGET:
|
|
3
3
|
summary: Defines a new target
|
|
4
|
-
example: TARGET
|
|
4
|
+
example: TARGET KEYSIGHT_N6700 PWR_SUPPLY1
|
|
5
5
|
parameters:
|
|
6
6
|
- name: Folder Name
|
|
7
7
|
required: true
|
|
@@ -10,8 +10,13 @@ TARGET:
|
|
|
10
10
|
- name: Name
|
|
11
11
|
required: true
|
|
12
12
|
description:
|
|
13
|
-
The target name. While this
|
|
14
|
-
it can be different to create multiple targets based on the same target
|
|
13
|
+
The target name. While this typically matches the Folder Name
|
|
14
|
+
it can be different to create multiple targets based on the same target definition.
|
|
15
|
+
As in the Example Usage, the target folder is KEYSIGHT_N6700 but the target name is PWR_SUPPLY1.
|
|
16
|
+
To create multiple targets from the same folder, just define multiple TARGET entries
|
|
17
|
+
with different target names. To make the target definition flexbible, you can use ERB to
|
|
18
|
+
insert the target name in procedures, libraries, etc via <%= target_name %>.
|
|
19
|
+
See [ERB target_name](/docs/configuration/format#target_name) for more information.
|
|
15
20
|
values: .*
|
|
16
21
|
modifiers:
|
|
17
22
|
CMD_BUFFER_DEPTH:
|
data/data/config/widgets.yaml
CHANGED
|
@@ -204,6 +204,36 @@ Decoration Widgets:
|
|
|
204
204
|
SPACER 0 100
|
|
205
205
|
LABEL "Spacer above"
|
|
206
206
|
END
|
|
207
|
+
FILEDISPLAY:
|
|
208
|
+
summary: Displays the contents of a target file with syntax highlighting
|
|
209
|
+
since: 6.10.3
|
|
210
|
+
parameters:
|
|
211
|
+
- name: File path
|
|
212
|
+
required: true
|
|
213
|
+
description: Path to the file relative to the target folder (e.g. "INST/procedures/file.rb")
|
|
214
|
+
values: .+
|
|
215
|
+
- name: Width
|
|
216
|
+
required: false
|
|
217
|
+
description: Width of the widget in pixels (default = 600)
|
|
218
|
+
values: \d+
|
|
219
|
+
- name: Height
|
|
220
|
+
required: false
|
|
221
|
+
description: Height of the widget in pixels (default = 300)
|
|
222
|
+
values: \d+
|
|
223
|
+
example: |
|
|
224
|
+
FILEDISPLAY "INST/data/sample.json" 400 200
|
|
225
|
+
FILECHECKSUM:
|
|
226
|
+
summary: Displays SHA-256 checksum of one or more files, with comparison if multiple
|
|
227
|
+
since: 6.10.3
|
|
228
|
+
parameters:
|
|
229
|
+
- name: File path
|
|
230
|
+
required: true
|
|
231
|
+
description: Path to a file relative to the target folder (e.g. "INST/procedures/file.rb"). Multiple file paths can be provided to compare checksums.
|
|
232
|
+
values: .+
|
|
233
|
+
example: |
|
|
234
|
+
FILECHECKSUM "INST/data/sample.json"
|
|
235
|
+
FILECHECKSUM "INST/data/sample.json" "INST2/data/sample.json"
|
|
236
|
+
FILECHECKSUM "INST/data/file1.bin" "INST/data/file2.bin" "INST/data/file3.bin"
|
|
207
237
|
Telemetry Widgets:
|
|
208
238
|
description: Telemetry widgets are used to display telemetry values.
|
|
209
239
|
The first parameters to each of these widgets is a telemetry mnemonic.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
/*
|
|
17
17
|
# Modified by OpenC3, Inc.
|
|
18
|
-
# All changes Copyright
|
|
18
|
+
# All changes Copyright 2026, OpenC3, Inc.
|
|
19
19
|
# All Rights Reserved
|
|
20
20
|
#
|
|
21
21
|
# This file may also be used under the terms of a commercial license
|
|
@@ -33,6 +33,7 @@ static ID id_ivar_line_number = 0;
|
|
|
33
33
|
static ID id_ivar_keyword = 0;
|
|
34
34
|
static ID id_ivar_parameters = 0;
|
|
35
35
|
static ID id_ivar_line = 0;
|
|
36
|
+
static ID id_ivar_preserve_lines = 0;
|
|
36
37
|
static ID id_method_readline = 0;
|
|
37
38
|
static ID id_method_close = 0;
|
|
38
39
|
static ID id_method_pos = 0;
|
|
@@ -42,6 +43,7 @@ static ID id_method_strip = 0;
|
|
|
42
43
|
static ID id_method_to_s = 0;
|
|
43
44
|
static ID id_method_upcase = 0;
|
|
44
45
|
static ID id_method_parse_errors = 0;
|
|
46
|
+
static ID id_method_chomp_exclamation = 0;
|
|
45
47
|
|
|
46
48
|
/*
|
|
47
49
|
* Removes quotes from the given string if present.
|
|
@@ -131,53 +133,61 @@ static VALUE parse_loop(VALUE self, VALUE io, VALUE yield_non_keyword_lines, VAL
|
|
|
131
133
|
rb_set_errinfo(Qnil);
|
|
132
134
|
break;
|
|
133
135
|
}
|
|
134
|
-
line = rb_funcall(line,
|
|
135
|
-
// Ensure the line length is not 0
|
|
136
|
-
if (RSTRING_LEN(line) == 0) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
136
|
+
line = rb_funcall(line, id_method_chomp_exclamation, 0);
|
|
139
137
|
|
|
140
|
-
if (RTEST(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
{
|
|
138
|
+
if (!RTEST(rb_ivar_get(self, id_ivar_preserve_lines))) {
|
|
139
|
+
line = rb_funcall(line, id_method_strip, 0);
|
|
140
|
+
|
|
141
|
+
// Ensure the line length is not 0
|
|
142
|
+
if (RSTRING_LEN(line) == 0) {
|
|
145
143
|
continue;
|
|
146
144
|
}
|
|
147
|
-
/* Remove the opening quote if we're continuing the line */
|
|
148
|
-
line = rb_str_new(RSTRING_PTR(line) + 1, RSTRING_LEN(line) - 1);
|
|
149
|
-
}
|
|
150
145
|
|
|
151
|
-
|
|
152
|
-
if ((RSTRING_PTR(line)[RSTRING_LEN(line) - 1] == '+') ||
|
|
153
|
-
(RSTRING_PTR(line)[RSTRING_LEN(line) - 1] == '\\'))
|
|
154
|
-
{
|
|
155
|
-
int newline = 0;
|
|
156
|
-
if (RSTRING_PTR(line)[RSTRING_LEN(line) - 1] == '+')
|
|
146
|
+
if (RTEST(string_concat))
|
|
157
147
|
{
|
|
158
|
-
|
|
148
|
+
/* Skip comment lines after a string concat */
|
|
149
|
+
if (RSTRING_PTR(line)[0] == '#')
|
|
150
|
+
{
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
/* Remove the opening quote if we're continuing the line */
|
|
154
|
+
line = rb_str_new(RSTRING_PTR(line) + 1, RSTRING_LEN(line) - 1);
|
|
159
155
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
156
|
+
|
|
157
|
+
/* Check for string continuation */
|
|
158
|
+
if ((RSTRING_PTR(line)[RSTRING_LEN(line) - 1] == '+') ||
|
|
159
|
+
(RSTRING_PTR(line)[RSTRING_LEN(line) - 1] == '\\'))
|
|
160
|
+
{
|
|
161
|
+
int newline = 0;
|
|
162
|
+
if (RSTRING_PTR(line)[RSTRING_LEN(line) - 1] == '+')
|
|
163
|
+
{
|
|
164
|
+
newline = 1;
|
|
165
|
+
}
|
|
166
|
+
rb_str_resize(line, RSTRING_LEN(line) - 1);
|
|
167
|
+
line = rb_funcall(line, id_method_strip, 0);
|
|
168
|
+
rb_str_append(ivar_line, line);
|
|
169
|
+
rb_str_resize(ivar_line, RSTRING_LEN(ivar_line) - 1);
|
|
170
|
+
if (newline == 1)
|
|
171
|
+
{
|
|
172
|
+
rb_str_cat2(ivar_line, "\n");
|
|
173
|
+
}
|
|
174
|
+
rb_ivar_set(self, id_ivar_line, ivar_line);
|
|
175
|
+
string_concat = Qtrue;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (RSTRING_PTR(line)[RSTRING_LEN(line) - 1] == '&')
|
|
165
179
|
{
|
|
166
|
-
|
|
180
|
+
rb_str_append(ivar_line, line);
|
|
181
|
+
rb_str_resize(ivar_line, RSTRING_LEN(ivar_line) - 1);
|
|
182
|
+
rb_ivar_set(self, id_ivar_line, ivar_line);
|
|
183
|
+
continue;
|
|
167
184
|
}
|
|
168
|
-
rb_ivar_set(self, id_ivar_line, ivar_line);
|
|
169
|
-
string_concat = Qtrue;
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
if (RSTRING_PTR(line)[RSTRING_LEN(line) - 1] == '&')
|
|
173
|
-
{
|
|
174
185
|
rb_str_append(ivar_line, line);
|
|
175
|
-
rb_str_resize(ivar_line, RSTRING_LEN(ivar_line) - 1);
|
|
176
186
|
rb_ivar_set(self, id_ivar_line, ivar_line);
|
|
177
|
-
|
|
187
|
+
} else {
|
|
188
|
+
ivar_line = line;
|
|
189
|
+
rb_ivar_set(self, id_ivar_line, ivar_line);
|
|
178
190
|
}
|
|
179
|
-
rb_str_append(ivar_line, line);
|
|
180
|
-
rb_ivar_set(self, id_ivar_line, ivar_line);
|
|
181
191
|
string_concat = Qfalse;
|
|
182
192
|
|
|
183
193
|
data = rb_funcall(ivar_line, id_method_scan, 1, rx);
|
|
@@ -284,6 +294,7 @@ void Init_config_parser(void)
|
|
|
284
294
|
id_ivar_keyword = rb_intern("@keyword");
|
|
285
295
|
id_ivar_parameters = rb_intern("@parameters");
|
|
286
296
|
id_ivar_line = rb_intern("@line");
|
|
297
|
+
id_ivar_preserve_lines = rb_intern("@preserve_lines");
|
|
287
298
|
id_method_readline = rb_intern("readline");
|
|
288
299
|
id_method_close = rb_intern("close");
|
|
289
300
|
id_method_pos = rb_intern("pos");
|
|
@@ -293,6 +304,7 @@ void Init_config_parser(void)
|
|
|
293
304
|
id_method_to_s = rb_intern("to_s");
|
|
294
305
|
id_method_upcase = rb_intern("upcase");
|
|
295
306
|
id_method_parse_errors = rb_intern("parse_errors");
|
|
307
|
+
id_method_chomp_exclamation = rb_intern("chomp!");
|
|
296
308
|
|
|
297
309
|
mOpenC3 = rb_define_module("OpenC3");
|
|
298
310
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# encoding: ascii-8bit
|
|
2
2
|
|
|
3
|
-
# Copyright
|
|
3
|
+
# Copyright 2026 OpenC3, Inc.
|
|
4
4
|
# All Rights Reserved.
|
|
5
5
|
#
|
|
6
6
|
# This program is free software; you can modify and/or redistribute it
|
|
@@ -135,7 +135,7 @@ module OpenC3
|
|
|
135
135
|
when :STRING, :BLOCK
|
|
136
136
|
if item.array_size
|
|
137
137
|
value = JSON.parse(value) if value.is_a? String
|
|
138
|
-
value =
|
|
138
|
+
value = value.map { |v| v.to_s }
|
|
139
139
|
else
|
|
140
140
|
value = value.to_s
|
|
141
141
|
end
|
|
@@ -25,7 +25,7 @@ require 'openc3/accessors/accessor'
|
|
|
25
25
|
OpenC3.disable_warnings do
|
|
26
26
|
class JsonPath
|
|
27
27
|
def self.process_object(obj_or_str, opts = {})
|
|
28
|
-
obj_or_str.is_a?(String) ? MultiJson.
|
|
28
|
+
obj_or_str.is_a?(String) ? MultiJson.load(obj_or_str, max_nesting: opts[:max_nesting], create_additions: true, allow_nan: true) : obj_or_str
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
end
|
data/lib/openc3/api/tlm_api.rb
CHANGED
|
@@ -284,13 +284,8 @@ module OpenC3
|
|
|
284
284
|
value_type = 'RAW' # Must request the raw value when dealing with the reserved items
|
|
285
285
|
end
|
|
286
286
|
|
|
287
|
-
#
|
|
287
|
+
# Arrays must be accessed as RAW since there's no conversion
|
|
288
288
|
if item['array_size']
|
|
289
|
-
# TODO: This needs work ... we're JSON encoding non numeric array values
|
|
290
|
-
if item['data_type'] == 'STRING' or item['data_type'] == 'BLOCK'
|
|
291
|
-
results << nil
|
|
292
|
-
next
|
|
293
|
-
end
|
|
294
289
|
value_type = 'RAW'
|
|
295
290
|
end
|
|
296
291
|
|
|
@@ -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 2025, OpenC3, Inc.
|
|
18
18
|
# All Rights Reserved
|
|
19
19
|
#
|
|
20
20
|
# This file may also be used under the terms of a commercial license
|
|
@@ -139,6 +139,7 @@ module OpenC3
|
|
|
139
139
|
@line_number = config_parser.line_number
|
|
140
140
|
@usage = usage
|
|
141
141
|
@url = url
|
|
142
|
+
@preserve_lines = false
|
|
142
143
|
end
|
|
143
144
|
end
|
|
144
145
|
|
|
@@ -389,6 +390,10 @@ module OpenC3
|
|
|
389
390
|
return value
|
|
390
391
|
end
|
|
391
392
|
|
|
393
|
+
def set_preserve_lines(state)
|
|
394
|
+
@preserve_lines = state
|
|
395
|
+
end
|
|
396
|
+
|
|
392
397
|
protected
|
|
393
398
|
|
|
394
399
|
# Writes the ERB parsed results
|
|
@@ -500,39 +505,54 @@ module OpenC3
|
|
|
500
505
|
|
|
501
506
|
begin
|
|
502
507
|
line = io.readline
|
|
508
|
+
line.chomp!
|
|
503
509
|
rescue Exception
|
|
504
510
|
break
|
|
505
511
|
end
|
|
506
512
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
next if line.length == 0
|
|
513
|
+
if not @preserve_lines
|
|
514
|
+
line.strip!
|
|
510
515
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
516
|
+
# Ensure the line length is not 0
|
|
517
|
+
if line.length == 0
|
|
518
|
+
if yield_non_keyword_lines
|
|
519
|
+
begin
|
|
520
|
+
yield(nil, [])
|
|
521
|
+
rescue => e
|
|
522
|
+
errors << e
|
|
523
|
+
end
|
|
524
|
+
end
|
|
514
525
|
next
|
|
515
526
|
end
|
|
516
|
-
# Remove the opening quote if we're continuing the line
|
|
517
|
-
line = line[1..-1]
|
|
518
|
-
end
|
|
519
527
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
528
|
+
if string_concat
|
|
529
|
+
# Skip comment lines after a string concatenation
|
|
530
|
+
if (line[0] == '#')
|
|
531
|
+
next
|
|
532
|
+
end
|
|
533
|
+
# Remove the opening quote if we're continuing the line
|
|
534
|
+
line = line[1..-1]
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
# Check for string continuation
|
|
538
|
+
case line[-1]
|
|
539
|
+
when '+', '\\' # String concatenation
|
|
540
|
+
newline = line[-1] == '+'
|
|
541
|
+
# Trim off the concat character plus any spaces, e.g. "line" \
|
|
542
|
+
trim = line[0..-2].strip()
|
|
543
|
+
# Now trim off the last quote so it will flow into the next line
|
|
544
|
+
@line += trim[0..-2]
|
|
545
|
+
@line += "\n" if newline
|
|
546
|
+
string_concat = true
|
|
547
|
+
next
|
|
548
|
+
when '&' # Line continuation
|
|
549
|
+
@line += line[0..-2]
|
|
550
|
+
next
|
|
551
|
+
else
|
|
552
|
+
@line += line
|
|
553
|
+
end
|
|
534
554
|
else
|
|
535
|
-
@line
|
|
555
|
+
@line = line
|
|
536
556
|
end
|
|
537
557
|
string_concat = false
|
|
538
558
|
|
|
@@ -34,6 +34,10 @@ require 'faraday'
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
module OpenC3
|
|
37
|
+
# Number of times to retry a request when a connection error occurs
|
|
38
|
+
RETRY_COUNT = 3
|
|
39
|
+
# Delay between retries in seconds
|
|
40
|
+
RETRY_DELAY = 0.1
|
|
37
41
|
|
|
38
42
|
class JsonApiError < StandardError; end
|
|
39
43
|
|
|
@@ -200,21 +204,41 @@ module OpenC3
|
|
|
200
204
|
|
|
201
205
|
# NOTE: This is a helper method and should not be called directly
|
|
202
206
|
def _send_request(method:, endpoint:, kwargs:)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
207
|
+
uri = URI("#{@url}#{endpoint}")
|
|
208
|
+
@log[0] = "#{method} Request: #{uri.to_s} #{kwargs}"
|
|
209
|
+
STDOUT.puts @log[0] if JsonDRb.debug?
|
|
210
|
+
|
|
211
|
+
retry_count = 0
|
|
212
|
+
while retry_count <= RETRY_COUNT
|
|
213
|
+
begin
|
|
214
|
+
resp = _http_request(method: method, uri: uri, kwargs: kwargs)
|
|
215
|
+
@log[1] = "#{method} Response: #{resp.status} #{resp.headers} #{resp.body}"
|
|
216
|
+
STDOUT.puts @log[1] if JsonDRb.debug?
|
|
217
|
+
@response_data = resp.body
|
|
218
|
+
return resp
|
|
219
|
+
rescue Faraday::ConnectionFailed, Errno::ECONNRESET, Errno::EPIPE, IOError => e
|
|
220
|
+
# Connection errors are retryable - reconnect and try again
|
|
221
|
+
retry_count += 1
|
|
222
|
+
@log[2] = "#{method} Exception: #{e.class}, #{e.message}, #{e.backtrace}"
|
|
223
|
+
if retry_count <= RETRY_COUNT
|
|
224
|
+
Logger.warn("JsonApiObject: Connection error, retry #{retry_count}/#{RETRY_COUNT}: #{e.class} #{e.message}")
|
|
225
|
+
disconnect()
|
|
226
|
+
sleep(RETRY_DELAY)
|
|
227
|
+
connect()
|
|
228
|
+
else
|
|
229
|
+
error = "Api Exception: #{@log[0]} ::: #{@log[1]} ::: #{@log[2]}"
|
|
230
|
+
raise error
|
|
231
|
+
end
|
|
232
|
+
rescue StandardError => e
|
|
233
|
+
@log[2] = "#{method} Exception: #{e.class}, #{e.message}, #{e.backtrace}"
|
|
234
|
+
disconnect()
|
|
235
|
+
error = "Api Exception: #{@log[0]} ::: #{@log[1]} ::: #{@log[2]}"
|
|
236
|
+
raise error
|
|
237
|
+
end
|
|
217
238
|
end
|
|
239
|
+
# Should not reach here, but just in case
|
|
240
|
+
error = "Api Exception: #{@log[0]} ::: #{@log[1]} ::: #{@log[2]}"
|
|
241
|
+
raise error
|
|
218
242
|
end
|
|
219
243
|
|
|
220
244
|
# NOTE: This is a helper method and should not be called directly
|
|
@@ -92,18 +92,36 @@ module OpenC3
|
|
|
92
92
|
'Content-Type' => 'application/json-rpc',
|
|
93
93
|
}
|
|
94
94
|
end
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
|
|
96
|
+
@log[0] = "Request: #{@uri.to_s} #{USER_AGENT} #{data.to_s}"
|
|
97
|
+
STDOUT.puts @log[0] if JsonDRb.debug?
|
|
98
|
+
|
|
99
|
+
retry_count = 0
|
|
100
|
+
while retry_count <= RETRY_COUNT
|
|
101
|
+
begin
|
|
102
|
+
resp = @http.post(@uri, data, headers)
|
|
103
|
+
@log[1] = "Response: #{resp.status} #{resp.headers} #{resp.body}"
|
|
104
|
+
@response_data = resp.body
|
|
105
|
+
STDOUT.puts @log[1] if JsonDRb.debug?
|
|
106
|
+
return resp.body
|
|
107
|
+
rescue Faraday::ConnectionFailed, Errno::ECONNRESET, Errno::EPIPE, IOError => e
|
|
108
|
+
# Connection errors are retryable - reconnect and try again
|
|
109
|
+
retry_count += 1
|
|
110
|
+
@log[2] = "Exception: #{e.class}, #{e.message}, #{e.backtrace}"
|
|
111
|
+
if retry_count <= RETRY_COUNT
|
|
112
|
+
Logger.warn("JsonDRbObject: Connection error, retry #{retry_count}/#{RETRY_COUNT}: #{e.class} #{e.message}")
|
|
113
|
+
disconnect()
|
|
114
|
+
sleep(RETRY_DELAY)
|
|
115
|
+
connect()
|
|
116
|
+
else
|
|
117
|
+
return nil
|
|
118
|
+
end
|
|
119
|
+
rescue StandardError => e
|
|
120
|
+
@log[2] = "Exception: #{e.class}, #{e.message}, #{e.backtrace}"
|
|
121
|
+
return nil
|
|
122
|
+
end
|
|
106
123
|
end
|
|
124
|
+
return nil
|
|
107
125
|
end
|
|
108
126
|
|
|
109
127
|
def handle_response(response:)
|