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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/ext/openc3/ext/crc/crc.c +1 -1
  3. data/lib/openc3/api/cmd_api.rb +1 -1
  4. data/lib/openc3/microservices/decom_microservice.rb +10 -2
  5. data/lib/openc3/microservices/reaction_microservice.rb +152 -81
  6. data/lib/openc3/microservices/timeline_microservice.rb +1 -1
  7. data/lib/openc3/microservices/trigger_group_microservice.rb +188 -118
  8. data/lib/openc3/migrations/20230615000000_autonomic.rb +86 -0
  9. data/lib/openc3/models/activity_model.rb +2 -4
  10. data/lib/openc3/models/microservice_model.rb +6 -2
  11. data/lib/openc3/models/model.rb +1 -3
  12. data/lib/openc3/models/reaction_model.rb +124 -119
  13. data/lib/openc3/models/scope_model.rb +15 -3
  14. data/lib/openc3/models/timeline_model.rb +1 -3
  15. data/lib/openc3/models/trigger_group_model.rb +16 -50
  16. data/lib/openc3/models/trigger_model.rb +86 -123
  17. data/lib/openc3/packets/json_packet.rb +2 -3
  18. data/lib/openc3/script/commands.rb +10 -0
  19. data/lib/openc3/script/script.rb +1 -0
  20. data/lib/openc3/top_level.rb +0 -12
  21. data/lib/openc3/utilities/authorization.rb +1 -1
  22. data/lib/openc3/utilities/bucket_require.rb +5 -1
  23. data/lib/openc3/utilities/bucket_utilities.rb +4 -1
  24. data/lib/openc3/utilities/cli_generator.rb +56 -4
  25. data/lib/openc3/utilities/ruby_lex_utils.rb +4 -0
  26. data/lib/openc3/version.rb +6 -6
  27. data/templates/plugin/README.md +54 -4
  28. data/templates/plugin/Rakefile +31 -3
  29. data/templates/tool_angular/.editorconfig +16 -0
  30. data/templates/tool_angular/.gitignore +44 -0
  31. data/templates/tool_angular/.vscode/extensions.json +4 -0
  32. data/templates/tool_angular/.vscode/launch.json +20 -0
  33. data/templates/tool_angular/.vscode/tasks.json +42 -0
  34. data/templates/tool_angular/angular.json +111 -0
  35. data/templates/tool_angular/extra-webpack.config.js +8 -0
  36. data/templates/tool_angular/package.json +47 -0
  37. data/templates/tool_angular/src/app/app-routing.module.ts +15 -0
  38. data/templates/tool_angular/src/app/app.component.html +31 -0
  39. data/templates/tool_angular/src/app/app.component.scss +26 -0
  40. data/templates/tool_angular/src/app/app.component.spec.ts +29 -0
  41. data/templates/tool_angular/src/app/app.component.ts +51 -0
  42. data/templates/tool_angular/src/app/app.module.ts +30 -0
  43. data/templates/tool_angular/src/app/custom-overlay-container.ts +17 -0
  44. data/templates/tool_angular/src/app/empty-route/empty-route.component.ts +7 -0
  45. data/templates/tool_angular/src/app/openc3-api.d.ts +1 -0
  46. data/templates/tool_angular/src/assets/.gitkeep +0 -0
  47. data/templates/tool_angular/src/environments/environment.prod.ts +3 -0
  48. data/templates/tool_angular/src/environments/environment.ts +16 -0
  49. data/templates/tool_angular/src/favicon.ico +0 -0
  50. data/templates/tool_angular/src/index.html +13 -0
  51. data/templates/tool_angular/src/main.single-spa.ts +40 -0
  52. data/templates/tool_angular/src/single-spa/asset-url.ts +12 -0
  53. data/templates/tool_angular/src/single-spa/single-spa-props.ts +8 -0
  54. data/templates/tool_angular/src/styles.scss +1 -0
  55. data/templates/tool_angular/tsconfig.app.json +13 -0
  56. data/templates/tool_angular/tsconfig.json +33 -0
  57. data/templates/tool_angular/tsconfig.spec.json +14 -0
  58. data/templates/tool_angular/yarn.lock +8080 -0
  59. data/templates/tool_react/.eslintrc +7 -0
  60. data/templates/tool_react/.gitignore +72 -0
  61. data/templates/tool_react/.prettierignore +8 -0
  62. data/templates/tool_react/babel.config.json +29 -0
  63. data/templates/tool_react/jest.config.js +12 -0
  64. data/templates/tool_react/package.json +53 -0
  65. data/templates/tool_react/src/openc3-tool_name.js +24 -0
  66. data/templates/tool_react/src/root.component.js +88 -0
  67. data/templates/tool_react/src/root.component.test.js +9 -0
  68. data/templates/tool_react/webpack.config.js +27 -0
  69. data/templates/tool_react/yarn.lock +6854 -0
  70. data/templates/tool_svelte/.gitignore +72 -0
  71. data/templates/tool_svelte/.prettierignore +8 -0
  72. data/templates/tool_svelte/babel.config.js +12 -0
  73. data/templates/tool_svelte/build/smui.css +5 -0
  74. data/templates/tool_svelte/jest.config.js +9 -0
  75. data/templates/tool_svelte/package.json +46 -0
  76. data/templates/tool_svelte/rollup.config.js +72 -0
  77. data/templates/tool_svelte/src/App.svelte +42 -0
  78. data/templates/tool_svelte/src/App.test.js +9 -0
  79. data/templates/tool_svelte/src/services/api.js +92 -0
  80. data/templates/tool_svelte/src/services/axios.js +85 -0
  81. data/templates/tool_svelte/src/services/cable.js +65 -0
  82. data/templates/tool_svelte/src/services/config-parser.js +199 -0
  83. data/templates/tool_svelte/src/services/openc3-api.js +647 -0
  84. data/templates/tool_svelte/src/theme/_smui-theme.scss +25 -0
  85. data/templates/tool_svelte/src/tool_name.js +17 -0
  86. data/templates/tool_svelte/yarn.lock +5052 -0
  87. data/templates/tool_vue/.browserslistrc +16 -0
  88. data/templates/tool_vue/.env.standalone +1 -0
  89. data/templates/tool_vue/.eslintrc.js +43 -0
  90. data/templates/tool_vue/.gitignore +2 -0
  91. data/templates/tool_vue/.nycrc +3 -0
  92. data/templates/tool_vue/.prettierrc.js +5 -0
  93. data/templates/tool_vue/babel.config.json +11 -0
  94. data/templates/tool_vue/jsconfig.json +6 -0
  95. data/templates/tool_vue/package.json +52 -0
  96. data/templates/tool_vue/src/App.vue +15 -0
  97. data/templates/tool_vue/src/main.js +38 -0
  98. data/templates/tool_vue/src/router.js +29 -0
  99. data/templates/tool_vue/src/tools/tool_name/tool_name.vue +63 -0
  100. data/templates/tool_vue/vue.config.js +30 -0
  101. data/templates/tool_vue/yarn.lock +9145 -0
  102. data/templates/widget/package.json +9 -9
  103. data/templates/widget/yarn.lock +77 -73
  104. metadata +76 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6639b1c8a8f8b4902a85d6bdf1f23800aefd25b61b333c661c25236058e9595
4
- data.tar.gz: e2b037827217d57a34a126189895afc3ff2c62e9758cc326f1cf24be08e1b791
3
+ metadata.gz: 35a757bea46ad5b21ac348aa90b6163595d04c702b752b728f628d7dd82f6132
4
+ data.tar.gz: 149be2f48cdb7864a3665bf46982a1e16fb37541f68203387297c14d0d55e7d8
5
5
  SHA512:
6
- metadata.gz: 6d2b3412ed3db8cdee1772416419737fc895d54145f58c087168c8b8c17649ab155cc36c4563a6bb826852c02ae0f89ac0972f101e28db04fa2cdc9163799732
7
- data.tar.gz: 39eacdb1118b721017fb84359426576ced5f08be342972a46429797b22e2f6ad46db4999171a03dc310dbb8c41e09d3bbbc8dc86145f807dfa50bfa0ffbacaf3
6
+ metadata.gz: 1595ea101a9473b1aa5a1894d08bcd5704943fae771f9e58c4506b905cae9cb5957ab0fc961349ec9ecc4466a3ae34b6987a2a9d21ed8b9d2b0d31592c16908b
7
+ data.tar.gz: afbf06dfc4d4dbbfd7916146b6ee2b3005959bae650735127710b257a675487591adb5739744e603ef5eb20cc545b4dc79962021aaef00127c9aa76017a4ba3e
@@ -410,7 +410,7 @@ static VALUE crc64_calculate(int argc, VALUE *argv, VALUE self)
410
410
  /*
411
411
  * Initialize methods for Crc
412
412
  */
413
- void Init_crc()
413
+ void Init_crc(void)
414
414
  {
415
415
  id_ivar_seed = rb_intern("@seed");
416
416
  id_ivar_xor = rb_intern("@xor");
@@ -393,7 +393,7 @@ module OpenC3
393
393
  # Check if any of the parameters have DISABLE_MESSAGES
394
394
  cmd_params.each do |key, value|
395
395
  item = packet['items'].find { |item| item['name'] == key.to_s }
396
- if item['states'] && item['states'][value] && item['states'][value]["messages_disabled"]
396
+ if item && item['states'] && item['states'][value] && item['states'][value]["messages_disabled"]
397
397
  log_message = false
398
398
  end
399
399
  end
@@ -124,14 +124,22 @@ module OpenC3
124
124
  when :BLUE, :GREEN, :GREEN_LOW, :GREEN_HIGH
125
125
  @logger.info message
126
126
  when :YELLOW, :YELLOW_LOW, :YELLOW_HIGH
127
+ notification = NotificationModel.new(
128
+ time: time_nsec,
129
+ severity: "caution",
130
+ url: "/tools/limitsmonitor",
131
+ title: "#{packet.target_name} #{packet.packet_name} #{item.name} #{item.limits.state}",
132
+ body: "#{item.name} is #{item.limits.state}"
133
+ )
134
+ NotificationsTopic.write_notification(notification.as_json(:allow_nan => true), scope: @scope)
127
135
  @logger.warn message
128
136
  when :RED, :RED_LOW, :RED_HIGH
129
137
  notification = NotificationModel.new(
130
138
  time: time_nsec,
131
139
  severity: "critical",
132
140
  url: "/tools/limitsmonitor",
133
- title: "#{packet.target_name} #{packet.packet_name} #{item.name} out of limits",
134
- body: "Item went into #{item.limits.state} limit status."
141
+ title: "#{packet.target_name} #{packet.packet_name} #{item.name} #{item.limits.state}",
142
+ body: "#{item.name} is #{item.limits.state}"
135
143
  )
136
144
  NotificationsTopic.write_notification(notification.as_json(:allow_nan => true), scope: @scope)
137
145
  @logger.error message
@@ -23,6 +23,7 @@
23
23
  require 'openc3/microservices/microservice'
24
24
  require 'openc3/models/reaction_model'
25
25
  require 'openc3/models/notification_model'
26
+ require 'openc3/topics/notifications_topic'
26
27
  require 'openc3/models/trigger_model'
27
28
  require 'openc3/topics/autonomic_topic'
28
29
  require 'openc3/utilities/authentication'
@@ -30,12 +31,12 @@ require 'openc3/utilities/authentication'
30
31
  require 'openc3/script'
31
32
 
32
33
  module OpenC3
33
-
34
- # This should remain a thread safe implamentation. This is the in memory
34
+ # This should remain a thread safe implementation. This is the in memory
35
35
  # cache that should mirror the database. This will update two hash
36
36
  # variables and will track triggers to lookup what triggers link to what
37
37
  # reactions.
38
38
  class ReactionBase
39
+ attr_reader :reactions
39
40
 
40
41
  def initialize(scope:)
41
42
  @scope = scope
@@ -45,7 +46,7 @@ module OpenC3
45
46
  @lookup = Hash.new
46
47
  end
47
48
 
48
- # RETURNS an Array of active and not snoozed reactions
49
+ # RETURNS an Array of actively snoozed reactions
49
50
  def get_snoozed
50
51
  data = nil
51
52
  @reactions_mutex.synchronize do
@@ -53,15 +54,15 @@ module OpenC3
53
54
  end
54
55
  ret = Array.new
55
56
  return ret unless data
56
- data.each do | _name, r_hash |
57
+ data.each do |_name, r_hash|
57
58
  data = Marshal.load( Marshal.dump(r_hash) )
58
59
  reaction = ReactionModel.from_json(data, name: data['name'], scope: data['scope'])
59
- ret << reaction if reaction.active && reaction.snoozed_until
60
+ ret << reaction if reaction.enabled && reaction.snoozed_until
60
61
  end
61
62
  return ret
62
63
  end
63
64
 
64
- # RETURNS an Array of active and not snoozed reactions
65
+ # RETURNS an Array of actively NOT snoozed reactions
65
66
  def get_reactions(trigger_name:)
66
67
  array_value = nil
67
68
  @lookup_mutex.synchronize do
@@ -69,26 +70,25 @@ module OpenC3
69
70
  end
70
71
  ret = Array.new
71
72
  return ret unless array_value
72
- array_value.each do | name |
73
+ array_value.each do |name|
73
74
  @reactions_mutex.synchronize do
74
75
  data = Marshal.load( Marshal.dump(@reactions[name]) )
75
76
  reaction = ReactionModel.from_json(data, name: data['name'], scope: data['scope'])
76
- ret << reaction if reaction.active && reaction.snoozed_until.nil?
77
+ ret << reaction if reaction.enabled && reaction.snoozed_until.nil?
77
78
  end
78
79
  end
79
80
  return ret
80
81
  end
81
82
 
82
- # Update the memeory database with a HASH of reactions from the external
83
- # database
83
+ # Update the memory database with a HASH of reactions from the external database
84
84
  def setup(reactions:)
85
85
  @reactions_mutex.synchronize do
86
86
  @reactions = Marshal.load( Marshal.dump(reactions) )
87
87
  end
88
88
  @lookup_mutex.synchronize do
89
89
  @lookup = Hash.new
90
- reactions.each do | reaction_name, reaction |
91
- reaction['triggers'].each do | trigger |
90
+ reactions.each do |reaction_name, reaction|
91
+ reaction['triggers'].each do |trigger|
92
92
  trigger_name = trigger['name']
93
93
  if @lookup[trigger_name].nil?
94
94
  @lookup[trigger_name] = [reaction_name]
@@ -132,7 +132,7 @@ module OpenC3
132
132
  @reactions_mutex.synchronize do
133
133
  @reactions[reaction_name] = reaction
134
134
  end
135
- reaction['triggers'].each do | trigger |
135
+ reaction['triggers'].each do |trigger|
136
136
  trigger_name = trigger['name']
137
137
  @lookup_mutex.synchronize do
138
138
  if @lookup[trigger_name].nil?
@@ -147,30 +147,30 @@ module OpenC3
147
147
  # Updates a reaction to the in memory database. This current does not
148
148
  # update the lookup Hash for the triggers.
149
149
  def update(reaction:)
150
- reaction_name = reaction['name']
151
150
  @reactions_mutex.synchronize do
152
- @reactions[reaction_name] = reaction
151
+ model = ReactionModel.from_json(reaction, name: reaction['name'], scope: reaction['scope'])
152
+ model.update()
153
+ @reactions[reaction['name']] = model.as_json(:allow_nan => true)
153
154
  end
154
155
  end
155
156
 
156
157
  # Removes a reaction to the in memory database.
157
158
  def remove(reaction:)
158
- reaction_name = reaction['name']
159
159
  @reactions_mutex.synchronize do
160
- @reactions.delete(reaction_name)
160
+ @reactions.delete(reaction['name'])
161
+ ReactionModel.delete(name: reaction['name'], scope: reaction['scope'])
161
162
  end
162
- reaction['triggers'].each do | trigger |
163
+ reaction['triggers'].each do |trigger|
163
164
  trigger_name = trigger['name']
164
165
  @lookup_mutex.synchronize do
165
- @lookup[trigger_name].delete(reaction_name)
166
+ @lookup[trigger_name].delete(reaction['name'])
166
167
  end
167
168
  end
168
169
  end
169
170
  end
170
171
 
171
- # This should remain a thread safe implamentation.
172
+ # This should remain a thread safe implementation.
172
173
  class QueueBase
173
-
174
174
  attr_reader :queue
175
175
 
176
176
  def initialize(scope:)
@@ -182,9 +182,8 @@ module OpenC3
182
182
  end
183
183
  end
184
184
 
185
- # This should remain a thread safe implamentation.
185
+ # This should remain a thread safe implementation.
186
186
  class SnoozeBase
187
-
188
187
  def initialize(scope:)
189
188
  # store the round robin watch
190
189
  @watch_mutex = Mutex.new
@@ -207,7 +206,6 @@ module OpenC3
207
206
  # Shared between the monitor thread and the manager thread to
208
207
  # share the resources.
209
208
  class ReactionShare
210
-
211
209
  attr_reader :reaction_base, :queue_base, :snooze_base
212
210
 
213
211
  def initialize(scope:)
@@ -215,7 +213,6 @@ module OpenC3
215
213
  @queue_base = QueueBase.new(scope: scope)
216
214
  @snooze_base = SnoozeBase.new(scope: scope)
217
215
  end
218
-
219
216
  end
220
217
 
221
218
  # The Reaction worker is a very simple thread pool worker. Once the manager
@@ -262,7 +259,7 @@ module OpenC3
262
259
  when 'reaction'
263
260
  run_reaction(reaction: reaction(data: data))
264
261
  when 'trigger'
265
- process_enabled_trigger(data: data)
262
+ process_true_trigger(data: data)
266
263
  end
267
264
  rescue StandardError => e
268
265
  @logger.error "ReactionWorker-#{@ident} failed to evaluate kind: #{kind} data: #{data}\n#{e.formatted}"
@@ -271,8 +268,8 @@ module OpenC3
271
268
  @logger.info "ReactionWorker-#{@ident} exiting"
272
269
  end
273
270
 
274
- def process_enabled_trigger(data:)
275
- @share.reaction_base.get_reactions(trigger_name: data['name']).each do | reaction |
271
+ def process_true_trigger(data:)
272
+ @share.reaction_base.get_reactions(trigger_name: data['name']).each do |reaction|
276
273
  run_reaction(reaction: reaction)
277
274
  end
278
275
  end
@@ -285,7 +282,21 @@ module OpenC3
285
282
  end
286
283
 
287
284
  def run_action(reaction:, action:)
285
+ reaction.updated_at = Time.now.to_nsec_from_epoch
286
+ reaction_json = reaction.as_json(:allow_nan => true)
287
+ # Let the frontend know which action is being run
288
+ # because we can combine commands and scripts with notifications
289
+ reaction_json['action'] = action['type']
290
+ notification = {
291
+ 'kind' => 'run',
292
+ 'type' => 'reaction',
293
+ 'data' => JSON.generate(reaction_json),
294
+ }
295
+ AutonomicTopic.write_notification(notification, scope: @scope)
296
+
288
297
  case action['type']
298
+ when 'notify'
299
+ run_notify(reaction: reaction, action: action)
289
300
  when 'command'
290
301
  run_command(reaction: reaction, action: action)
291
302
  when 'script'
@@ -293,21 +304,31 @@ module OpenC3
293
304
  end
294
305
  end
295
306
 
307
+ def run_notify(reaction:, action:)
308
+ notification = NotificationModel.new(
309
+ time: Time.now.to_nsec_from_epoch,
310
+ severity: action['severity'],
311
+ url: "/tools/autonomic/reactions",
312
+ title: "#{reaction.name} run",
313
+ body: action['value']
314
+ )
315
+ NotificationsTopic.write_notification(notification.as_json(:allow_nan => true), scope: @scope)
316
+ @logger.info "ReactionWorker-#{@ident} #{reaction.name} notify action complete, body: #{action['value']}, severity: #{action['severity']}"
317
+ end
318
+
296
319
  def run_command(reaction:, action:)
297
- @logger.debug "ReactionWorker-#{@ident} running reaction #{reaction.name}, command: '#{action['value']}' "
298
320
  begin
299
321
  username = reaction.username
300
322
  token = get_token(username)
301
323
  raise "No token available for username: #{username}" unless token
302
324
  cmd_no_hazardous_check(action['value'], scope: @scope, token: token)
303
- @logger.info "ReactionWorker-#{@ident} #{reaction.name} command action complete, #{action['value']}"
325
+ @logger.info "ReactionWorker-#{@ident} #{reaction.name} command action complete, command: #{action['value']}"
304
326
  rescue StandardError => e
305
327
  @logger.error "ReactionWorker-#{@ident} #{reaction.name} command action failed, #{action}\n#{e.message}"
306
328
  end
307
329
  end
308
330
 
309
331
  def run_script(reaction:, action:)
310
- @logger.debug "ReactionWorker-#{@ident} running reaction #{reaction.name}, script: '#{action['value']}'"
311
332
  begin
312
333
  username = reaction.username
313
334
  token = get_token(username)
@@ -351,7 +372,7 @@ module OpenC3
351
372
 
352
373
  def generate_thread_pool()
353
374
  thread_pool = []
354
- @worker_count.times do | i |
375
+ @worker_count.times do |i|
355
376
  worker = ReactionWorker.new(name: @name, logger: @logger, scope: @scope, share: @share, ident: i)
356
377
  thread_pool << Thread.new { worker.run }
357
378
  end
@@ -376,7 +397,7 @@ module OpenC3
376
397
  end
377
398
 
378
399
  def active_triggers(reaction:)
379
- reaction.triggers.each do | trigger |
400
+ reaction.triggers.each do |trigger|
380
401
  t = TriggerModel.get(name: trigger['name'], group: trigger['group'], scope: @scope)
381
402
  return true if t && t.state
382
403
  end
@@ -384,16 +405,11 @@ module OpenC3
384
405
  end
385
406
 
386
407
  def manage_snoozed_reactions(current_time:)
387
- @share.reaction_base.get_snoozed.each do | reaction |
408
+ @share.reaction_base.get_snoozed.each do |reaction|
388
409
  time_difference = reaction.snoozed_until - current_time
389
410
  if time_difference <= 0 && @share.snooze_base.not_queued?(reaction: reaction)
390
- @logger.info "#{reaction.name} current: #{current_time}, vs #{reaction.snoozed_until}, #{time_difference}"
391
- unless reaction.review
392
- @logger.debug "#{reaction.name} review set to false, setting snoozed_until back to nil"
393
- @share.reaction_base.wake(name: reaction.name)
394
- next
395
- end
396
- if active_triggers(reaction: reaction)
411
+ # LEVEL triggers mean we run if the trigger is active
412
+ if reaction.triggerLevel == 'LEVEL' and active_triggers(reaction: reaction)
397
413
  @share.queue_base.enqueue(kind: 'reaction', data: reaction.as_json(:allow_nan => true))
398
414
  else
399
415
  @share.reaction_base.wake(name: reaction.name)
@@ -404,7 +420,7 @@ module OpenC3
404
420
 
405
421
  def shutdown
406
422
  @cancel_thread = true
407
- @worker_count.times do | i |
423
+ @worker_count.times do |i|
408
424
  @share.queue_base.enqueue(kind: nil, data: nil)
409
425
  end
410
426
  end
@@ -415,8 +431,39 @@ module OpenC3
415
431
  # AutonomicTopic for changes.
416
432
  class ReactionMicroservice < Microservice
417
433
  attr_reader :name, :scope, :share, :manager, :manager_thread
434
+ TOPIC_LOOKUP = {
435
+ 'group' => {
436
+ 'created' => :no_op,
437
+ 'updated' => :no_op,
438
+ 'deleted' => :no_op,
439
+ },
440
+ 'trigger' => {
441
+ 'error' => :no_op,
442
+ 'created' => :no_op,
443
+ 'updated' => :no_op,
444
+ 'deleted' => :no_op,
445
+ 'enabled' => :no_op,
446
+ 'disabled' => :no_op,
447
+ 'true' => :trigger_true_event,
448
+ 'false' => :no_op,
449
+ },
450
+ 'reaction' => {
451
+ 'run' => :no_op,
452
+ 'deployed' => :no_op,
453
+ 'undeployed' => :no_op,
454
+ 'created' => :reaction_created_event,
455
+ 'updated' => :reaction_updated_event,
456
+ 'deleted' => :reaction_deleted_event,
457
+ 'enabled' => :reaction_enabled_event,
458
+ 'disabled' => :reaction_disabled_event,
459
+ 'snoozed' => :no_op,
460
+ 'awakened' => :no_op,
461
+ 'executed' => :reaction_execute_event,
462
+ }
463
+ }
418
464
 
419
465
  def initialize(*args)
466
+ # The name is passed in via the reaction_model as "#{scope}__OPENC3__REACTION"
420
467
  super(*args)
421
468
  @share = ReactionShare.new(scope: @scope)
422
469
  @manager = ReactionSnoozeManager.new(name: @name, logger: @logger, scope: @scope, share: @share)
@@ -426,53 +473,36 @@ module OpenC3
426
473
 
427
474
  def run
428
475
  @logger.info "ReactionMicroservice running"
476
+ # Let the frontend know that the microservice has been deployed and is running
477
+ notification = {
478
+ 'kind' => 'deployed',
479
+ 'type' => 'reaction',
480
+ # name and updated_at fields are required for Event formatting
481
+ 'data' => JSON.generate({
482
+ 'name' => @name,
483
+ 'updated_at' => Time.now.to_nsec_from_epoch,
484
+ }),
485
+ }
486
+ AutonomicTopic.write_notification(notification, scope: @scope)
487
+
429
488
  @manager_thread = Thread.new { @manager.run }
430
489
  loop do
431
490
  reactions = ReactionModel.all(scope: @scope)
432
491
  @share.reaction_base.setup(reactions: reactions)
433
492
  break if @cancel_thread
434
-
435
493
  block_for_updates()
436
494
  break if @cancel_thread
437
495
  end
438
496
  @logger.info "ReactionMicroservice exiting"
439
497
  end
440
498
 
441
- def topic_lookup_functions
442
- return {
443
- 'group' => {
444
- 'created' => :no_op,
445
- 'updated' => :no_op,
446
- 'deleted' => :no_op,
447
- },
448
- 'trigger' => {
449
- 'created' => :no_op,
450
- 'updated' => :no_op,
451
- 'deleted' => :no_op,
452
- 'enabled' => :trigger_enabled_event,
453
- 'disabled' => :no_op,
454
- 'activated' => :no_op,
455
- 'deactivated' => :no_op,
456
- },
457
- 'reaction' => {
458
- 'created' => :reaction_created_event,
459
- 'updated' => :refresh_event,
460
- 'deleted' => :reaction_deleted_event,
461
- 'sleep' => :no_op,
462
- 'awaken' => :no_op,
463
- 'activated' => :reaction_updated_event,
464
- 'deactivated' => :reaction_updated_event,
465
- }
466
- }
467
- end
468
-
469
499
  def block_for_updates
470
500
  @read_topic = true
471
- while @read_topic
501
+ while @read_topic && !@cancel_thread
472
502
  begin
473
503
  AutonomicTopic.read_topics(@topics) do |_topic, _msg_id, msg_hash, _redis|
474
504
  @logger.debug "ReactionMicroservice block_for_updates: #{msg_hash.to_s}"
475
- public_send(topic_lookup_functions[msg_hash['type']][msg_hash['kind']], msg_hash)
505
+ public_send(TOPIC_LOOKUP[msg_hash['type']][msg_hash['kind']], msg_hash)
476
506
  end
477
507
  rescue StandardError => e
478
508
  @logger.error "ReactionMicroservice failed to read topics #{@topics}\n#{e.formatted}"
@@ -484,29 +514,70 @@ module OpenC3
484
514
  @logger.debug "ReactionMicroservice web socket event: #{data}"
485
515
  end
486
516
 
487
- def refresh_event(data)
488
- @logger.debug "ReactionMicroservice web socket schedule refresh: #{data}"
517
+ def reaction_updated_event(msg_hash)
518
+ @logger.debug "ReactionMicroservice reaction updated msg_hash: #{msg_hash}"
519
+ reaction = JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true)
520
+ @share.reaction_base.update(reaction: reaction)
489
521
  @read_topic = false
490
522
  end
491
523
 
492
- #
493
- def trigger_enabled_event(msg_hash)
494
- @logger.debug "ReactionMicroservice trigger event msg_hash: #{msg_hash}"
524
+ def trigger_true_event(msg_hash)
525
+ @logger.debug "ReactionMicroservice trigger true msg_hash: #{msg_hash}"
495
526
  @share.queue_base.enqueue(kind: 'trigger', data: JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true))
496
527
  end
497
528
 
498
529
  # Add the reaction to the shared data.
499
530
  def reaction_created_event(msg_hash)
500
531
  @logger.debug "ReactionMicroservice reaction created msg_hash: #{msg_hash}"
501
- @share.reaction_base.add(reaction: JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true))
532
+ reaction = JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true)
533
+ @share.reaction_base.add(reaction: reaction)
534
+
535
+ # If the reaction triggerLevel is LEVEL we have to check its triggers
536
+ # on add because if the trigger is active it should run
537
+ if reaction['triggerLevel'] == 'LEVEL'
538
+ reaction['triggers'].each do |trigger_hash|
539
+ trigger = TriggerModel.get(name: trigger_hash['name'], group: trigger_hash['group'], scope: reaction['scope'])
540
+ if trigger && trigger.state
541
+ @logger.info "ReactionMicroservice reaction #{reaction['name']} created. Since triggerLevel is 'LEVEL' it was run due to #{trigger.name}."
542
+ @share.queue_base.enqueue(kind: 'reaction', data: reaction)
543
+ end
544
+ end
545
+ end
502
546
  end
503
547
 
504
548
  # Update the reaction to the shared data.
505
- def reaction_updated_event(msg_hash)
506
- @logger.debug "ReactionMicroservice reaction updated msg_hash: #{msg_hash}"
549
+ def reaction_enabled_event(msg_hash)
550
+ @logger.debug "ReactionMicroservice reaction enabled msg_hash: #{msg_hash}"
551
+ reaction = JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true)
552
+ @share.reaction_base.update(reaction: reaction)
553
+
554
+ # If the reaction triggerLevel is LEVEL we have to check its triggers
555
+ # on add because if the trigger is active it should run
556
+ if reaction['triggerLevel'] == 'LEVEL'
557
+ reaction['triggers'].each do |trigger_hash|
558
+ trigger = TriggerModel.get(name: trigger_hash['name'], group: trigger_hash['group'], scope: reaction['scope'])
559
+ if trigger && trigger.state
560
+ @logger.info "ReactionMicroservice reaction #{reaction['name']} enabled. Since triggerLevel is 'LEVEL' it was run due to #{trigger.name}."
561
+ @share.queue_base.enqueue(kind: 'reaction', data: reaction)
562
+ end
563
+ end
564
+ end
565
+ end
566
+
567
+ # Update the reaction to the shared data.
568
+ def reaction_disabled_event(msg_hash)
569
+ @logger.debug "ReactionMicroservice reaction disabled msg_hash: #{msg_hash}"
507
570
  @share.reaction_base.update(reaction: JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true))
508
571
  end
509
572
 
573
+ # Add the reaction to the shared data.
574
+ def reaction_execute_event(msg_hash)
575
+ @logger.debug "ReactionMicroservice reaction execute msg_hash: #{msg_hash}"
576
+ reaction = JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true)
577
+ @share.reaction_base.update(reaction: reaction)
578
+ @share.queue_base.enqueue(kind: 'reaction', data: reaction)
579
+ end
580
+
510
581
  # Remove the reaction from the shared data
511
582
  def reaction_deleted_event(msg_hash)
512
583
  @logger.debug "ReactionMicroservice reaction deleted msg_hash: #{msg_hash}"
@@ -136,7 +136,7 @@ module OpenC3
136
136
 
137
137
  # Shared between the monitor thread and the manager thread to
138
138
  # share the planned activities. This should remain a thread
139
- # safe implamentation.
139
+ # safe implementation.
140
140
  class Schedule
141
141
  def initialize(name)
142
142
  @name = name