openc3 5.8.1 → 5.9.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/ext/openc3/ext/crc/crc.c +1 -1
- data/lib/openc3/api/cmd_api.rb +1 -1
- data/lib/openc3/microservices/decom_microservice.rb +10 -2
- data/lib/openc3/microservices/reaction_microservice.rb +152 -81
- data/lib/openc3/microservices/timeline_microservice.rb +1 -1
- data/lib/openc3/microservices/trigger_group_microservice.rb +188 -118
- data/lib/openc3/migrations/20230615000000_autonomic.rb +86 -0
- data/lib/openc3/models/activity_model.rb +2 -4
- data/lib/openc3/models/microservice_model.rb +6 -2
- data/lib/openc3/models/model.rb +1 -3
- data/lib/openc3/models/reaction_model.rb +124 -119
- data/lib/openc3/models/scope_model.rb +15 -3
- data/lib/openc3/models/timeline_model.rb +1 -3
- data/lib/openc3/models/trigger_group_model.rb +16 -50
- data/lib/openc3/models/trigger_model.rb +86 -123
- data/lib/openc3/packets/json_packet.rb +2 -3
- data/lib/openc3/script/commands.rb +10 -0
- data/lib/openc3/script/script.rb +1 -0
- data/lib/openc3/top_level.rb +0 -12
- data/lib/openc3/utilities/authorization.rb +1 -1
- data/lib/openc3/utilities/bucket_require.rb +5 -1
- data/lib/openc3/utilities/bucket_utilities.rb +4 -1
- data/lib/openc3/utilities/cli_generator.rb +56 -4
- data/lib/openc3/utilities/ruby_lex_utils.rb +4 -0
- data/lib/openc3/version.rb +6 -6
- data/templates/plugin/README.md +54 -4
- data/templates/plugin/Rakefile +31 -3
- data/templates/tool_angular/.editorconfig +16 -0
- data/templates/tool_angular/.gitignore +44 -0
- data/templates/tool_angular/.vscode/extensions.json +4 -0
- data/templates/tool_angular/.vscode/launch.json +20 -0
- data/templates/tool_angular/.vscode/tasks.json +42 -0
- data/templates/tool_angular/angular.json +111 -0
- data/templates/tool_angular/extra-webpack.config.js +8 -0
- data/templates/tool_angular/package.json +47 -0
- data/templates/tool_angular/src/app/app-routing.module.ts +15 -0
- data/templates/tool_angular/src/app/app.component.html +31 -0
- data/templates/tool_angular/src/app/app.component.scss +26 -0
- data/templates/tool_angular/src/app/app.component.spec.ts +29 -0
- data/templates/tool_angular/src/app/app.component.ts +51 -0
- data/templates/tool_angular/src/app/app.module.ts +30 -0
- data/templates/tool_angular/src/app/custom-overlay-container.ts +17 -0
- data/templates/tool_angular/src/app/empty-route/empty-route.component.ts +7 -0
- data/templates/tool_angular/src/app/openc3-api.d.ts +1 -0
- data/templates/tool_angular/src/assets/.gitkeep +0 -0
- data/templates/tool_angular/src/environments/environment.prod.ts +3 -0
- data/templates/tool_angular/src/environments/environment.ts +16 -0
- data/templates/tool_angular/src/favicon.ico +0 -0
- data/templates/tool_angular/src/index.html +13 -0
- data/templates/tool_angular/src/main.single-spa.ts +40 -0
- data/templates/tool_angular/src/single-spa/asset-url.ts +12 -0
- data/templates/tool_angular/src/single-spa/single-spa-props.ts +8 -0
- data/templates/tool_angular/src/styles.scss +1 -0
- data/templates/tool_angular/tsconfig.app.json +13 -0
- data/templates/tool_angular/tsconfig.json +33 -0
- data/templates/tool_angular/tsconfig.spec.json +14 -0
- data/templates/tool_angular/yarn.lock +8080 -0
- data/templates/tool_react/.eslintrc +7 -0
- data/templates/tool_react/.gitignore +72 -0
- data/templates/tool_react/.prettierignore +8 -0
- data/templates/tool_react/babel.config.json +29 -0
- data/templates/tool_react/jest.config.js +12 -0
- data/templates/tool_react/package.json +53 -0
- data/templates/tool_react/src/openc3-tool_name.js +24 -0
- data/templates/tool_react/src/root.component.js +88 -0
- data/templates/tool_react/src/root.component.test.js +9 -0
- data/templates/tool_react/webpack.config.js +27 -0
- data/templates/tool_react/yarn.lock +6854 -0
- data/templates/tool_svelte/.gitignore +72 -0
- data/templates/tool_svelte/.prettierignore +8 -0
- data/templates/tool_svelte/babel.config.js +12 -0
- data/templates/tool_svelte/build/smui.css +5 -0
- data/templates/tool_svelte/jest.config.js +9 -0
- data/templates/tool_svelte/package.json +46 -0
- data/templates/tool_svelte/rollup.config.js +72 -0
- data/templates/tool_svelte/src/App.svelte +42 -0
- data/templates/tool_svelte/src/App.test.js +9 -0
- data/templates/tool_svelte/src/services/api.js +92 -0
- data/templates/tool_svelte/src/services/axios.js +85 -0
- data/templates/tool_svelte/src/services/cable.js +65 -0
- data/templates/tool_svelte/src/services/config-parser.js +199 -0
- data/templates/tool_svelte/src/services/openc3-api.js +647 -0
- data/templates/tool_svelte/src/theme/_smui-theme.scss +25 -0
- data/templates/tool_svelte/src/tool_name.js +17 -0
- data/templates/tool_svelte/yarn.lock +5052 -0
- data/templates/tool_vue/.browserslistrc +16 -0
- data/templates/tool_vue/.env.standalone +1 -0
- data/templates/tool_vue/.eslintrc.js +43 -0
- data/templates/tool_vue/.gitignore +2 -0
- data/templates/tool_vue/.nycrc +3 -0
- data/templates/tool_vue/.prettierrc.js +5 -0
- data/templates/tool_vue/babel.config.json +11 -0
- data/templates/tool_vue/jsconfig.json +6 -0
- data/templates/tool_vue/package.json +52 -0
- data/templates/tool_vue/src/App.vue +15 -0
- data/templates/tool_vue/src/main.js +38 -0
- data/templates/tool_vue/src/router.js +29 -0
- data/templates/tool_vue/src/tools/tool_name/tool_name.vue +63 -0
- data/templates/tool_vue/vue.config.js +30 -0
- data/templates/tool_vue/yarn.lock +9145 -0
- data/templates/widget/package.json +9 -9
- data/templates/widget/yarn.lock +77 -73
- metadata +76 -2
|
@@ -25,40 +25,47 @@ require 'openc3/models/notification_model'
|
|
|
25
25
|
require 'openc3/models/trigger_model'
|
|
26
26
|
require 'openc3/topics/autonomic_topic'
|
|
27
27
|
require 'openc3/utilities/authentication'
|
|
28
|
+
require 'openc3/packets/json_packet'
|
|
28
29
|
|
|
29
30
|
require 'openc3/script'
|
|
30
31
|
|
|
31
32
|
module OpenC3
|
|
32
|
-
|
|
33
33
|
class TriggerLoopError < TriggerError; end
|
|
34
34
|
|
|
35
35
|
# Stored in the TriggerGroupShare this should be a thread safe
|
|
36
36
|
# hash that triggers will be added, updated, and removed from
|
|
37
37
|
class PacketBase
|
|
38
|
-
|
|
39
38
|
def initialize(scope:)
|
|
40
39
|
@scope = scope
|
|
41
40
|
@mutex = Mutex.new
|
|
42
41
|
@packets = Hash.new
|
|
43
42
|
end
|
|
44
43
|
|
|
45
|
-
# ["#{@scope}__DECOM__{#{@target}}__#{@packet}"]
|
|
46
44
|
def packet(target:, packet:)
|
|
47
45
|
topic = "#{@scope}__DECOM__{#{target}}__#{packet}"
|
|
48
46
|
@mutex.synchronize do
|
|
49
|
-
return
|
|
47
|
+
return nil unless @packets[topic]
|
|
48
|
+
# Deep copy the packet so it doesn't change under us
|
|
49
|
+
return Marshal.load( Marshal.dump(@packets[topic][-1]) )
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
def
|
|
53
|
+
def previous_packet(target:, packet:)
|
|
54
|
+
topic = "#{@scope}__DECOM__{#{target}}__#{packet}"
|
|
54
55
|
@mutex.synchronize do
|
|
55
|
-
return
|
|
56
|
+
return nil unless @packets[topic] and @packets[topic].length == 2
|
|
57
|
+
# Deep copy the packet so it doesn't change under us
|
|
58
|
+
return Marshal.load( Marshal.dump(@packets[topic][0]) )
|
|
56
59
|
end
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
def add(topic:, packet:)
|
|
60
63
|
@mutex.synchronize do
|
|
61
|
-
@packets[topic]
|
|
64
|
+
@packets[topic] ||= []
|
|
65
|
+
if @packets[topic].length == 2
|
|
66
|
+
@packets[topic].shift
|
|
67
|
+
end
|
|
68
|
+
@packets[topic].push(packet)
|
|
62
69
|
end
|
|
63
70
|
end
|
|
64
71
|
|
|
@@ -72,8 +79,7 @@ module OpenC3
|
|
|
72
79
|
# Stored in the TriggerGroupShare this should be a thread safe
|
|
73
80
|
# hash that triggers will be added, updated, and removed from.
|
|
74
81
|
class TriggerBase
|
|
75
|
-
|
|
76
|
-
attr_reader :autonomic_topic
|
|
82
|
+
attr_reader :autonomic_topic, :triggers
|
|
77
83
|
|
|
78
84
|
def initialize(scope:)
|
|
79
85
|
@scope = scope
|
|
@@ -84,8 +90,8 @@ module OpenC3
|
|
|
84
90
|
@lookup = Hash.new
|
|
85
91
|
end
|
|
86
92
|
|
|
87
|
-
# Get triggers to evaluate based on the topic.
|
|
88
|
-
# topic is
|
|
93
|
+
# Get triggers to evaluate based on the topic. If the
|
|
94
|
+
# topic is equal to the autonomic topic it will
|
|
89
95
|
# return only triggers with roots
|
|
90
96
|
def get_triggers(topic:)
|
|
91
97
|
if @autonomic_topic == topic
|
|
@@ -104,19 +110,19 @@ module OpenC3
|
|
|
104
110
|
data = @triggers[name]
|
|
105
111
|
return unless data
|
|
106
112
|
trigger = TriggerModel.from_json(data, name: data['name'], scope: data['scope'])
|
|
107
|
-
if value == -1 && trigger.
|
|
108
|
-
trigger.
|
|
113
|
+
if value == -1 && trigger.enabled
|
|
114
|
+
trigger.disable()
|
|
109
115
|
elsif value == 1 && trigger.state == false
|
|
110
|
-
trigger.
|
|
116
|
+
trigger.state = true
|
|
111
117
|
elsif value == 0 && trigger.state == true
|
|
112
|
-
trigger.
|
|
118
|
+
trigger.state = false
|
|
113
119
|
end
|
|
114
120
|
@triggers[name] = trigger.as_json(:allow_nan => true)
|
|
115
121
|
end
|
|
116
122
|
end
|
|
117
123
|
|
|
118
|
-
# returns a Hash of ALL
|
|
119
|
-
def
|
|
124
|
+
# returns a Hash of ALL enabled Trigger objects
|
|
125
|
+
def enabled_triggers
|
|
120
126
|
val = nil
|
|
121
127
|
@triggers_mutex.synchronize do
|
|
122
128
|
val = Marshal.load( Marshal.dump(@triggers) )
|
|
@@ -124,12 +130,12 @@ module OpenC3
|
|
|
124
130
|
ret = Hash.new
|
|
125
131
|
val.each do | name, data |
|
|
126
132
|
trigger = TriggerModel.from_json(data, name: data['name'], scope: data['scope'])
|
|
127
|
-
ret[name] = trigger if trigger.
|
|
133
|
+
ret[name] = trigger if trigger.enabled
|
|
128
134
|
end
|
|
129
135
|
return ret
|
|
130
136
|
end
|
|
131
137
|
|
|
132
|
-
# returns an Array of
|
|
138
|
+
# returns an Array of enabled Trigger objects that have roots to other triggers
|
|
133
139
|
def triggers_with_roots
|
|
134
140
|
val = nil
|
|
135
141
|
@triggers_mutex.synchronize do
|
|
@@ -138,12 +144,12 @@ module OpenC3
|
|
|
138
144
|
ret = []
|
|
139
145
|
val.each do | _name, data |
|
|
140
146
|
trigger = TriggerModel.from_json(data, name: data['name'], scope: data['scope'])
|
|
141
|
-
ret << trigger if trigger.
|
|
147
|
+
ret << trigger if trigger.enabled && ! trigger.roots.empty?
|
|
142
148
|
end
|
|
143
149
|
return ret
|
|
144
150
|
end
|
|
145
151
|
|
|
146
|
-
# returns an Array of
|
|
152
|
+
# returns an Array of enabled Trigger objects that use a topic
|
|
147
153
|
def triggers_from(topic:)
|
|
148
154
|
val = nil
|
|
149
155
|
@lookup_mutex.synchronize do
|
|
@@ -152,10 +158,10 @@ module OpenC3
|
|
|
152
158
|
return [] if val.nil?
|
|
153
159
|
ret = []
|
|
154
160
|
@triggers_mutex.synchronize do
|
|
155
|
-
val.each do | trigger_name
|
|
161
|
+
val.each do | trigger_name |
|
|
156
162
|
data = Marshal.load( Marshal.dump(@triggers[trigger_name]) )
|
|
157
163
|
trigger = TriggerModel.from_json(data, name: data['name'], scope: data['scope'])
|
|
158
|
-
ret << trigger if trigger.
|
|
164
|
+
ret << trigger if trigger.enabled
|
|
159
165
|
end
|
|
160
166
|
end
|
|
161
167
|
return ret
|
|
@@ -168,53 +174,60 @@ module OpenC3
|
|
|
168
174
|
end
|
|
169
175
|
end
|
|
170
176
|
|
|
171
|
-
# database
|
|
172
|
-
def
|
|
177
|
+
# Rebuild the database lookup of all triggers in the group
|
|
178
|
+
def rebuild(triggers:)
|
|
173
179
|
@triggers_mutex.synchronize do
|
|
174
180
|
@triggers = Marshal.load( Marshal.dump(triggers) )
|
|
175
181
|
end
|
|
176
182
|
@lookup_mutex.synchronize do
|
|
177
|
-
@lookup = {@autonomic_topic =>
|
|
183
|
+
@lookup = { @autonomic_topic => [] }
|
|
178
184
|
triggers.each do | _name, data |
|
|
179
185
|
trigger = TriggerModel.from_json(data, name: data['name'], scope: data['scope'])
|
|
180
186
|
trigger.generate_topics.each do | topic |
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
else
|
|
184
|
-
@lookup[topic][trigger.name] = 1
|
|
185
|
-
end
|
|
187
|
+
@lookup[topic] ||= []
|
|
188
|
+
@lookup[topic] << trigger.name
|
|
186
189
|
end
|
|
187
190
|
end
|
|
188
191
|
end
|
|
189
192
|
end
|
|
190
193
|
|
|
191
|
-
#
|
|
194
|
+
# Add a trigger from TriggerBase, must only be called once per trigger
|
|
192
195
|
def add(trigger:)
|
|
193
196
|
@triggers_mutex.synchronize do
|
|
194
197
|
@triggers[trigger['name']] = Marshal.load( Marshal.dump(trigger) )
|
|
195
198
|
end
|
|
196
|
-
|
|
199
|
+
trigger = TriggerModel.from_json(trigger, name: trigger['name'], scope: trigger['scope'])
|
|
197
200
|
@lookup_mutex.synchronize do
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
else
|
|
202
|
-
@lookup[topic][t.name] = 1
|
|
203
|
-
end
|
|
201
|
+
trigger.generate_topics.each do | topic |
|
|
202
|
+
@lookup[topic] ||= []
|
|
203
|
+
@lookup[topic] << trigger.name
|
|
204
204
|
end
|
|
205
205
|
end
|
|
206
206
|
end
|
|
207
207
|
|
|
208
|
+
# update a trigger from TriggerBase
|
|
209
|
+
def update(trigger:)
|
|
210
|
+
@triggers_mutex.synchronize do
|
|
211
|
+
model = TriggerModel.from_json(trigger, name: trigger['name'], scope: trigger['scope'])
|
|
212
|
+
model.update()
|
|
213
|
+
@triggers[trigger['name']] = model.as_json(:allow_nan => true)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
208
217
|
# remove a trigger from TriggerBase
|
|
209
218
|
def remove(trigger:)
|
|
219
|
+
topics = []
|
|
210
220
|
@triggers_mutex.synchronize do
|
|
211
221
|
@triggers.delete(trigger['name'])
|
|
222
|
+
model = TriggerModel.from_json(trigger, name: trigger['name'], scope: trigger['scope'])
|
|
223
|
+
topics = model.generate_topics()
|
|
224
|
+
TriggerModel.delete(name: trigger['name'], group: trigger['group'], scope: trigger['scope'])
|
|
212
225
|
end
|
|
213
|
-
t = TriggerModel.from_json(trigger, name: trigger['name'], scope: trigger['scope'])
|
|
214
226
|
@lookup_mutex.synchronize do
|
|
215
|
-
|
|
227
|
+
topics.each do | topic |
|
|
216
228
|
unless @lookup[topic].nil?
|
|
217
|
-
@lookup[topic].delete(
|
|
229
|
+
@lookup[topic].delete(trigger['name'])
|
|
230
|
+
@lookup.delete(topic) if @lookup[topic].empty?
|
|
218
231
|
end
|
|
219
232
|
end
|
|
220
233
|
end
|
|
@@ -223,13 +236,8 @@ module OpenC3
|
|
|
223
236
|
|
|
224
237
|
# Shared between the monitor thread and the manager thread to
|
|
225
238
|
# share the triggers. This should remain a thread
|
|
226
|
-
# safe
|
|
239
|
+
# safe implementation.
|
|
227
240
|
class TriggerGroupShare
|
|
228
|
-
|
|
229
|
-
def self.get_group(name:)
|
|
230
|
-
return name.split('__')[2]
|
|
231
|
-
end
|
|
232
|
-
|
|
233
241
|
attr_reader :trigger_base, :packet_base
|
|
234
242
|
|
|
235
243
|
def initialize(scope:)
|
|
@@ -241,16 +249,16 @@ module OpenC3
|
|
|
241
249
|
|
|
242
250
|
# The TriggerGroupWorker is a very simple thread pool worker. Once
|
|
243
251
|
# the trigger manager has pushed a packet to the queue one of
|
|
244
|
-
# these workers will evaluate the triggers
|
|
245
|
-
# evaluate triggers for that packet.
|
|
252
|
+
# these workers will evaluate the triggers for that packet.
|
|
246
253
|
class TriggerGroupWorker
|
|
247
254
|
TYPE = 'type'.freeze
|
|
248
|
-
ITEM_RAW = 'raw'.freeze
|
|
249
255
|
ITEM_TARGET = 'target'.freeze
|
|
250
256
|
ITEM_PACKET = 'packet'.freeze
|
|
251
257
|
ITEM_TYPE = 'item'.freeze
|
|
258
|
+
ITEM_VALUE_TYPE = 'valueType'.freeze
|
|
252
259
|
FLOAT_TYPE = 'float'.freeze
|
|
253
260
|
STRING_TYPE = 'string'.freeze
|
|
261
|
+
REGEX_TYPE = 'regex'.freeze
|
|
254
262
|
LIMIT_TYPE = 'limit'.freeze
|
|
255
263
|
TRIGGER_TYPE = 'trigger'.freeze
|
|
256
264
|
|
|
@@ -266,13 +274,29 @@ module OpenC3
|
|
|
266
274
|
@ident = ident
|
|
267
275
|
end
|
|
268
276
|
|
|
277
|
+
def notify(name:, severity:, message:)
|
|
278
|
+
data = {}
|
|
279
|
+
# All AutonomicTopic notifications must have 'name' and 'updated_at' in the data
|
|
280
|
+
data['name'] = name
|
|
281
|
+
data['updated_at'] = Time.now.to_nsec_from_epoch
|
|
282
|
+
data['severity'] = severity
|
|
283
|
+
data['message'] = message
|
|
284
|
+
notification = {
|
|
285
|
+
'kind' => 'error',
|
|
286
|
+
'type' => 'trigger',
|
|
287
|
+
'data' => JSON.generate(data),
|
|
288
|
+
}
|
|
289
|
+
AutonomicTopic.write_notification(notification, scope: @scope)
|
|
290
|
+
@logger.public_send(severity.intern, message)
|
|
291
|
+
end
|
|
292
|
+
|
|
269
293
|
def run
|
|
270
294
|
@logger.info "TriggerGroupWorker-#{@ident} running"
|
|
271
295
|
loop do
|
|
272
296
|
topic = @queue.pop
|
|
273
297
|
break if topic.nil?
|
|
274
298
|
begin
|
|
275
|
-
|
|
299
|
+
evaluate_data_packet(topic: topic)
|
|
276
300
|
rescue StandardError => e
|
|
277
301
|
@logger.error "TriggerGroupWorker-#{@ident} failed to evaluate data packet from topic: #{topic}\n#{e.formatted}"
|
|
278
302
|
end
|
|
@@ -280,24 +304,18 @@ module OpenC3
|
|
|
280
304
|
@logger.info "TriggerGroupWorker-#{@ident} exiting"
|
|
281
305
|
end
|
|
282
306
|
|
|
283
|
-
def evaluate_wrapper(topic:)
|
|
284
|
-
evaluate_data_packet(topic: topic, triggers: @share.trigger_base.triggers)
|
|
285
|
-
end
|
|
286
|
-
|
|
287
307
|
# Each packet will be evaluated to all triggers and use the result to send
|
|
288
308
|
# the results back to the topic to be used by the reaction microservice.
|
|
289
|
-
def evaluate_data_packet(topic
|
|
309
|
+
def evaluate_data_packet(topic:)
|
|
290
310
|
visited = Hash.new
|
|
291
311
|
@logger.debug "TriggerGroupWorker-#{@ident} topic: #{topic}"
|
|
292
|
-
|
|
293
|
-
@logger.debug "TriggerGroupWorker-#{@ident} triggers_to_eval: #{triggers_to_eval}"
|
|
294
|
-
triggers_to_eval.each do | trigger |
|
|
312
|
+
@share.trigger_base.get_triggers(topic: topic).each do |trigger|
|
|
295
313
|
@logger.debug "TriggerGroupWorker-#{@ident} eval head: #{trigger}"
|
|
296
314
|
value = evaluate_trigger(
|
|
297
315
|
head: trigger,
|
|
298
316
|
trigger: trigger,
|
|
299
317
|
visited: visited,
|
|
300
|
-
triggers:
|
|
318
|
+
triggers: @share.trigger_base.enabled_triggers
|
|
301
319
|
)
|
|
302
320
|
@logger.debug "TriggerGroupWorker-#{@ident} trigger: #{trigger} value: #{value}"
|
|
303
321
|
# value MUST be -1, 0, or 1
|
|
@@ -315,36 +333,54 @@ module OpenC3
|
|
|
315
333
|
packet: operand[ITEM_PACKET]
|
|
316
334
|
)
|
|
317
335
|
return nil if packet.nil?
|
|
318
|
-
limit = packet
|
|
319
|
-
if limit.nil? == false && limit.include?('_')
|
|
320
|
-
return other[LIMIT_TYPE] if limit.include?(other[LIMIT_TYPE])
|
|
321
|
-
end
|
|
336
|
+
_, limit = packet.read_with_limits_state(operand[ITEM_TYPE], operand[ITEM_VALUE_TYPE].intern)
|
|
322
337
|
return limit
|
|
323
338
|
end
|
|
324
339
|
|
|
325
340
|
# extract the value outlined in the operand to get the packet item value
|
|
326
341
|
# IF raw in operand it will pull the raw value over the converted
|
|
327
|
-
def get_packet_value(operand:)
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
342
|
+
def get_packet_value(operand:, previous:)
|
|
343
|
+
if previous
|
|
344
|
+
packet = @share.packet_base.previous_packet(
|
|
345
|
+
target: operand[ITEM_TARGET],
|
|
346
|
+
packet: operand[ITEM_PACKET]
|
|
347
|
+
)
|
|
348
|
+
# Previous might not be populated ... that's ok just return nil
|
|
349
|
+
return nil unless packet
|
|
350
|
+
else
|
|
351
|
+
packet = @share.packet_base.packet(
|
|
352
|
+
target: operand[ITEM_TARGET],
|
|
353
|
+
packet: operand[ITEM_PACKET]
|
|
354
|
+
)
|
|
355
|
+
end
|
|
356
|
+
# This shouldn't happen because the frontend provides valid items but good to check
|
|
357
|
+
# The raise is ultimately rescued inside evaluate_trigger when operand_value is called
|
|
358
|
+
raise "Packet #{operand[ITEM_TARGET]} #{operand[ITEM_PACKET]} not found" if packet.nil?
|
|
359
|
+
value = packet.read(operand[ITEM_TYPE], operand[ITEM_VALUE_TYPE].intern)
|
|
360
|
+
raise "Item #{operand[ITEM_TARGET]} #{operand[ITEM_PACKET]} #{operand[ITEM_TYPE]} not found" if value.nil?
|
|
361
|
+
value
|
|
336
362
|
end
|
|
337
363
|
|
|
338
364
|
# extract the value of the operand from the packet
|
|
339
|
-
def operand_value(operand:, other:, visited:)
|
|
340
|
-
if operand[TYPE] == ITEM_TYPE && other[TYPE] == LIMIT_TYPE
|
|
365
|
+
def operand_value(operand:, other:, visited:, previous: false)
|
|
366
|
+
if operand[TYPE] == ITEM_TYPE && other && other[TYPE] == LIMIT_TYPE
|
|
341
367
|
return get_packet_limit(operand: operand, other: other)
|
|
342
368
|
elsif operand[TYPE] == ITEM_TYPE
|
|
343
|
-
return get_packet_value(operand: operand)
|
|
369
|
+
return get_packet_value(operand: operand, previous: previous)
|
|
344
370
|
elsif operand[TYPE] == TRIGGER_TYPE
|
|
345
371
|
return visited["#{operand[TRIGGER_TYPE]}__R"] == 1
|
|
346
|
-
|
|
372
|
+
elsif operand[TYPE] == FLOAT_TYPE
|
|
373
|
+
return operand[operand[TYPE]].to_f
|
|
374
|
+
elsif operand[TYPE] == STRING_TYPE
|
|
375
|
+
return operand[operand[TYPE]].to_s
|
|
376
|
+
elsif operand[TYPE] == REGEX_TYPE
|
|
377
|
+
# This can potentially throw an exception on badly formatted Regexp
|
|
378
|
+
return Regexp.new(operand[operand[TYPE]])
|
|
379
|
+
elsif operand[TYPE] == LIMIT_TYPE
|
|
347
380
|
return operand[operand[TYPE]]
|
|
381
|
+
else
|
|
382
|
+
# This is a logic error ... should never get here
|
|
383
|
+
raise "Unknown operand type: #{operand}"
|
|
348
384
|
end
|
|
349
385
|
end
|
|
350
386
|
|
|
@@ -353,8 +389,8 @@ module OpenC3
|
|
|
353
389
|
# 0 (the value is considered as a false value)
|
|
354
390
|
# 1 (the value is considered as a true value)
|
|
355
391
|
#
|
|
356
|
-
def evaluate(left:, operator:, right:)
|
|
357
|
-
@logger.debug "TriggerGroupWorker-#{@ident} evaluate: (#{left} #{operator} #{right})"
|
|
392
|
+
def evaluate(name:, left:, operator:, right:)
|
|
393
|
+
@logger.debug "TriggerGroupWorker-#{@ident} evaluate: (#{left}(#{left.class}) #{operator} #{right}(#{right.class}))"
|
|
358
394
|
begin
|
|
359
395
|
case operator
|
|
360
396
|
when '>'
|
|
@@ -365,17 +401,22 @@ module OpenC3
|
|
|
365
401
|
return left >= right ? 1 : 0
|
|
366
402
|
when '<='
|
|
367
403
|
return left <= right ? 1 : 0
|
|
368
|
-
when '!='
|
|
404
|
+
when '!=', 'CHANGES'
|
|
369
405
|
return left != right ? 1 : 0
|
|
370
|
-
when '=='
|
|
406
|
+
when '==', 'DOES NOT CHANGE'
|
|
371
407
|
return left == right ? 1 : 0
|
|
408
|
+
when '!~'
|
|
409
|
+
return left !~ right ? 1 : 0
|
|
410
|
+
when '=~'
|
|
411
|
+
return left =~ right ? 1 : 0
|
|
372
412
|
when 'AND'
|
|
373
413
|
return left && right ? 1 : 0
|
|
374
414
|
when 'OR'
|
|
375
415
|
return left || right ? 1 : 0
|
|
376
416
|
end
|
|
377
|
-
rescue ArgumentError
|
|
378
|
-
|
|
417
|
+
rescue ArgumentError => error
|
|
418
|
+
message = "invalid evaluate: (#{left} #{operator} #{right})"
|
|
419
|
+
notify(name: name, severity: 'error', message: message)
|
|
379
420
|
return -1
|
|
380
421
|
end
|
|
381
422
|
end
|
|
@@ -384,7 +425,7 @@ module OpenC3
|
|
|
384
425
|
# TriggerGroupWorkers to call. It will use the trigger name and append a
|
|
385
426
|
# __P for path or __R for result. The Path is a Hash that contains
|
|
386
427
|
# a key for each node traveled to get results. When the result has
|
|
387
|
-
# been found it will be stored in the result key __R in the
|
|
428
|
+
# been found it will be stored in the result key __R in the visited Hash
|
|
388
429
|
# and eval_trigger will return a number.
|
|
389
430
|
# -1 (the value is considered an error used to disable the trigger)
|
|
390
431
|
# 0 (the value is considered as a false value)
|
|
@@ -401,14 +442,16 @@ module OpenC3
|
|
|
401
442
|
end
|
|
402
443
|
if visited["#{head.name}__P"][trigger.name]
|
|
403
444
|
# Not sure if this is posible as on create it validates that the dependents are already created
|
|
404
|
-
|
|
445
|
+
message = "loop detected from #{head.name} -> #{trigger.name} path: #{visited["#{head.name}__P"]}"
|
|
446
|
+
notify(name: trigger.name, severity: 'error', message: error.message)
|
|
405
447
|
return visited["#{trigger.name}__R"] = -1
|
|
406
448
|
end
|
|
407
449
|
trigger.roots.each do | root_trigger_name |
|
|
408
450
|
next if visited["#{root_trigger_name}__R"]
|
|
409
451
|
root_trigger = triggers[root_trigger_name]
|
|
410
452
|
if head.name == root_trigger.name
|
|
411
|
-
|
|
453
|
+
message = "loop detected from #{head.name} -> #{root_trigger_name} path: #{visited["#{head.name}__P"]}"
|
|
454
|
+
notify(name: trigger.name, severity: 'error', message: error.message)
|
|
412
455
|
return visited["#{trigger.name}__R"] = -1
|
|
413
456
|
end
|
|
414
457
|
result = evaluate_trigger(
|
|
@@ -420,23 +463,36 @@ module OpenC3
|
|
|
420
463
|
@logger.debug "TriggerGroupWorker-#{@ident} #{root_trigger.name} result: #{result}"
|
|
421
464
|
visited["#{root_trigger.name}__R"] = visited["#{head.name}__P"][root_trigger.name] = result
|
|
422
465
|
end
|
|
423
|
-
|
|
424
|
-
|
|
466
|
+
begin
|
|
467
|
+
left = operand_value(operand: trigger.left, other: trigger.right, visited: visited)
|
|
468
|
+
if trigger.operator.include?('CHANGE')
|
|
469
|
+
right = operand_value(operand: trigger.left, other: trigger.right, visited: visited, previous: true)
|
|
470
|
+
else
|
|
471
|
+
right = operand_value(operand: trigger.right, other: trigger.left, visited: visited)
|
|
472
|
+
end
|
|
473
|
+
rescue => error
|
|
474
|
+
# This will primarily happen when the user inputs a bad Regexp
|
|
475
|
+
notify(name: trigger.name, severity: 'error', message: error.message)
|
|
476
|
+
return visited["#{trigger.name}__R"] = -1
|
|
477
|
+
end
|
|
478
|
+
# Convert the standard '==' and '!=' into Ruby Regexp operators
|
|
479
|
+
operator = trigger.operator
|
|
480
|
+
if right and right.is_a? Regexp
|
|
481
|
+
operator = '=~' if operator == '=='
|
|
482
|
+
operator = '!~' if operator == '!='
|
|
483
|
+
end
|
|
425
484
|
if left.nil? || right.nil?
|
|
426
485
|
return visited["#{trigger.name}__R"] = 0
|
|
427
486
|
end
|
|
428
|
-
result = evaluate(left: left, operator:
|
|
487
|
+
result = evaluate(name: trigger.name,left: left, operator: operator, right: right)
|
|
429
488
|
return visited["#{trigger.name}__R"] = result
|
|
430
489
|
end
|
|
431
|
-
|
|
432
490
|
end
|
|
433
491
|
|
|
434
492
|
# The trigger manager starts a thread pool and subscribes
|
|
435
|
-
# to the telemtry decom topic
|
|
436
|
-
# TriggerGroupManager adds the "packet" to the thread pool queue
|
|
493
|
+
# to the telemtry decom topic. It adds the "packet" to the thread pool queue
|
|
437
494
|
# and the thread will evaluate the "trigger".
|
|
438
495
|
class TriggerGroupManager
|
|
439
|
-
|
|
440
496
|
attr_reader :name, :scope, :share, :group, :topics, :thread_pool
|
|
441
497
|
|
|
442
498
|
def initialize(name:, logger:, scope:, group:, share:)
|
|
@@ -480,7 +536,6 @@ module OpenC3
|
|
|
480
536
|
@logger.error "TriggerGroupManager failed to update topics.\n#{e.formatted}"
|
|
481
537
|
end
|
|
482
538
|
break if @cancel_thread
|
|
483
|
-
|
|
484
539
|
block_for_updates()
|
|
485
540
|
break if @cancel_thread
|
|
486
541
|
end
|
|
@@ -503,7 +558,7 @@ module OpenC3
|
|
|
503
558
|
Topic.read_topics(@topics) do |topic, _msg_id, msg_hash, _redis|
|
|
504
559
|
@logger.debug "TriggerGroupManager block_for_updates: #{topic} #{msg_hash.to_s}"
|
|
505
560
|
if topic != @share.trigger_base.autonomic_topic
|
|
506
|
-
packet =
|
|
561
|
+
packet = JsonPacket.new(:TLM, msg_hash['target_name'], msg_hash['packet_name'], msg_hash['time'].to_i, false, msg_hash["json_data"])
|
|
507
562
|
@share.packet_base.add(topic: topic, packet: packet)
|
|
508
563
|
end
|
|
509
564
|
@queue << "#{topic}"
|
|
@@ -533,10 +588,23 @@ module OpenC3
|
|
|
533
588
|
# stream this will trigger an update again to the schedule.
|
|
534
589
|
class TriggerGroupMicroservice < Microservice
|
|
535
590
|
attr_reader :name, :scope, :share, :group, :manager, :manager_thread
|
|
591
|
+
# This lookup is mapping all the different trigger notifications
|
|
592
|
+
# which are primarily sent by notify in TriggerModel
|
|
593
|
+
TOPIC_LOOKUP = {
|
|
594
|
+
'error' => :no_op, # Sent by TriggerGroupWorker
|
|
595
|
+
'created' => :created_trigger_event,
|
|
596
|
+
'updated' => :rebuild_trigger_event,
|
|
597
|
+
'deleted' => :deleted_trigger_event,
|
|
598
|
+
'enabled' => :updated_trigger_event,
|
|
599
|
+
'disabled' => :updated_trigger_event,
|
|
600
|
+
'true' => :no_op, # Sent by TriggerGroupWorker
|
|
601
|
+
'false' => :no_op, # Sent by TriggerGroupWorker
|
|
602
|
+
}
|
|
536
603
|
|
|
537
604
|
def initialize(*args)
|
|
538
605
|
super(*args)
|
|
539
|
-
|
|
606
|
+
# The name is passed in via the trigger_group_model as "#{scope}__TRIGGER_GROUP__#{name}"
|
|
607
|
+
@group = @name.split('__')[2]
|
|
540
608
|
@share = TriggerGroupShare.new(scope: @scope)
|
|
541
609
|
@manager = TriggerGroupManager.new(name: @name, logger: @logger, scope: @scope, group: @group, share: @share)
|
|
542
610
|
@manager_thread = nil
|
|
@@ -548,36 +616,26 @@ module OpenC3
|
|
|
548
616
|
@manager_thread = Thread.new { @manager.run }
|
|
549
617
|
loop do
|
|
550
618
|
triggers = TriggerModel.all(scope: @scope, group: @group)
|
|
551
|
-
@share.trigger_base.
|
|
619
|
+
@share.trigger_base.rebuild(triggers: triggers)
|
|
620
|
+
@manager.refresh() # Everytime we do a full base update we refesh the manager
|
|
552
621
|
break if @cancel_thread
|
|
553
|
-
|
|
554
622
|
block_for_updates()
|
|
555
623
|
break if @cancel_thread
|
|
556
624
|
end
|
|
557
625
|
@logger.info "TriggerGroupMicroservice exiting"
|
|
558
626
|
end
|
|
559
627
|
|
|
560
|
-
def topic_lookup_functions
|
|
561
|
-
return {
|
|
562
|
-
'created' => :created_trigger_event,
|
|
563
|
-
'updated' => :created_trigger_event,
|
|
564
|
-
'deleted' => :deleted_trigger_event,
|
|
565
|
-
'enabled' => :created_trigger_event,
|
|
566
|
-
'disabled' => :created_trigger_event,
|
|
567
|
-
'activated' => :created_trigger_event,
|
|
568
|
-
'deactivated' => :created_trigger_event,
|
|
569
|
-
}
|
|
570
|
-
end
|
|
571
|
-
|
|
572
628
|
def block_for_updates
|
|
573
629
|
@read_topic = true
|
|
574
|
-
while @read_topic
|
|
630
|
+
while @read_topic && !@cancel_thread
|
|
575
631
|
begin
|
|
576
632
|
AutonomicTopic.read_topics(@topics) do |_topic, _msg_id, msg_hash, _redis|
|
|
633
|
+
break if @cancel_thread
|
|
577
634
|
@logger.debug "TriggerGroupMicroservice block_for_updates: #{msg_hash.to_s}"
|
|
635
|
+
# Process trigger notifications created by TriggerModel notify
|
|
578
636
|
if msg_hash['type'] == 'trigger'
|
|
579
637
|
data = JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true)
|
|
580
|
-
public_send(
|
|
638
|
+
public_send(TOPIC_LOOKUP[msg_hash['kind']], data)
|
|
581
639
|
end
|
|
582
640
|
end
|
|
583
641
|
rescue StandardError => e
|
|
@@ -590,11 +648,6 @@ module OpenC3
|
|
|
590
648
|
@logger.debug "TriggerGroupMicroservice web socket event: #{data}"
|
|
591
649
|
end
|
|
592
650
|
|
|
593
|
-
def refresh_event(data)
|
|
594
|
-
@logger.debug "TriggerGroupMicroservice web socket schedule refresh: #{data}"
|
|
595
|
-
@read_topic = false
|
|
596
|
-
end
|
|
597
|
-
|
|
598
651
|
# Add the trigger to the share.
|
|
599
652
|
def created_trigger_event(data)
|
|
600
653
|
@logger.debug "TriggerGroupMicroservice created_trigger_event #{data}"
|
|
@@ -604,6 +657,23 @@ module OpenC3
|
|
|
604
657
|
end
|
|
605
658
|
end
|
|
606
659
|
|
|
660
|
+
def updated_trigger_event(data)
|
|
661
|
+
@logger.debug "TriggerGroupMicroservice updated_trigger_event #{data}"
|
|
662
|
+
if data['group'] == @group
|
|
663
|
+
@share.trigger_base.update(trigger: data)
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
# When a trigger is updated it could change items which modifies topics and
|
|
668
|
+
# potentially adds or removes topics so refresh everything just to be safe
|
|
669
|
+
def rebuild_trigger_event(data)
|
|
670
|
+
@logger.debug "TriggerGroupMicroservice rebuild_trigger_event #{data}"
|
|
671
|
+
if data['group'] == @group
|
|
672
|
+
@share.trigger_base.update(trigger: data)
|
|
673
|
+
@read_topic = false
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
|
|
607
677
|
# Remove the trigger from the share.
|
|
608
678
|
def deleted_trigger_event(data)
|
|
609
679
|
@logger.debug "TriggerGroupMicroservice deleted_trigger_event #{data}"
|