openc3 7.1.0 → 7.1.1
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 +3 -0
- data/data/config/interface_modifiers.yaml +3 -1
- data/data/config/microservice.yaml +3 -1
- data/data/config/plugins.yaml +1 -0
- data/lib/openc3/api/api.rb +1 -0
- data/lib/openc3/api/calendar_api.rb +183 -0
- data/lib/openc3/microservices/decom_microservice.rb +4 -2
- data/lib/openc3/models/plugin_model.rb +20 -8
- data/lib/openc3/models/queue_model.rb +36 -46
- data/lib/openc3/models/trigger_model.rb +1 -1
- data/lib/openc3/packets/parsers/xtce_parser.rb +23 -1
- data/lib/openc3/script/script.rb +4 -2
- data/lib/openc3/topics/command_topic.rb +1 -0
- data/lib/openc3/topics/telemetry_decom_topic.rb +1 -0
- data/lib/openc3/utilities/cli_generator.rb +7 -0
- data/lib/openc3/utilities/running_script.rb +8 -10
- data/lib/openc3/version.rb +5 -5
- data/templates/microservice/microservices/TEMPLATE/microservice.py +9 -0
- 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 +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 719b55747a7004cc44da1976c3f15eec6c67a3fca702b56792862f59f02cb8b5
|
|
4
|
+
data.tar.gz: c80082e75c059dac71d15a56264cba9c968f82c494e564e33b598c75ac01a6c2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5b3c1ca82717f1debd25ab4a6ac0239aa9baee2b5f736f61b7f52103397b608ab3de8fd0d50e10f0a4481b507b2a519987a4c4c68f7d0f3770cd48f3e165b8eb
|
|
7
|
+
data.tar.gz: d2bc96742a2555be7c8149dd728155a76ba7c1a16474eb26a36388bdedaacc1fba9c447ab373beebd8fd58ac81248541531de1a163bc156be195c76e34b515f2
|
data/bin/openc3cli
CHANGED
|
@@ -444,6 +444,9 @@ def unload_plugin(plugin_name, scope:)
|
|
|
444
444
|
plugin_model = OpenC3::PluginModel.get_model(name: plugin_name, scope: scope)
|
|
445
445
|
plugin_model.destroy
|
|
446
446
|
OpenC3::LocalMode.remove_local_plugin(plugin_name, scope: scope)
|
|
447
|
+
# Remove the backing gem now that no PluginModel references it,
|
|
448
|
+
# so it disappears from the admin Packages tab.
|
|
449
|
+
OpenC3::PluginModel.cleanup_gem(plugin_name, scope: scope)
|
|
447
450
|
OpenC3::Logger.info("PluginModel destroyed: #{plugin_name}", scope: scope)
|
|
448
451
|
rescue => e
|
|
449
452
|
abort("Error uninstalling plugin: #{scope}: #{plugin_name}: #{e.formatted}")
|
|
@@ -212,7 +212,9 @@ WORK_DIR:
|
|
|
212
212
|
WORK_DIR '/openc3/lib/openc3/microservices'
|
|
213
213
|
PORT:
|
|
214
214
|
summary: Open port for the microservice
|
|
215
|
-
description:
|
|
215
|
+
description:
|
|
216
|
+
Kubernetes needs a Service to be applied to open a port so this is required for Kubernetes support
|
|
217
|
+
See [Exposing Microservices](/docs/guides/exposing-microservices) for more information.
|
|
216
218
|
since: 5.7.0
|
|
217
219
|
parameters:
|
|
218
220
|
- name: Number
|
|
@@ -40,7 +40,9 @@ MICROSERVICE:
|
|
|
40
40
|
WORK_DIR .
|
|
41
41
|
PORT:
|
|
42
42
|
summary: Open port for the microservice
|
|
43
|
-
description:
|
|
43
|
+
description:
|
|
44
|
+
Kubernetes needs a Service to be applied to open a port so this is required for Kubernetes support.
|
|
45
|
+
See [Exposing Microservices](/docs/guides/exposing-microservices) for more information.
|
|
44
46
|
since: 5.0.10
|
|
45
47
|
parameters:
|
|
46
48
|
- name: Number
|
data/data/config/plugins.yaml
CHANGED
data/lib/openc3/api/api.rb
CHANGED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
3
|
+
# Copyright 2026 OpenC3, Inc.
|
|
4
|
+
# All Rights Reserved.
|
|
5
|
+
#
|
|
6
|
+
# This program is distributed in the hope that it will be useful,
|
|
7
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
8
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
9
|
+
# See LICENSE.md for more details.
|
|
10
|
+
#
|
|
11
|
+
# This file may also be used under the terms of a commercial license
|
|
12
|
+
# if purchased from OpenC3, Inc.
|
|
13
|
+
|
|
14
|
+
require 'date'
|
|
15
|
+
require 'openc3/models/timeline_model'
|
|
16
|
+
require 'openc3/models/activity_model'
|
|
17
|
+
require 'openc3/topics/timeline_topic'
|
|
18
|
+
|
|
19
|
+
module OpenC3
|
|
20
|
+
module Api
|
|
21
|
+
# NOTE: These methods are intentionally NOT added to WHITELIST. Their signatures
|
|
22
|
+
# match openc3/lib/openc3/script/calendar.rb (no manual:/token: kwargs), so they
|
|
23
|
+
# cannot be dispatched via JSON-RPC (which auto-injects manual/token from headers).
|
|
24
|
+
# The script-side calendar methods reach the server through the timeline/activity
|
|
25
|
+
# HTTP controllers, which call these helpers after performing their own
|
|
26
|
+
# authorization.
|
|
27
|
+
|
|
28
|
+
# Returns an array of all timelines for the given scope.
|
|
29
|
+
def list_timelines(scope: $openc3_scope)
|
|
30
|
+
ret = []
|
|
31
|
+
TimelineModel.all.each do |timeline, value|
|
|
32
|
+
if scope == timeline.split('__')[0]
|
|
33
|
+
ret << value
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
ret
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Creates a new timeline and deploys its microservice.
|
|
40
|
+
# @return [Hash] the created timeline as a hash
|
|
41
|
+
def create_timeline(name, color: nil, scope: $openc3_scope)
|
|
42
|
+
model = TimelineModel.new(name: name, color: color, scope: scope)
|
|
43
|
+
model.create()
|
|
44
|
+
model.deploy()
|
|
45
|
+
model.as_json()
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Hash, nil] the timeline as a hash, or nil if not found
|
|
49
|
+
def get_timeline(name, scope: $openc3_scope)
|
|
50
|
+
model = TimelineModel.get(name: name, scope: scope)
|
|
51
|
+
return nil if model.nil?
|
|
52
|
+
model.as_json()
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Updates the color of an existing timeline.
|
|
56
|
+
# @return [Hash, nil] the updated timeline as a hash, or nil if not found
|
|
57
|
+
def set_timeline_color(name, color, scope: $openc3_scope)
|
|
58
|
+
model = TimelineModel.get(name: name, scope: scope)
|
|
59
|
+
return nil if model.nil?
|
|
60
|
+
model.color = color
|
|
61
|
+
model.update()
|
|
62
|
+
model.notify(kind: 'updated')
|
|
63
|
+
model.as_json()
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Updates the execute flag of an existing timeline.
|
|
67
|
+
# @return [Hash, nil] the updated timeline as a hash, or nil if not found
|
|
68
|
+
def set_timeline_execute(name, enable, scope: $openc3_scope)
|
|
69
|
+
model = TimelineModel.get(name: name, scope: scope)
|
|
70
|
+
return nil if model.nil?
|
|
71
|
+
model.execute = enable
|
|
72
|
+
model.update()
|
|
73
|
+
model.notify(kind: 'updated')
|
|
74
|
+
model.as_json()
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Deletes a timeline (and optionally all of its activities when force is true).
|
|
78
|
+
# @return [Hash, nil] {'name' => name}, or nil if not found
|
|
79
|
+
def delete_timeline(name, force: false, scope: $openc3_scope)
|
|
80
|
+
model = TimelineModel.get(name: name, scope: scope)
|
|
81
|
+
return nil if model.nil?
|
|
82
|
+
TimelineModel.delete(name: name, scope: scope, force: force)
|
|
83
|
+
model.undeploy()
|
|
84
|
+
model.notify(kind: 'deleted')
|
|
85
|
+
{ 'name' => name }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Creates a new activity on the specified timeline.
|
|
89
|
+
# username is read from data['username'] if present and is used for the audit event.
|
|
90
|
+
# @return [Hash] the created activity as a hash
|
|
91
|
+
def create_timeline_activity(name, kind:, start:, stop:, data: {}, recurring: nil, scope: $openc3_scope)
|
|
92
|
+
data ||= {}
|
|
93
|
+
hash = {
|
|
94
|
+
kind: kind,
|
|
95
|
+
start: _cal_to_epoch(start),
|
|
96
|
+
stop: _cal_to_epoch(stop),
|
|
97
|
+
data: data,
|
|
98
|
+
}
|
|
99
|
+
if recurring
|
|
100
|
+
recurring = recurring.dup
|
|
101
|
+
if recurring['end']
|
|
102
|
+
recurring['end'] = _cal_to_epoch(recurring['end'])
|
|
103
|
+
end
|
|
104
|
+
hash[:recurring] = recurring
|
|
105
|
+
end
|
|
106
|
+
model = ActivityModel.from_json(hash, name: name, scope: scope)
|
|
107
|
+
model.create(username: data['username'])
|
|
108
|
+
model.as_json()
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Updates an existing activity on the specified timeline.
|
|
112
|
+
# @return [Hash, nil] the updated activity as a hash, or nil if not found
|
|
113
|
+
def update_timeline_activity(name, id:, kind:, start:, stop:, uuid:, data: {}, scope: $openc3_scope)
|
|
114
|
+
data ||= {}
|
|
115
|
+
model = ActivityModel.score(name: name, score: id.to_i, uuid: uuid, scope: scope)
|
|
116
|
+
return nil if model.nil?
|
|
117
|
+
model.update(
|
|
118
|
+
start: _cal_to_epoch(start),
|
|
119
|
+
stop: _cal_to_epoch(stop),
|
|
120
|
+
kind: kind,
|
|
121
|
+
data: data,
|
|
122
|
+
username: data['username'],
|
|
123
|
+
)
|
|
124
|
+
model.as_json()
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @return [Hash, nil] the activity as a hash, or nil if not found
|
|
128
|
+
def get_timeline_activity(name, start, uuid, scope: $openc3_scope)
|
|
129
|
+
model = ActivityModel.score(name: name, score: start.to_i, uuid: uuid, scope: scope)
|
|
130
|
+
return nil if model.nil?
|
|
131
|
+
model.as_json()
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Returns activities on the timeline in the given window.
|
|
135
|
+
# When start/stop are nil, defaults to a window of [now - 7 days, now + 7 days].
|
|
136
|
+
# When limit is nil, defaults to one event per minute over the window.
|
|
137
|
+
# @return [Array<Hash>] the matching activities
|
|
138
|
+
def get_timeline_activities(name, start: nil, stop: nil, limit: nil, scope: $openc3_scope)
|
|
139
|
+
now = DateTime.now.new_offset(0)
|
|
140
|
+
start_score = start.nil? ? (now - 7).strftime('%s').to_i : _cal_to_epoch(start)
|
|
141
|
+
stop_score = stop.nil? ? (now + 7).strftime('%s').to_i : _cal_to_epoch(stop)
|
|
142
|
+
limit ||= ((stop_score - start_score) / 60).to_i
|
|
143
|
+
ActivityModel.get(name: name, scope: scope, start: start_score, stop: stop_score, limit: limit)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Removes an activity (or all members of its recurring group when recurring is truthy).
|
|
147
|
+
# @return [Integer] number of activities removed (0 indicates not found)
|
|
148
|
+
def delete_timeline_activity(name, start, uuid, recurring: nil, scope: $openc3_scope)
|
|
149
|
+
ActivityModel.destroy(name: name, scope: scope, score: start.to_i, uuid: uuid, recurring: recurring)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @return [Integer] count of activities on the timeline
|
|
153
|
+
def count_timeline_activities(name, scope: $openc3_scope)
|
|
154
|
+
ActivityModel.count(name: name, scope: scope)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Commits an event to an existing activity.
|
|
158
|
+
# @return [Hash, nil] the activity as a hash, or nil if not found
|
|
159
|
+
def commit_timeline_activity(name, start, uuid, status:, message: nil, scope: $openc3_scope)
|
|
160
|
+
model = ActivityModel.score(name: name, score: start.to_i, uuid: uuid, scope: scope)
|
|
161
|
+
return nil if model.nil?
|
|
162
|
+
model.commit(status: status, message: message)
|
|
163
|
+
model.as_json()
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Convert a value to an epoch integer. Accepts Integer/Numeric (treated as already-epoch),
|
|
167
|
+
# numeric strings, and ISO-style date/time strings or DateTime/Time objects.
|
|
168
|
+
def _cal_to_epoch(value)
|
|
169
|
+
case value
|
|
170
|
+
when Integer
|
|
171
|
+
value
|
|
172
|
+
when Numeric
|
|
173
|
+
value.to_i
|
|
174
|
+
when DateTime, Time, Date
|
|
175
|
+
value.to_datetime.strftime('%s').to_i
|
|
176
|
+
else
|
|
177
|
+
s = value.to_s
|
|
178
|
+
return s.to_i if s.match?(/\A-?\d+\z/)
|
|
179
|
+
DateTime.parse(s).strftime('%s').to_i
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -94,13 +94,15 @@ module OpenC3
|
|
|
94
94
|
@limits_event_topic = "#{@scope}__openc3_limits_events"
|
|
95
95
|
@topics << @limits_event_topic
|
|
96
96
|
Topic.update_topic_offsets(@topics, db_shard: @db_shard)
|
|
97
|
+
# Initialize before assigning limits_change_callback - sync_system below
|
|
98
|
+
# can fire the callback synchronously for any persisted-disabled items.
|
|
99
|
+
@limits_response_queue = Queue.new
|
|
100
|
+
@limits_response_thread = nil
|
|
97
101
|
System.telemetry.limits_change_callback = method(:limits_change_callback)
|
|
98
102
|
LimitsEventTopic.sync_system(scope: @scope)
|
|
99
103
|
@error_count = 0
|
|
100
104
|
@metric.set(name: 'decom_total', value: @count, type: 'counter')
|
|
101
105
|
@metric.set(name: 'decom_error_total', value: @error_count, type: 'counter')
|
|
102
|
-
@limits_response_queue = Queue.new
|
|
103
|
-
@limits_response_thread = nil
|
|
104
106
|
end
|
|
105
107
|
|
|
106
108
|
def run
|
|
@@ -158,12 +158,12 @@ module OpenC3
|
|
|
158
158
|
variables[current_variable_name]['description'] = params[0]
|
|
159
159
|
when 'VARIABLE_STATE'
|
|
160
160
|
usage = "#{keyword} <Display Text> <Value>"
|
|
161
|
-
parser.verify_num_parameters(
|
|
161
|
+
parser.verify_num_parameters(1, 2, usage)
|
|
162
162
|
unless current_variable_name
|
|
163
163
|
raise "VARIABLE_STATE must follow a VARIABLE definition"
|
|
164
164
|
end
|
|
165
165
|
variables[current_variable_name]['options'] ||= []
|
|
166
|
-
option = { 'value' => params[1], 'text' => params[0] }
|
|
166
|
+
option = { 'value' => params[1] || params[0], 'text' => params[0] }
|
|
167
167
|
variables[current_variable_name]['options'] << option
|
|
168
168
|
end
|
|
169
169
|
end
|
|
@@ -229,12 +229,13 @@ module OpenC3
|
|
|
229
229
|
plugin_model.homepage = pkg.spec.homepage
|
|
230
230
|
plugin_model.repository = pkg.spec.metadata['source_code_uri'] # this key because it's in the official gemspec examples
|
|
231
231
|
plugin_model.keywords = pkg.spec.metadata['openc3_store_keywords']&.split(/, ?/)
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
232
|
+
# Resolve the plugin's store image path. The gemspec generated by
|
|
233
|
+
# `cli generate plugin` sets `openc3_store_image` to `public/store_img.png`
|
|
234
|
+
# but does not create that file, so we must verify existence regardless
|
|
235
|
+
# of whether the path came from metadata or the default — otherwise the
|
|
236
|
+
# frontend hits a 500 when fetching the missing file.
|
|
237
|
+
img_path = pkg.spec.metadata['openc3_store_image'] || 'public/store_img.png'
|
|
238
|
+
img_path = nil unless File.exist?(File.join(gem_path, img_path))
|
|
238
239
|
package_name = "#{pkg.spec.name}-#{pkg.spec.version}"
|
|
239
240
|
plugin_model.img_path = File.join('gems', package_name, img_path) if img_path # convert this filesystem path to volumes mount path
|
|
240
241
|
plugin_model.update() unless validate_only
|
|
@@ -525,5 +526,16 @@ module OpenC3
|
|
|
525
526
|
end
|
|
526
527
|
return result.sort
|
|
527
528
|
end
|
|
529
|
+
|
|
530
|
+
# Remove the backing gem for an unloaded plugin so it disappears from
|
|
531
|
+
# GemModel.names (and the admin Packages tab). Skips the removal if any
|
|
532
|
+
# other PluginModel (any scope, any counter) still references the gem,
|
|
533
|
+
# via the existing PluginModel.gem_names check inside GemModel.destroy.
|
|
534
|
+
def self.cleanup_gem(plugin_name, scope:)
|
|
535
|
+
gem_filename = plugin_name.split("__")[0]
|
|
536
|
+
OpenC3::GemModel.destroy(gem_filename, log_and_raise_needed_errors: false)
|
|
537
|
+
rescue => e
|
|
538
|
+
Logger.warn("Could not remove gem #{gem_filename}: #{e.message}", scope: scope)
|
|
539
|
+
end
|
|
528
540
|
end
|
|
529
541
|
end
|
|
@@ -44,34 +44,37 @@ module OpenC3
|
|
|
44
44
|
model = get_model(name: name, scope: scope)
|
|
45
45
|
raise QueueError, "Queue '#{name}' not found in scope '#{scope}'" unless model
|
|
46
46
|
|
|
47
|
-
if model.state
|
|
48
|
-
result = Store.zrevrange("#{scope}:#{name}", 0, 0, with_scores: true)
|
|
49
|
-
if result.empty?
|
|
50
|
-
id = 1.0
|
|
51
|
-
else
|
|
52
|
-
id = result[0][1].to_f + 1
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Build command data with support for both formats
|
|
56
|
-
command_data = { username: username, timestamp: Time.now.to_nsec_from_epoch, validate: validate, timeout: timeout }
|
|
57
|
-
if target_name && cmd_name
|
|
58
|
-
# New format: store target_name, cmd_name, and cmd_params separately
|
|
59
|
-
command_data[:target_name] = target_name
|
|
60
|
-
command_data[:cmd_name] = cmd_name
|
|
61
|
-
command_data[:cmd_params] = JSON.generate(cmd_params.as_json, allow_nan: true) if cmd_params
|
|
62
|
-
elsif command
|
|
63
|
-
# Legacy format: store command string for backwards compatibility
|
|
64
|
-
command_data[:value] = command
|
|
65
|
-
else
|
|
66
|
-
raise QueueError, "Must provide either command string or target_name/cmd_name parameters"
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
Store.zadd("#{scope}:#{name}", id, command_data.to_json)
|
|
70
|
-
model.notify(kind: 'command')
|
|
71
|
-
else
|
|
47
|
+
if model.state == 'DISABLE'
|
|
72
48
|
error_msg = command || "#{target_name} #{cmd_name}"
|
|
73
49
|
raise QueueError, "Queue '#{name}' is disabled. Command '#{error_msg}' not queued."
|
|
74
50
|
end
|
|
51
|
+
|
|
52
|
+
result = Store.zrevrange("#{scope}:#{name}", 0, 0, with_scores: true)
|
|
53
|
+
id = result.empty? ? 1.0 : result[0][1].to_f + 1
|
|
54
|
+
|
|
55
|
+
command_data = build_command_data(username: username, command: command, target_name: target_name,
|
|
56
|
+
cmd_name: cmd_name, cmd_params: cmd_params, validate: validate, timeout: timeout)
|
|
57
|
+
Store.zadd("#{scope}:#{name}", id, command_data.to_json)
|
|
58
|
+
model.notify(kind: 'command')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Build the hash that gets serialized into Redis. Always uses symbol keys so
|
|
62
|
+
# downstream code in this class can access values consistently. cmd_params is
|
|
63
|
+
# JSON-encoded as a string so binary data survives the round-trip via as_json.
|
|
64
|
+
def self.build_command_data(username:, command: nil, target_name: nil, cmd_name: nil, cmd_params: nil, validate: nil, timeout: nil)
|
|
65
|
+
command_data = { username: username, timestamp: Time.now.to_nsec_from_epoch }
|
|
66
|
+
if target_name && cmd_name
|
|
67
|
+
command_data[:target_name] = target_name
|
|
68
|
+
command_data[:cmd_name] = cmd_name
|
|
69
|
+
command_data[:cmd_params] = JSON.generate(cmd_params.as_json, allow_nan: true) if cmd_params
|
|
70
|
+
elsif command
|
|
71
|
+
command_data[:value] = command
|
|
72
|
+
else
|
|
73
|
+
raise QueueError, "Must provide either command string or target_name/cmd_name parameters"
|
|
74
|
+
end
|
|
75
|
+
command_data[:validate] = validate unless validate.nil?
|
|
76
|
+
command_data[:timeout] = timeout unless timeout.nil?
|
|
77
|
+
command_data
|
|
75
78
|
end
|
|
76
79
|
|
|
77
80
|
attr_accessor :name, :state
|
|
@@ -115,49 +118,36 @@ module OpenC3
|
|
|
115
118
|
QueueTopic.write_notification(notification, scope: @scope)
|
|
116
119
|
end
|
|
117
120
|
|
|
118
|
-
def insert_command(id,
|
|
121
|
+
def insert_command(id: nil, username:, command: nil, target_name: nil, cmd_name: nil, cmd_params: nil, validate: nil, timeout: nil)
|
|
119
122
|
if @state == 'DISABLE'
|
|
120
|
-
|
|
121
|
-
command_name = command_data['value']
|
|
122
|
-
else
|
|
123
|
-
command_name = "#{command_data['target_name']} #{command_data['cmd_name']}"
|
|
124
|
-
end
|
|
123
|
+
command_name = command || "#{target_name} #{cmd_name}"
|
|
125
124
|
raise QueueError, "Queue '#{@name}' is disabled. Command '#{command_name}' not queued."
|
|
126
125
|
end
|
|
127
126
|
|
|
128
127
|
unless id
|
|
129
128
|
result = Store.zrevrange("#{@scope}:#{@name}", 0, 0, with_scores: true)
|
|
130
|
-
|
|
131
|
-
id = 1.0
|
|
132
|
-
else
|
|
133
|
-
id = result[0][1].to_f + 1
|
|
134
|
-
end
|
|
129
|
+
id = result.empty? ? 1.0 : result[0][1].to_f + 1
|
|
135
130
|
end
|
|
136
131
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
command_data['cmd_params'] = JSON.generate(command_data['cmd_params'].as_json, allow_nan: true)
|
|
140
|
-
end
|
|
132
|
+
command_data = self.class.build_command_data(username: username, command: command, target_name: target_name,
|
|
133
|
+
cmd_name: cmd_name, cmd_params: cmd_params, validate: validate, timeout: timeout)
|
|
141
134
|
Store.zadd("#{@scope}:#{@name}", id, command_data.to_json)
|
|
142
135
|
notify(kind: 'command')
|
|
143
136
|
end
|
|
144
137
|
|
|
145
|
-
def update_command(id:,
|
|
138
|
+
def update_command(id:, username:, command: nil, target_name: nil, cmd_name: nil, cmd_params: nil, validate: nil, timeout: nil)
|
|
146
139
|
if @state == 'DISABLE'
|
|
147
140
|
raise QueueError, "Queue '#{@name}' is disabled. Command at id #{id} not updated."
|
|
148
141
|
end
|
|
149
142
|
|
|
150
|
-
# Check if command exists at the given id
|
|
151
143
|
existing = Store.zrangebyscore("#{@scope}:#{@name}", id, id)
|
|
152
144
|
if existing.empty?
|
|
153
145
|
raise QueueError, "No command found at id #{id} in queue '#{@name}'"
|
|
154
146
|
end
|
|
155
147
|
|
|
156
|
-
# Remove the existing command and add the new one at the same id
|
|
157
148
|
Store.zremrangebyscore("#{@scope}:#{@name}", id, id)
|
|
158
|
-
command_data =
|
|
159
|
-
|
|
160
|
-
command_data[:timeout] = timeout unless timeout.nil?
|
|
149
|
+
command_data = self.class.build_command_data(username: username, command: command, target_name: target_name,
|
|
150
|
+
cmd_name: cmd_name, cmd_params: cmd_params, validate: validate, timeout: timeout)
|
|
161
151
|
Store.zadd("#{@scope}:#{@name}", id, command_data.to_json)
|
|
162
152
|
notify(kind: 'command')
|
|
163
153
|
end
|
|
@@ -709,6 +709,26 @@ module OpenC3
|
|
|
709
709
|
end
|
|
710
710
|
end
|
|
711
711
|
|
|
712
|
+
def set_item_range_and_default(item)
|
|
713
|
+
return if item.range
|
|
714
|
+
return if item.data_type == :STRING || item.data_type == :BLOCK
|
|
715
|
+
|
|
716
|
+
if item.data_type == :INT
|
|
717
|
+
item.range = (-(2**(item.bit_size - 1)))..((2**(item.bit_size - 1)) - 1)
|
|
718
|
+
item.default = 0 if item.default.nil?
|
|
719
|
+
elsif item.data_type == :UINT
|
|
720
|
+
item.range = 0..((2**item.bit_size) - 1)
|
|
721
|
+
item.default = 0 if item.default.nil?
|
|
722
|
+
elsif item.data_type == :FLOAT
|
|
723
|
+
if item.bit_size == 32
|
|
724
|
+
item.range = -3.402823e38..3.402823e38
|
|
725
|
+
else
|
|
726
|
+
item.range = -Float::MAX..Float::MAX
|
|
727
|
+
end
|
|
728
|
+
item.default = 0.0 if item.default.nil?
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
|
|
712
732
|
def set_limits(item, type)
|
|
713
733
|
return unless @current_cmd_or_tlm == PacketConfig::TELEMETRY
|
|
714
734
|
|
|
@@ -756,7 +776,9 @@ module OpenC3
|
|
|
756
776
|
@current_packet.get_item(item.name)
|
|
757
777
|
rescue
|
|
758
778
|
# Item hasn't already been added so define it
|
|
759
|
-
|
|
779
|
+
cloned_item = item.clone
|
|
780
|
+
@current_packet.define(cloned_item)
|
|
781
|
+
set_item_range_and_default(cloned_item) if @current_cmd_or_tlm == PacketConfig::COMMAND
|
|
760
782
|
count += 1
|
|
761
783
|
end
|
|
762
784
|
end
|
data/lib/openc3/script/script.rb
CHANGED
|
@@ -206,10 +206,12 @@ module OpenC3
|
|
|
206
206
|
_file_dialog(title, message, filter)
|
|
207
207
|
end
|
|
208
208
|
|
|
209
|
-
def open_bucket_dialog(title, message = "Open Bucket File")
|
|
209
|
+
def open_bucket_dialog(title, message = "Open Bucket File", default_path: nil, filter: nil)
|
|
210
210
|
answer = ''
|
|
211
|
+
hint = default_path ? " [default: #{default_path}]" : ''
|
|
212
|
+
hint += " filter: #{filter}" if filter
|
|
211
213
|
while answer.empty?
|
|
212
|
-
print "#{title}\n#{message}\n<Type bucket file path (e.g. BUCKET/path/to/file)
|
|
214
|
+
print "#{title}\n#{message}\n<Type bucket file path (e.g. BUCKET/path/to/file)>#{hint}:"
|
|
213
215
|
answer = gets
|
|
214
216
|
answer.chomp!
|
|
215
217
|
end
|
|
@@ -33,6 +33,7 @@ module OpenC3
|
|
|
33
33
|
received_count: packet.received_count,
|
|
34
34
|
stored: packet.stored.to_s,
|
|
35
35
|
buffer: packet.buffer(false) }
|
|
36
|
+
msg_hash[:extra] = JSON.generate(packet.extra.as_json, allow_nan: true) if packet.extra
|
|
36
37
|
db_shard = Store.db_shard_for_target(packet.target_name, scope: scope)
|
|
37
38
|
EphemeralStoreQueued.instance(db_shard: db_shard).write_topic(topic, msg_hash)
|
|
38
39
|
end
|
|
@@ -43,6 +43,7 @@ module OpenC3
|
|
|
43
43
|
:received_count => packet.received_count,
|
|
44
44
|
:json_data => json_data,
|
|
45
45
|
}
|
|
46
|
+
msg_hash[:extra] = JSON.generate(packet.extra.as_json, allow_nan: true) if packet.extra
|
|
46
47
|
db_shard = Store.db_shard_for_target(packet.target_name, scope: scope)
|
|
47
48
|
Topic.write_topic("#{scope}__DECOM__{#{packet.target_name}}__#{packet.packet_name}", msg_hash, id, db_shard: db_shard)
|
|
48
49
|
|
|
@@ -293,6 +293,13 @@ RUBY
|
|
|
293
293
|
if File.exist?(microservice_path)
|
|
294
294
|
abort("Microservice #{microservice_path} already exists!")
|
|
295
295
|
end
|
|
296
|
+
# plugin.txt is checked separately because the user may have deleted the
|
|
297
|
+
# microservices/NAME directory without cleaning up the plugin.txt entry.
|
|
298
|
+
# A duplicate MICROSERVICE entry causes plugin install to fail with
|
|
299
|
+
# "openc3_microservices:...already exists at create".
|
|
300
|
+
if File.exist?('plugin.txt') && File.read('plugin.txt') =~ /^MICROSERVICE\s+#{Regexp.escape(microservice_name)}\b/
|
|
301
|
+
abort("plugin.txt already declares MICROSERVICE #{microservice_name}. Remove that entry before regenerating.")
|
|
302
|
+
end
|
|
296
303
|
microservice_filename = "#{microservice_name.downcase}.#{@@language}"
|
|
297
304
|
microservice_class = microservice_filename.filename_to_class_name
|
|
298
305
|
microservice_class.inspect # Remove unused variable warning. These are used in binding for generator
|
|
@@ -1280,18 +1280,16 @@ class RunningScript
|
|
|
1280
1280
|
# Start Output Thread
|
|
1281
1281
|
@@output_thread = Thread.new { output_thread() } unless @@output_thread
|
|
1282
1282
|
|
|
1283
|
-
if @
|
|
1284
|
-
if @script_status.
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
start(@script_status.filename, line_no: @script_status.start_line_no, complete: true)
|
|
1288
|
-
else
|
|
1289
|
-
# Execute selection
|
|
1290
|
-
start(@script_status.filename, line_no: @script_status.start_line_no, end_line_no: @script_status.end_line_no, complete: true)
|
|
1291
|
-
end
|
|
1283
|
+
if @script_status.start_line_no != 1 or !@script_status.end_line_no.nil?
|
|
1284
|
+
if @script_status.end_line_no.nil?
|
|
1285
|
+
# Run From Line / Goto line
|
|
1286
|
+
start(@script_status.filename, line_no: @script_status.start_line_no, complete: true)
|
|
1292
1287
|
else
|
|
1293
|
-
|
|
1288
|
+
# Execute Selection
|
|
1289
|
+
start(@script_status.filename, line_no: @script_status.start_line_no, end_line_no: @script_status.end_line_no, complete: true)
|
|
1294
1290
|
end
|
|
1291
|
+
elsif @script_engine
|
|
1292
|
+
@script_engine.run_text(text, filename: @script_status.filename)
|
|
1295
1293
|
else
|
|
1296
1294
|
if initial_filename == 'SCRIPTRUNNER'
|
|
1297
1295
|
# Don't instrument pseudo scripts
|
data/lib/openc3/version.rb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# encoding: ascii-8bit
|
|
2
2
|
|
|
3
|
-
OPENC3_VERSION = '7.1.
|
|
3
|
+
OPENC3_VERSION = '7.1.1'
|
|
4
4
|
module OpenC3
|
|
5
5
|
module Version
|
|
6
6
|
MAJOR = '7'
|
|
7
7
|
MINOR = '1'
|
|
8
|
-
PATCH = '
|
|
8
|
+
PATCH = '1'
|
|
9
9
|
OTHER = ''
|
|
10
|
-
BUILD = '
|
|
10
|
+
BUILD = '1c377442111a2b1801fb5c6b49a27d604e2cce55'
|
|
11
11
|
end
|
|
12
|
-
VERSION = '7.1.
|
|
13
|
-
GEM_VERSION = '7.1.
|
|
12
|
+
VERSION = '7.1.1'
|
|
13
|
+
GEM_VERSION = '7.1.1'
|
|
14
14
|
end
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import time
|
|
2
|
+
|
|
3
|
+
# Add any plugin lib directories to the Python search path so this microservice
|
|
4
|
+
# can import helpers from `/lib` folders inside installed plugins (Ruby gets this
|
|
5
|
+
# for free via gem `$LOAD_PATH`; Python does not).
|
|
6
|
+
import glob
|
|
7
|
+
from openc3.top_level import add_to_search_path
|
|
8
|
+
for path in glob.glob("/gems/gems/**/lib"):
|
|
9
|
+
add_to_search_path(path, True)
|
|
10
|
+
|
|
2
11
|
from openc3.microservices.microservice import Microservice
|
|
3
12
|
from openc3.utilities.sleeper import Sleeper
|
|
4
13
|
from openc3.api import *
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "<%= tool_name %>",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.1",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"ng": "ng",
|
|
6
6
|
"start": "ng serve",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@angular/platform-browser-dynamic": "^18.2.6",
|
|
24
24
|
"@angular/router": "^18.2.6",
|
|
25
25
|
"@astrouxds/astro-web-components": "^7.24.0",
|
|
26
|
-
"@openc3/js-common": "7.1.
|
|
26
|
+
"@openc3/js-common": "7.1.1",
|
|
27
27
|
"rxjs": "~7.8.0",
|
|
28
28
|
"single-spa": "^5.9.5",
|
|
29
29
|
"single-spa-angular": "^9.2.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "<%= tool_name %>",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@astrouxds/astro-web-components": "^7.24.0",
|
|
14
|
-
"@openc3/js-common": "7.1.
|
|
15
|
-
"@openc3/vue-common": "7.1.
|
|
14
|
+
"@openc3/js-common": "7.1.1",
|
|
15
|
+
"@openc3/vue-common": "7.1.1",
|
|
16
16
|
"axios": "^1.7.7",
|
|
17
17
|
"date-fns": "^4.1.0",
|
|
18
18
|
"lodash": "^4.17.21",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "<%= widget_name %>",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@astrouxds/astro-web-components": "^7.24.0",
|
|
11
|
-
"@openc3/vue-common": "7.1.
|
|
11
|
+
"@openc3/vue-common": "7.1.1",
|
|
12
12
|
"vuetify": "^3.7.1"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openc3
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.1.
|
|
4
|
+
version: 7.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ryan Melton
|
|
@@ -973,6 +973,7 @@ files:
|
|
|
973
973
|
- lib/openc3/api/README.md
|
|
974
974
|
- lib/openc3/api/api.rb
|
|
975
975
|
- lib/openc3/api/authorized_api.rb
|
|
976
|
+
- lib/openc3/api/calendar_api.rb
|
|
976
977
|
- lib/openc3/api/cmd_api.rb
|
|
977
978
|
- lib/openc3/api/config_api.rb
|
|
978
979
|
- lib/openc3/api/interface_api.rb
|