openc3 6.6.0 → 6.8.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 +77 -16
- data/data/config/command_modifiers.yaml +3 -3
- data/data/config/interface_modifiers.yaml +1 -1
- data/data/config/table_manager.yaml +1 -1
- data/data/config/telemetry_modifiers.yaml +3 -3
- data/data/config/widgets.yaml +2 -2
- data/lib/openc3/accessors.rb +1 -1
- data/lib/openc3/api/cmd_api.rb +15 -4
- data/lib/openc3/api/settings_api.rb +8 -0
- data/lib/openc3/api/stash_api.rb +1 -1
- data/lib/openc3/api/tlm_api.rb +96 -14
- data/lib/openc3/core_ext/kernel.rb +3 -3
- data/lib/openc3/logs/log_writer.rb +16 -12
- data/lib/openc3/microservices/interface_microservice.rb +14 -1
- data/lib/openc3/microservices/plugin_microservice.rb +2 -2
- data/lib/openc3/microservices/queue_microservice.rb +166 -0
- data/lib/openc3/models/cvt_model.rb +140 -3
- data/lib/openc3/models/plugin_model.rb +7 -2
- data/lib/openc3/models/plugin_store_model.rb +70 -0
- data/lib/openc3/models/queue_model.rb +232 -0
- data/lib/openc3/models/target_model.rb +26 -0
- data/lib/openc3/models/tool_model.rb +1 -1
- data/lib/openc3/packets/packet.rb +3 -3
- data/lib/openc3/packets/parsers/state_parser.rb +7 -1
- data/lib/openc3/packets/structure.rb +9 -2
- data/lib/openc3/script/calendar.rb +10 -10
- data/lib/openc3/script/commands.rb +4 -4
- data/lib/openc3/script/queue.rb +80 -0
- data/lib/openc3/script/script.rb +1 -0
- data/lib/openc3/script/script_runner.rb +7 -2
- data/lib/openc3/script/tables.rb +3 -3
- data/lib/openc3/script/web_socket_api.rb +11 -0
- data/lib/openc3/topics/queue_topic.rb +29 -0
- data/lib/openc3/utilities/authorization.rb +1 -1
- data/lib/openc3/utilities/cosmos_rails_formatter.rb +1 -1
- data/lib/openc3/utilities/local_mode.rb +2 -0
- data/lib/openc3/utilities/logger.rb +1 -1
- data/lib/openc3/utilities/running_script.rb +5 -1
- data/lib/openc3/version.rb +5 -5
- 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 +83 -8
@@ -0,0 +1,232 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
# Copyright 2025 OpenC3, Inc.
|
4
|
+
# All Rights Reserved.
|
5
|
+
#
|
6
|
+
# This program is free software; you can modify and/or redistribute it
|
7
|
+
# under the terms of the GNU Affero General Public License
|
8
|
+
# as published by the Free Software Foundation; version 3 with
|
9
|
+
# attribution addendums as found in the LICENSE.txt
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU Affero General Public License for more details.
|
15
|
+
|
16
|
+
# This file may also be used under the terms of a commercial license
|
17
|
+
# if purchased from OpenC3, Inc.
|
18
|
+
|
19
|
+
require 'openc3/models/model'
|
20
|
+
require 'openc3/models/microservice_model'
|
21
|
+
require 'openc3/topics/queue_topic'
|
22
|
+
require 'openc3/utilities/logger'
|
23
|
+
|
24
|
+
module OpenC3
|
25
|
+
class QueueError < StandardError; end
|
26
|
+
|
27
|
+
class QueueModel < Model
|
28
|
+
PRIMARY_KEY = 'openc3__queue'.freeze
|
29
|
+
|
30
|
+
@@class_mutex = Mutex.new
|
31
|
+
|
32
|
+
# NOTE: The following three class methods are used by the ModelController
|
33
|
+
# and are reimplemented to enable various Model class methods to work
|
34
|
+
def self.get(name:, scope:)
|
35
|
+
super("#{scope}__#{PRIMARY_KEY}", name: name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.names(scope:)
|
39
|
+
super("#{scope}__#{PRIMARY_KEY}")
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.all(scope:)
|
43
|
+
super("#{scope}__#{PRIMARY_KEY}")
|
44
|
+
end
|
45
|
+
# END NOTE
|
46
|
+
|
47
|
+
def self.queue_command(name, command:, username:, scope:)
|
48
|
+
model = get_model(name: name, scope: scope)
|
49
|
+
raise QueueError, "Queue '#{name}' not found in scope '#{scope}'" unless model
|
50
|
+
|
51
|
+
if model.state != 'DISABLE'
|
52
|
+
result = Store.zrevrange("#{scope}:#{name}", 0, 0, with_scores: true)
|
53
|
+
if result.empty?
|
54
|
+
index = 1.0
|
55
|
+
else
|
56
|
+
index = result[0][1].to_f + 1
|
57
|
+
end
|
58
|
+
Store.zadd("#{scope}:#{name}", index, { username: username, value: command, timestamp: Time.now.to_nsec_from_epoch }.to_json)
|
59
|
+
model.notify(kind: 'command')
|
60
|
+
else
|
61
|
+
raise QueueError, "Queue '#{name}' is disabled. Command '#{command}' not queued."
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_accessor :name, :state
|
66
|
+
|
67
|
+
def initialize(name:, scope:, state: 'HOLD', updated_at: nil)
|
68
|
+
super("#{scope}__#{PRIMARY_KEY}", name: name, updated_at: updated_at, scope: scope)
|
69
|
+
@microservice_name = "#{scope}__QUEUE__#{name}"
|
70
|
+
if %w(HOLD RELEASE DISABLE).include?(state)
|
71
|
+
@state = state
|
72
|
+
else
|
73
|
+
@state = 'HOLD'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def create(update: false, force: false, queued: false)
|
78
|
+
super(update: update, force: force, queued: queued)
|
79
|
+
if update
|
80
|
+
notify(kind: 'updated')
|
81
|
+
else
|
82
|
+
deploy()
|
83
|
+
notify(kind: 'created')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Hash] generated from the QueueModel
|
88
|
+
def as_json(*a)
|
89
|
+
return {
|
90
|
+
'name' => @name,
|
91
|
+
'scope' => @scope,
|
92
|
+
'state' => @state,
|
93
|
+
'updated_at' => @updated_at
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [] update the redis stream / queue topic that something has changed
|
98
|
+
def notify(kind:)
|
99
|
+
notification = {
|
100
|
+
'kind' => kind,
|
101
|
+
'data' => JSON.generate(as_json(:allow_nan => true)),
|
102
|
+
}
|
103
|
+
QueueTopic.write_notification(notification, scope: @scope)
|
104
|
+
end
|
105
|
+
|
106
|
+
def insert_command(index, command_data)
|
107
|
+
if @state == 'DISABLE'
|
108
|
+
raise QueueError, "Queue '#{@name}' is disabled. Command '#{command_data['value']}' not queued."
|
109
|
+
end
|
110
|
+
|
111
|
+
unless index
|
112
|
+
result = Store.zrevrange("#{@scope}:#{@name}", 0, 0, with_scores: true)
|
113
|
+
if result.empty?
|
114
|
+
index = 1.0
|
115
|
+
else
|
116
|
+
index = result[0][1].to_f + 1
|
117
|
+
end
|
118
|
+
end
|
119
|
+
Store.zadd("#{@scope}:#{@name}", index, command_data.to_json)
|
120
|
+
notify(kind: 'command')
|
121
|
+
end
|
122
|
+
|
123
|
+
def update_command(index:, command:, username:)
|
124
|
+
if @state == 'DISABLE'
|
125
|
+
raise QueueError, "Queue '#{@name}' is disabled. Command at index #{index} not updated."
|
126
|
+
end
|
127
|
+
|
128
|
+
# Check if command exists at the given index
|
129
|
+
existing = Store.zrangebyscore("#{@scope}:#{@name}", index, index)
|
130
|
+
if existing.empty?
|
131
|
+
raise QueueError, "No command found at index #{index} in queue '#{@name}'"
|
132
|
+
end
|
133
|
+
|
134
|
+
# Remove the existing command and add the new one at the same index
|
135
|
+
Store.zremrangebyscore("#{@scope}:#{@name}", index, index)
|
136
|
+
command_data = { username: username, value: command, timestamp: Time.now.to_nsec_from_epoch }
|
137
|
+
Store.zadd("#{@scope}:#{@name}", index, command_data.to_json)
|
138
|
+
notify(kind: 'command')
|
139
|
+
end
|
140
|
+
|
141
|
+
def remove_command(index = nil)
|
142
|
+
if @state == 'DISABLE'
|
143
|
+
raise QueueError, "Queue '#{@name}' is disabled. Command not removed."
|
144
|
+
end
|
145
|
+
|
146
|
+
if index
|
147
|
+
# Remove specific index
|
148
|
+
result = Store.zrangebyscore("#{@scope}:#{@name}", index, index)
|
149
|
+
if result.empty?
|
150
|
+
return nil
|
151
|
+
else
|
152
|
+
Store.zremrangebyscore("#{@scope}:#{@name}", index, index)
|
153
|
+
command_data = JSON.parse(result[0])
|
154
|
+
command_data['index'] = index.to_f
|
155
|
+
notify(kind: 'command')
|
156
|
+
return command_data
|
157
|
+
end
|
158
|
+
else
|
159
|
+
# Remove first element (lowest score)
|
160
|
+
result = Store.zrange("#{@scope}:#{@name}", 0, 0, with_scores: true)
|
161
|
+
if result.empty?
|
162
|
+
return nil
|
163
|
+
else
|
164
|
+
score = result[0][1]
|
165
|
+
Store.zremrangebyscore("#{@scope}:#{@name}", score, score)
|
166
|
+
command_data = JSON.parse(result[0][0])
|
167
|
+
command_data['index'] = score.to_f
|
168
|
+
notify(kind: 'command')
|
169
|
+
return command_data
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def list
|
175
|
+
return Store.zrange("#{@scope}:#{@name}", 0, -1, with_scores: true).map do |item|
|
176
|
+
result = JSON.parse(item[0])
|
177
|
+
result['index'] = item[1].to_f
|
178
|
+
result
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def create_microservice(topics:)
|
183
|
+
# queue Microservice
|
184
|
+
microservice = MicroserviceModel.new(
|
185
|
+
name: @microservice_name,
|
186
|
+
folder_name: nil,
|
187
|
+
cmd: ['ruby', 'queue_microservice.rb', @microservice_name],
|
188
|
+
work_dir: '/openc3/lib/openc3/microservices',
|
189
|
+
options: [
|
190
|
+
["QUEUE_STATE", @state],
|
191
|
+
],
|
192
|
+
topics: topics,
|
193
|
+
target_names: [],
|
194
|
+
plugin: nil,
|
195
|
+
scope: @scope
|
196
|
+
)
|
197
|
+
microservice.create
|
198
|
+
end
|
199
|
+
|
200
|
+
def deploy
|
201
|
+
topics = ["#{@scope}__#{QueueTopic::PRIMARY_KEY}"]
|
202
|
+
if MicroserviceModel.get_model(name: @microservice_name, scope: @scope).nil?
|
203
|
+
create_microservice(topics: topics)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def undeploy
|
208
|
+
model = MicroserviceModel.get_model(name: @microservice_name, scope: @scope)
|
209
|
+
if model
|
210
|
+
# Let the frontend know that the microservice is shutting down
|
211
|
+
# Custom event which matches the 'deployed' event in QueueMicroservice
|
212
|
+
notification = {
|
213
|
+
'kind' => 'undeployed',
|
214
|
+
# name and updated_at fields are required for Event formatting
|
215
|
+
'data' => JSON.generate({
|
216
|
+
'name' => @microservice_name,
|
217
|
+
'updated_at' => Time.now.to_nsec_from_epoch,
|
218
|
+
}),
|
219
|
+
}
|
220
|
+
QueueTopic.write_notification(notification, scope: @scope)
|
221
|
+
model.destroy
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Delete the model from the Store
|
226
|
+
def destroy
|
227
|
+
undeploy()
|
228
|
+
Store.zremrangebyrank("#{@scope}:#{@name}", 0, -1)
|
229
|
+
super()
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -1088,6 +1088,25 @@ module OpenC3
|
|
1088
1088
|
Logger.info "Configured microservice #{microservice_name}"
|
1089
1089
|
end
|
1090
1090
|
|
1091
|
+
def deploy_tsdb_microservice(gem_path, variables, topics, instance = nil, parent = nil)
|
1092
|
+
microservice_name = "#{@scope}__TSDB#{instance}__#{@name}"
|
1093
|
+
microservice = MicroserviceModel.new(
|
1094
|
+
name: microservice_name,
|
1095
|
+
folder_name: @folder_name,
|
1096
|
+
cmd: ["python", "quest_microservice.py", microservice_name],
|
1097
|
+
work_dir: "/openc3/python/openc3/microservices",
|
1098
|
+
topics: topics,
|
1099
|
+
plugin: @plugin,
|
1100
|
+
parent: nil,
|
1101
|
+
needs_dependencies: @needs_dependencies,
|
1102
|
+
shard: @shard,
|
1103
|
+
scope: @scope
|
1104
|
+
)
|
1105
|
+
microservice.create
|
1106
|
+
microservice.deploy(gem_path, variables)
|
1107
|
+
Logger.info "Configured microservice #{microservice_name}"
|
1108
|
+
end
|
1109
|
+
|
1091
1110
|
def deploy_reducer_microservice(gem_path, variables, topics, instance = nil, parent = nil)
|
1092
1111
|
microservice_name = "#{@scope}__REDUCER#{instance}__#{@name}"
|
1093
1112
|
microservice = MicroserviceModel.new(
|
@@ -1250,6 +1269,13 @@ module OpenC3
|
|
1250
1269
|
deploy_decom_microservice(system.targets[@name], gem_path, variables, topics, instance, parent)
|
1251
1270
|
end
|
1252
1271
|
|
1272
|
+
# TSDB Microservice
|
1273
|
+
if ENV['OPENC3_TSDB_HOSTNAME'] and ENV['OPENC3_TSDB_QUERY_PORT'] and ENV['OPENC3_TSDB_INGEST_PORT'] and ENV['OPENC3_TSDB_USERNAME'] and ENV['OPENC3_TSDB_PASSWORD']
|
1274
|
+
deploy_target_microservices('TSDB', decom_topic_list, "#{@scope}__DECOM__{#{@name}}") do |topics, instance, parent|
|
1275
|
+
deploy_tsdb_microservice(gem_path, variables, topics, instance, parent)
|
1276
|
+
end
|
1277
|
+
end
|
1278
|
+
|
1253
1279
|
# Reducer Microservice
|
1254
1280
|
unless @reducer_disable
|
1255
1281
|
# TODO: Does Reducer even need a topic list?
|
@@ -184,7 +184,7 @@ module OpenC3
|
|
184
184
|
end
|
185
185
|
end
|
186
186
|
|
187
|
-
if @url and !@url.start_with?('/') and
|
187
|
+
if @url and !@url.start_with?('/') and @url !~ URI::regexp
|
188
188
|
raise "URL must be a full URL (http://domain.com/path) or a relative path (/path)"
|
189
189
|
end
|
190
190
|
|
@@ -334,7 +334,7 @@ module OpenC3
|
|
334
334
|
synchronize() do
|
335
335
|
begin
|
336
336
|
internal_buffer_equals(buffer)
|
337
|
-
rescue RuntimeError
|
337
|
+
rescue RuntimeError
|
338
338
|
Logger.instance.error "#{@target_name} #{@packet_name} received with actual packet length of #{buffer.length} but defined length of #{@defined_length}"
|
339
339
|
end
|
340
340
|
@read_conversion_cache.clear if @read_conversion_cache
|
@@ -1315,7 +1315,7 @@ module OpenC3
|
|
1315
1315
|
|
1316
1316
|
begin
|
1317
1317
|
current_value = read(item.name, :RAW)
|
1318
|
-
|
1318
|
+
|
1319
1319
|
case current_value
|
1320
1320
|
when Array
|
1321
1321
|
# For arrays, create a new array of zeros with the same size
|
@@ -1325,7 +1325,7 @@ module OpenC3
|
|
1325
1325
|
when :FLOAT
|
1326
1326
|
obfuscated_value = Array.new(current_value.size, 0.0)
|
1327
1327
|
when :STRING, :BLOCK
|
1328
|
-
obfuscated_value = Array.new(current_value.size) { |i|
|
1328
|
+
obfuscated_value = Array.new(current_value.size) { |i|
|
1329
1329
|
"\x00" * current_value[i].length if current_value[i]
|
1330
1330
|
}
|
1331
1331
|
else
|
@@ -80,7 +80,13 @@ module OpenC3
|
|
80
80
|
if data_type == :STRING || data_type == :BLOCK
|
81
81
|
@parser.parameters[1]
|
82
82
|
else
|
83
|
-
@parser.parameters[1].convert_to_value
|
83
|
+
value = @parser.parameters[1].convert_to_value
|
84
|
+
# Check if the value is a string, which indicates an error in parsing
|
85
|
+
# except for 'ANY' which is a valid state value
|
86
|
+
if value.is_a?(String) and value != "ANY"
|
87
|
+
raise @parser.error("Invalid state value #{value} for data type #{data_type}.", @usage)
|
88
|
+
end
|
89
|
+
return value
|
84
90
|
end
|
85
91
|
end
|
86
92
|
|
@@ -603,7 +603,7 @@ module OpenC3
|
|
603
603
|
if item.variable_bit_size
|
604
604
|
# Bit size is determined by length field
|
605
605
|
length_value = self.read(item.variable_bit_size['length_item_name'], :CONVERTED)
|
606
|
-
if item.data_type == :INT or item.data_type == :UINT and not item.original_array_size
|
606
|
+
if (item.data_type == :INT or item.data_type == :UINT) and not item.original_array_size
|
607
607
|
case length_value
|
608
608
|
when 0
|
609
609
|
return 6
|
@@ -641,7 +641,14 @@ module OpenC3
|
|
641
641
|
# Calculate the actual current size of this variable length item
|
642
642
|
new_bit_size = calculate_total_bit_size(item)
|
643
643
|
|
644
|
-
if item.
|
644
|
+
if item.original_array_size
|
645
|
+
# Array size has changed from original - so we need to adjust everything after this item
|
646
|
+
# This includes items that may have the same bit_offset as the variable length item because it
|
647
|
+
# started out at zero bit_size
|
648
|
+
if item.original_array_size != new_bit_size
|
649
|
+
adjustment += (new_bit_size - item.original_array_size)
|
650
|
+
end
|
651
|
+
elsif item.original_bit_size != new_bit_size
|
645
652
|
# Bit size has changed from original - so we need to adjust everything after this item
|
646
653
|
# This includes items that may have the same bit_offset as the variable length item because it
|
647
654
|
# started out at zero bit_size
|
@@ -25,7 +25,7 @@ module OpenC3
|
|
25
25
|
|
26
26
|
def list_timelines(scope: $openc3_scope)
|
27
27
|
response = $api_server.request('get', "/openc3-api/timeline", scope: scope)
|
28
|
-
return
|
28
|
+
return _cal_handle_response(response, 'Failed to list timelines')
|
29
29
|
end
|
30
30
|
|
31
31
|
def create_timeline(name, color: nil, scope: $openc3_scope)
|
@@ -33,19 +33,19 @@ module OpenC3
|
|
33
33
|
data['name'] = name
|
34
34
|
data['color'] = color if color
|
35
35
|
response = $api_server.request('post', "/openc3-api/timeline", data: data, json: true, scope: scope)
|
36
|
-
return
|
36
|
+
return _cal_handle_response(response, 'Failed to create timeline')
|
37
37
|
end
|
38
38
|
|
39
39
|
def get_timeline(name, scope: $openc3_scope)
|
40
40
|
response = $api_server.request('get', "/openc3-api/timeline/#{name}", scope: scope)
|
41
|
-
return
|
41
|
+
return _cal_handle_response(response, 'Failed to get timeline')
|
42
42
|
end
|
43
43
|
|
44
44
|
def set_timeline_color(name, color, scope: $openc3_scope)
|
45
45
|
post_data = {}
|
46
46
|
post_data['color'] = color
|
47
47
|
response = $api_server.request('post', "/openc3-api/timeline/#{name}/color", data: post_data, json: true, scope: scope)
|
48
|
-
return
|
48
|
+
return _cal_handle_response(response, 'Failed to set timeline color')
|
49
49
|
end
|
50
50
|
|
51
51
|
def delete_timeline(name, force: false, scope: $openc3_scope)
|
@@ -54,7 +54,7 @@ module OpenC3
|
|
54
54
|
url += "?force=true"
|
55
55
|
end
|
56
56
|
response = $api_server.request('delete', url, scope: scope)
|
57
|
-
return
|
57
|
+
return _cal_handle_response(response, 'Failed to delete timeline')
|
58
58
|
end
|
59
59
|
|
60
60
|
def create_timeline_activity(name, kind:, start:, stop:, data: {}, scope: $openc3_scope)
|
@@ -69,12 +69,12 @@ module OpenC3
|
|
69
69
|
post_data['kind'] = kind
|
70
70
|
post_data['data'] = data
|
71
71
|
response = $api_server.request('post', "/openc3-api/timeline/#{name}/activities", data: post_data, json: true, scope: scope)
|
72
|
-
return
|
72
|
+
return _cal_handle_response(response, 'Failed to create timeline activity')
|
73
73
|
end
|
74
74
|
|
75
75
|
def get_timeline_activity(name, start, uuid, scope: $openc3_scope)
|
76
76
|
response = $api_server.request('get', "/openc3-api/timeline/#{name}/activity/#{start}/#{uuid}", scope: scope)
|
77
|
-
return
|
77
|
+
return _cal_handle_response(response, 'Failed to get timeline activity')
|
78
78
|
end
|
79
79
|
|
80
80
|
def get_timeline_activities(name, start: nil, stop: nil, limit: nil, scope: $openc3_scope)
|
@@ -86,16 +86,16 @@ module OpenC3
|
|
86
86
|
url += "?limit=#{limit}"
|
87
87
|
end
|
88
88
|
response = $api_server.request('get', url, scope: scope)
|
89
|
-
return
|
89
|
+
return _cal_handle_response(response, 'Failed to get timeline activities')
|
90
90
|
end
|
91
91
|
|
92
92
|
def delete_timeline_activity(name, start, uuid, scope: $openc3_scope)
|
93
93
|
response = $api_server.request('delete', "/openc3-api/timeline/#{name}/activity/#{start}/#{uuid}", scope: scope)
|
94
|
-
return
|
94
|
+
return _cal_handle_response(response, 'Failed to delete timeline activity')
|
95
95
|
end
|
96
96
|
|
97
97
|
# Helper method to handle the response
|
98
|
-
def
|
98
|
+
def _cal_handle_response(response, error_message)
|
99
99
|
return nil if response.nil?
|
100
100
|
if response.status >= 400
|
101
101
|
result = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
|
@@ -115,14 +115,14 @@ module OpenC3
|
|
115
115
|
end
|
116
116
|
end
|
117
117
|
_log_cmd(command, raw, no_range, no_hazardous)
|
118
|
-
|
118
|
+
end
|
119
119
|
|
120
120
|
# Send the command and log the results
|
121
121
|
# This method signature has to include the keyword params present in cmd_api.rb _cmd_implementation()
|
122
122
|
# except for range_check, hazardous_check, and raw as they are part of the cmd name
|
123
123
|
# manual is always false since this is called from script and that is the default
|
124
124
|
# NOTE: This is a helper method and should not be called directly
|
125
|
-
def _cmd(cmd, cmd_no_hazardous, *args, timeout: nil, log_message: nil, validate: true, scope: $openc3_scope, token: $openc3_token, **kwargs)
|
125
|
+
def _cmd(cmd, cmd_no_hazardous, *args, timeout: nil, log_message: nil, validate: true, queue: nil, scope: $openc3_scope, token: $openc3_token, **kwargs)
|
126
126
|
extract_string_kwargs_to_args(args, kwargs)
|
127
127
|
raw = cmd.include?('raw')
|
128
128
|
no_range = cmd.include?('no_range') || cmd.include?('no_checks')
|
@@ -132,7 +132,7 @@ module OpenC3
|
|
132
132
|
else
|
133
133
|
begin
|
134
134
|
begin
|
135
|
-
command = $api_server.method_missing(cmd, *args, timeout: timeout, log_message: log_message, validate: validate, scope: scope, token: token)
|
135
|
+
command = $api_server.method_missing(cmd, *args, timeout: timeout, log_message: log_message, validate: validate, queue: queue, scope: scope, token: token)
|
136
136
|
if log_message.nil? or log_message
|
137
137
|
_log_cmd(command, raw, no_range, no_hazardous)
|
138
138
|
end
|
@@ -140,7 +140,7 @@ module OpenC3
|
|
140
140
|
# This opens a prompt at which point they can cancel and stop the script
|
141
141
|
# or say Yes and send the command. Thus we don't care about the return value.
|
142
142
|
prompt_for_hazardous(e.target_name, e.cmd_name, e.hazardous_description)
|
143
|
-
command = $api_server.method_missing(cmd_no_hazardous, *args, timeout: timeout, log_message: log_message, validate: validate, scope: scope, token: token)
|
143
|
+
command = $api_server.method_missing(cmd_no_hazardous, *args, timeout: timeout, log_message: log_message, validate: validate, queue: queue, scope: scope, token: token)
|
144
144
|
if log_message.nil? or log_message
|
145
145
|
_log_cmd(command, raw, no_range, no_hazardous)
|
146
146
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
# Copyright 2025 OpenC3, Inc.
|
4
|
+
# All Rights Reserved.
|
5
|
+
#
|
6
|
+
# This program is free software; you can modify and/or redistribute it
|
7
|
+
# under the terms of the GNU Affero General Public License
|
8
|
+
# as published by the Free Software Foundation; version 3 with
|
9
|
+
# attribution addendums as found in the LICENSE.txt
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU Affero General Public License for more details.
|
15
|
+
|
16
|
+
# This file may also be used under the terms of a commercial license
|
17
|
+
# if purchased from OpenC3, Inc.
|
18
|
+
|
19
|
+
require 'openc3/script/extract'
|
20
|
+
|
21
|
+
module OpenC3
|
22
|
+
module Script
|
23
|
+
include Extract
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Helper method that makes the request and parses the response
|
28
|
+
def _make_request(action:, verb:, uri:, scope:, data: nil)
|
29
|
+
response = $api_server.request(verb, uri, data: data, json: data.nil? ? false : true, scope: scope)
|
30
|
+
if response.nil?
|
31
|
+
raise "Failed to #{action}. No response from server."
|
32
|
+
elsif response.status != 200 and response.status != 201
|
33
|
+
result = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
|
34
|
+
raise "Failed to #{action} due to #{result['message']}"
|
35
|
+
end
|
36
|
+
return JSON.parse(response.body, :allow_nan => true, :create_additions => true)
|
37
|
+
end
|
38
|
+
|
39
|
+
def queue_all(scope: $openc3_scope)
|
40
|
+
return _make_request(action: 'index queue', verb: 'get', uri: "/openc3-api/queues", scope: scope)
|
41
|
+
end
|
42
|
+
|
43
|
+
def queue_get(name, scope: $openc3_scope)
|
44
|
+
return _make_request(action: 'get queue', verb: 'get', uri: "/openc3-api/queues/#{name}", scope: scope)
|
45
|
+
end
|
46
|
+
|
47
|
+
def queue_list(name, scope: $openc3_scope)
|
48
|
+
return _make_request(action: 'list queue', verb: 'get', uri: "/openc3-api/queues/#{name}/list", scope: scope)
|
49
|
+
end
|
50
|
+
|
51
|
+
def queue_create(name, state: 'HOLD', scope: $openc3_scope)
|
52
|
+
data = {}
|
53
|
+
data['state'] = state
|
54
|
+
return _make_request(action: 'create queue', verb: 'post', uri: "/openc3-api/queues/#{name}", data: data, scope: scope)
|
55
|
+
end
|
56
|
+
|
57
|
+
def queue_hold(name, scope: $openc3_scope)
|
58
|
+
return _make_request(action: 'hold queue', verb: 'post', uri: "/openc3-api/queues/#{name}/hold", scope: scope)
|
59
|
+
end
|
60
|
+
|
61
|
+
def queue_release(name, scope: $openc3_scope)
|
62
|
+
return _make_request(action: 'release queue', verb: 'post', uri: "/openc3-api/queues/#{name}/release", scope: scope)
|
63
|
+
end
|
64
|
+
|
65
|
+
def queue_disable(name, scope: $openc3_scope)
|
66
|
+
return _make_request(action: 'disable queue', verb: 'post', uri: "/openc3-api/queues/#{name}/disable", scope: scope)
|
67
|
+
end
|
68
|
+
|
69
|
+
def queue_exec(name, index: nil, scope: $openc3_scope)
|
70
|
+
data = {}
|
71
|
+
data['index'] = index if index
|
72
|
+
return _make_request(action: 'exec command', verb: 'post', uri: "/openc3-api/queues/#{name}/exec_command", data: data, scope: scope)
|
73
|
+
end
|
74
|
+
|
75
|
+
def queue_delete(name, scope: $openc3_scope)
|
76
|
+
return _make_request(action: 'delete queue', verb: 'delete', uri: "/openc3-api/queues/#{name}", scope: scope)
|
77
|
+
end
|
78
|
+
alias queue_destroy queue_delete
|
79
|
+
end
|
80
|
+
end
|
data/lib/openc3/script/script.rb
CHANGED
@@ -34,6 +34,7 @@ require 'openc3/script/limits'
|
|
34
34
|
require 'openc3/script/metadata'
|
35
35
|
require 'openc3/script/packages'
|
36
36
|
require 'openc3/script/plugins'
|
37
|
+
require 'openc3/script/queue'
|
37
38
|
require 'openc3/script/screen'
|
38
39
|
require 'openc3/script/script_runner'
|
39
40
|
require 'openc3/script/storage'
|
@@ -71,7 +71,7 @@ module OpenC3
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
-
def script_run(filename, disconnect: false, environment: nil, scope: $openc3_scope)
|
74
|
+
def script_run(filename, disconnect: false, environment: nil, suite_runner: nil, scope: $openc3_scope)
|
75
75
|
if disconnect
|
76
76
|
endpoint = "/script-api/scripts/#{filename}/run/disconnect"
|
77
77
|
else
|
@@ -87,8 +87,13 @@ module OpenC3
|
|
87
87
|
else
|
88
88
|
env_data = []
|
89
89
|
end
|
90
|
+
data = { environment: env_data }
|
91
|
+
if suite_runner
|
92
|
+
# TODO 7.0: Should suiteRunner be snake case?
|
93
|
+
data['suiteRunner'] = suite_runner
|
94
|
+
end
|
90
95
|
# NOTE: json: true causes json_api_object to JSON generate and set the Content-Type to json
|
91
|
-
response = $script_runner_api_server.request('post', endpoint, json: true, data:
|
96
|
+
response = $script_runner_api_server.request('post', endpoint, json: true, data: data, scope: scope)
|
92
97
|
if response.nil? || response.status != 200
|
93
98
|
_script_response_error(response, "Failed to run #{filename}", scope: scope)
|
94
99
|
else
|
data/lib/openc3/script/tables.rb
CHANGED
@@ -24,7 +24,7 @@ module OpenC3
|
|
24
24
|
post_data = {}
|
25
25
|
post_data['definition'] = definition
|
26
26
|
response = $api_server.request('post', '/openc3-api/tables/generate', json: true, data: post_data, scope: scope)
|
27
|
-
return
|
27
|
+
return _tables_handle_response(response, 'Failed to create binary')
|
28
28
|
end
|
29
29
|
|
30
30
|
def table_create_report(filename, definition, table_name: nil, scope: $openc3_scope)
|
@@ -33,11 +33,11 @@ module OpenC3
|
|
33
33
|
post_data['definition'] = definition
|
34
34
|
post_data['table_name'] = table_name if table_name
|
35
35
|
response = $api_server.request('post', '/openc3-api/tables/report', json: true, data: post_data, scope: scope)
|
36
|
-
return
|
36
|
+
return _tables_handle_response(response, 'Failed to create report')
|
37
37
|
end
|
38
38
|
|
39
39
|
# Helper method to handle the response
|
40
|
-
def
|
40
|
+
def _tables_handle_response(response, error_message)
|
41
41
|
return nil if response.nil?
|
42
42
|
if response.status >= 400
|
43
43
|
result = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
|
@@ -306,6 +306,17 @@ module OpenC3
|
|
306
306
|
end
|
307
307
|
end
|
308
308
|
|
309
|
+
# Queue WebSocket
|
310
|
+
class QueueEventsWebSocketApi < CmdTlmWebSocketApi
|
311
|
+
def initialize(history_count: 0, url: nil, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_scope)
|
312
|
+
@identifier = {
|
313
|
+
channel: "QueueEventsChannel",
|
314
|
+
history_count: history_count
|
315
|
+
}
|
316
|
+
super(url: url, write_timeout: write_timeout, read_timeout: read_timeout, connect_timeout: connect_timeout, authentication: authentication, scope: scope)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
309
320
|
# Streaming API WebSocket
|
310
321
|
class StreamingWebSocketApi < CmdTlmWebSocketApi
|
311
322
|
def initialize(url: nil, write_timeout: 10.0, read_timeout: 10.0, connect_timeout: 5.0, authentication: nil, scope: $openc3_scope)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
# Copyright 2025 OpenC3, Inc.
|
4
|
+
# All Rights Reserved.
|
5
|
+
#
|
6
|
+
# This program is free software; you can modify and/or redistribute it
|
7
|
+
# under the terms of the GNU Affero General Public License
|
8
|
+
# as published by the Free Software Foundation; version 3 with
|
9
|
+
# attribution addendums as found in the LICENSE.txt
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU Affero General Public License for more details.
|
15
|
+
|
16
|
+
# This file may also be used under the terms of a commercial license
|
17
|
+
# if purchased from OpenC3, Inc.
|
18
|
+
|
19
|
+
require 'openc3/topics/topic'
|
20
|
+
|
21
|
+
module OpenC3
|
22
|
+
class QueueTopic < Topic
|
23
|
+
PRIMARY_KEY = "openc3_queue"
|
24
|
+
|
25
|
+
def self.write_notification(notification, scope:)
|
26
|
+
Topic.write_topic("#{scope}__#{PRIMARY_KEY}", notification, '*', 1000)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|