openc3 5.19.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -7
  3. data/bin/openc3cli +12 -120
  4. data/bin/pipinstall +5 -3
  5. data/data/config/command_modifiers.yaml +17 -1
  6. data/data/config/interface_modifiers.yaml +21 -4
  7. data/data/config/item_modifiers.yaml +1 -1
  8. data/data/config/microservice.yaml +15 -2
  9. data/data/config/param_item_modifiers.yaml +1 -1
  10. data/data/config/parameter_modifiers.yaml +1 -1
  11. data/data/config/table_manager.yaml +2 -2
  12. data/data/config/target.yaml +11 -0
  13. data/data/config/telemetry_modifiers.yaml +17 -1
  14. data/data/config/tool.yaml +12 -0
  15. data/data/config/widgets.yaml +41 -17
  16. data/ext/openc3/ext/packet/packet.c +3 -0
  17. data/lib/openc3/accessors/form_accessor.rb +4 -3
  18. data/lib/openc3/accessors/html_accessor.rb +3 -3
  19. data/lib/openc3/accessors/http_accessor.rb +13 -13
  20. data/lib/openc3/accessors/xml_accessor.rb +16 -4
  21. data/lib/openc3/api/cmd_api.rb +4 -5
  22. data/lib/openc3/api/limits_api.rb +3 -3
  23. data/lib/openc3/api/target_api.rb +0 -30
  24. data/lib/openc3/api/tlm_api.rb +2 -1
  25. data/lib/openc3/bridge/bridge_config.rb +1 -2
  26. data/lib/openc3/ccsds/ccsds_parser.rb +12 -8
  27. data/lib/openc3/config/config_parser.rb +10 -3
  28. data/lib/openc3/conversions/bit_reverse_conversion.rb +1 -0
  29. data/lib/openc3/conversions/conversion.rb +5 -1
  30. data/lib/openc3/conversions/generic_conversion.rb +3 -8
  31. data/lib/openc3/conversions/object_read_conversion.rb +1 -8
  32. data/lib/openc3/conversions/polynomial_conversion.rb +3 -8
  33. data/lib/openc3/conversions/processor_conversion.rb +13 -11
  34. data/lib/openc3/conversions/segmented_polynomial_conversion.rb +3 -11
  35. data/lib/openc3/conversions/unix_time_conversion.rb +4 -7
  36. data/lib/openc3/conversions/unix_time_formatted_conversion.rb +4 -3
  37. data/lib/openc3/conversions/unix_time_seconds_conversion.rb +4 -3
  38. data/lib/openc3/core_ext/array.rb +0 -16
  39. data/lib/openc3/core_ext.rb +0 -1
  40. data/lib/openc3/interfaces/file_interface.rb +198 -0
  41. data/lib/openc3/interfaces/http_client_interface.rb +71 -39
  42. data/lib/openc3/interfaces/http_server_interface.rb +1 -9
  43. data/lib/openc3/interfaces/interface.rb +3 -2
  44. data/lib/openc3/interfaces/mqtt_interface.rb +32 -15
  45. data/lib/openc3/interfaces/mqtt_stream_interface.rb +19 -4
  46. data/lib/openc3/interfaces/protocols/crc_protocol.rb +7 -0
  47. data/lib/openc3/interfaces/serial_interface.rb +1 -0
  48. data/lib/openc3/interfaces/tcpip_server_interface.rb +1 -2
  49. data/lib/openc3/interfaces/udp_interface.rb +5 -3
  50. data/lib/openc3/interfaces.rb +2 -4
  51. data/lib/openc3/io/json_drb.rb +5 -0
  52. data/lib/openc3/io/json_rpc.rb +10 -9
  53. data/lib/openc3/io/udp_sockets.rb +7 -5
  54. data/lib/openc3/microservices/decom_microservice.rb +24 -7
  55. data/lib/openc3/microservices/interface_microservice.rb +65 -7
  56. data/lib/openc3/microservices/microservice.rb +1 -2
  57. data/lib/openc3/microservices/multi_microservice.rb +3 -3
  58. data/lib/openc3/migrations/20241208080000_no_critical_cmd.rb +31 -0
  59. data/lib/openc3/migrations/20241208080001_no_trigger_group.rb +46 -0
  60. data/lib/openc3/models/activity_model.rb +7 -3
  61. data/lib/openc3/models/cvt_model.rb +7 -1
  62. data/lib/openc3/models/interface_model.rb +9 -3
  63. data/lib/openc3/models/microservice_model.rb +8 -1
  64. data/lib/openc3/models/model.rb +1 -0
  65. data/lib/openc3/models/plugin_model.rb +11 -6
  66. data/lib/openc3/models/python_package_model.rb +10 -3
  67. data/lib/openc3/models/reaction_model.rb +14 -10
  68. data/lib/openc3/models/scope_model.rb +87 -25
  69. data/lib/openc3/models/target_model.rb +17 -1
  70. data/lib/openc3/models/timeline_model.rb +17 -5
  71. data/lib/openc3/models/tool_model.rb +15 -3
  72. data/lib/openc3/models/trigger_group_model.rb +6 -3
  73. data/lib/openc3/operators/microservice_operator.rb +10 -3
  74. data/lib/openc3/packets/commands.rb +17 -6
  75. data/lib/openc3/packets/limits.rb +0 -12
  76. data/lib/openc3/packets/packet.rb +10 -1
  77. data/lib/openc3/packets/packet_config.rb +34 -1
  78. data/lib/openc3/packets/packet_item.rb +30 -32
  79. data/lib/openc3/packets/structure_item.rb +2 -2
  80. data/lib/openc3/script/calendar.rb +1 -6
  81. data/lib/openc3/script/commands.rb +19 -13
  82. data/lib/openc3/script/critical_cmd.rb +91 -0
  83. data/lib/openc3/script/screen.rb +2 -2
  84. data/lib/openc3/script/script.rb +17 -10
  85. data/lib/openc3/script/web_socket_api.rb +5 -5
  86. data/lib/openc3/streams/mqtt_stream.rb +41 -33
  87. data/lib/openc3/streams/serial_stream.rb +27 -27
  88. data/lib/openc3/streams/stream.rb +17 -17
  89. data/lib/openc3/streams/tcpip_client_stream.rb +1 -1
  90. data/lib/openc3/streams/tcpip_socket_stream.rb +19 -19
  91. data/lib/openc3/system/system.rb +1 -1
  92. data/lib/openc3/system.rb +2 -3
  93. data/lib/openc3/tools/table_manager/table.rb +2 -2
  94. data/lib/openc3/tools/table_manager/table_parser.rb +1 -1
  95. data/lib/openc3/top_level.rb +9 -5
  96. data/lib/openc3/topics/command_decom_topic.rb +0 -7
  97. data/lib/openc3/topics/command_topic.rb +16 -0
  98. data/lib/openc3/topics/interface_topic.rb +2 -0
  99. data/lib/openc3/utilities/authentication.rb +7 -3
  100. data/lib/openc3/utilities/bucket_utilities.rb +1 -1
  101. data/lib/openc3/utilities/cli_generator.rb +0 -1
  102. data/lib/openc3/utilities/logger.rb +1 -0
  103. data/lib/openc3/utilities/store_queued.rb +1 -0
  104. data/lib/openc3/version.rb +6 -6
  105. data/templates/conversion/conversion.rb +2 -0
  106. data/templates/plugin/README.md +1 -1
  107. data/templates/target/targets/TARGET/lib/target.rb +1 -1
  108. data/templates/tool_angular/package.json +9 -9
  109. data/templates/tool_angular/src/app/app.component.html +4 -13
  110. data/templates/tool_angular/src/app/app.component.scss +5 -13
  111. data/templates/tool_angular/src/app/app.component.ts +5 -4
  112. data/templates/tool_angular/src/app/custom-overlay-container.ts +2 -2
  113. data/templates/tool_angular/src/app/openc3-api.d.ts +1 -1
  114. data/templates/tool_angular/src/main.single-spa.ts +1 -1
  115. data/templates/tool_react/package.json +1 -0
  116. data/templates/tool_react/src/root.component.js +1 -1
  117. data/templates/tool_svelte/build/smui.css +1 -1
  118. data/templates/tool_svelte/package.json +11 -9
  119. data/templates/tool_svelte/rollup.config.js +2 -0
  120. data/templates/tool_svelte/src/App.svelte +2 -2
  121. data/templates/tool_vue/eslint.config.mjs +68 -0
  122. data/templates/tool_vue/jsconfig.json +1 -1
  123. data/templates/tool_vue/package.json +26 -43
  124. data/templates/tool_vue/src/App.vue +3 -5
  125. data/templates/tool_vue/src/main.js +12 -23
  126. data/templates/tool_vue/src/router.js +19 -18
  127. data/templates/tool_vue/src/tools/tool_name/tool_name.vue +2 -2
  128. data/templates/tool_vue/vite.config.js +52 -0
  129. data/templates/widget/package.json +19 -26
  130. data/templates/widget/src/Widget.vue +13 -15
  131. data/templates/widget/vite.config.js +26 -0
  132. metadata +25 -39
  133. data/lib/openc3/core_ext/hash.rb +0 -40
  134. data/lib/openc3/core_ext/httpclient.rb +0 -11
  135. data/lib/openc3/interfaces/linc_interface.rb +0 -480
  136. data/lib/openc3/interfaces/protocols/override_protocol.rb +0 -4
  137. data/lib/openc3/microservices/reaction_microservice.rb +0 -607
  138. data/lib/openc3/microservices/timeline_microservice.rb +0 -400
  139. data/lib/openc3/microservices/trigger_group_microservice.rb +0 -698
  140. data/lib/openc3/migrations/20230615000000_autonomic.rb +0 -86
  141. data/lib/openc3/migrations/20240915000000_activity_uuid.rb +0 -28
  142. data/lib/openc3/system/system_config.rb +0 -413
  143. data/templates/tool_svelte/src/services/api.js +0 -92
  144. data/templates/tool_svelte/src/services/axios.js +0 -85
  145. data/templates/tool_svelte/src/services/cable.js +0 -65
  146. data/templates/tool_svelte/src/services/config-parser.js +0 -198
  147. data/templates/tool_svelte/src/services/openc3-api.js +0 -606
  148. data/templates/tool_vue/.eslintrc.js +0 -43
  149. data/templates/tool_vue/babel.config.json +0 -11
  150. data/templates/tool_vue/vue.config.js +0 -38
  151. data/templates/widget/.eslintrc.js +0 -43
  152. data/templates/widget/babel.config.json +0 -11
  153. data/templates/widget/vue.config.js +0 -28
  154. /data/templates/tool_vue/{.prettierrc.js → .prettierrc.cjs} +0 -0
  155. /data/templates/widget/{.prettierrc.js → .prettierrc.cjs} +0 -0
@@ -1,607 +0,0 @@
1
- # encoding: ascii-8bit
2
-
3
- # Copyright 2022 Ball Aerospace & Technologies Corp.
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
- # Modified by OpenC3, Inc.
17
- # All changes Copyright 2022, OpenC3, Inc.
18
- # All Rights Reserved
19
- #
20
- # This file may also be used under the terms of a commercial license
21
- # if purchased from OpenC3, Inc.
22
-
23
- require 'openc3/microservices/microservice'
24
- require 'openc3/models/reaction_model'
25
- require 'openc3/models/trigger_model'
26
- require 'openc3/topics/autonomic_topic'
27
- require 'openc3/utilities/authentication'
28
-
29
- require 'openc3/script'
30
-
31
- module OpenC3
32
- # This should remain a thread safe implementation. This is the in memory
33
- # cache that should mirror the database. This will update two hash
34
- # variables and will track triggers to lookup what triggers link to what
35
- # reactions.
36
- class ReactionBase
37
- attr_reader :reactions
38
-
39
- def initialize(scope:)
40
- @scope = scope
41
- @reactions_mutex = Mutex.new
42
- @reactions = Hash.new
43
- @lookup_mutex = Mutex.new
44
- @lookup = Hash.new
45
- end
46
-
47
- # RETURNS an Array of actively snoozed reactions
48
- def get_snoozed
49
- data = nil
50
- @reactions_mutex.synchronize do
51
- data = Marshal.load( Marshal.dump(@reactions) )
52
- end
53
- ret = Array.new
54
- return ret unless data
55
- data.each do |_name, r_hash|
56
- data = Marshal.load( Marshal.dump(r_hash) )
57
- reaction = ReactionModel.from_json(data, name: data['name'], scope: data['scope'])
58
- ret << reaction if reaction.enabled && reaction.snoozed_until
59
- end
60
- return ret
61
- end
62
-
63
- # RETURNS an Array of actively NOT snoozed reactions
64
- def get_reactions(trigger_name:)
65
- array_value = nil
66
- @lookup_mutex.synchronize do
67
- array_value = Marshal.load( Marshal.dump(@lookup[trigger_name]) )
68
- end
69
- ret = Array.new
70
- return ret unless array_value
71
- array_value.each do |name|
72
- @reactions_mutex.synchronize do
73
- data = Marshal.load( Marshal.dump(@reactions[name]) )
74
- reaction = ReactionModel.from_json(data, name: data['name'], scope: data['scope'])
75
- ret << reaction if reaction.enabled && reaction.snoozed_until.nil?
76
- end
77
- end
78
- return ret
79
- end
80
-
81
- # Update the memory database with a HASH of reactions from the external database
82
- def setup(reactions:)
83
- @reactions_mutex.synchronize do
84
- @reactions = Marshal.load( Marshal.dump(reactions) )
85
- end
86
- @lookup_mutex.synchronize do
87
- @lookup = Hash.new
88
- reactions.each do |reaction_name, reaction|
89
- reaction['triggers'].each do |trigger|
90
- trigger_name = trigger['name']
91
- if @lookup[trigger_name].nil?
92
- @lookup[trigger_name] = [reaction_name]
93
- else
94
- @lookup[trigger_name] << reaction_name
95
- end
96
- end
97
- end
98
- end
99
- end
100
-
101
- # Pulls the latest reaction name from the in memory database to see
102
- # if the reaction should be put to sleep.
103
- def sleep(name:)
104
- @reactions_mutex.synchronize do
105
- data = Marshal.load( Marshal.dump(@reactions[name]) )
106
- return unless data
107
- reaction = ReactionModel.from_json(data, name: data['name'], scope: data['scope'])
108
- if reaction.snoozed_until.nil? || Time.now.to_i >= reaction.snoozed_until
109
- reaction.sleep()
110
- end
111
- @reactions[name] = reaction.as_json(:allow_nan => true)
112
- end
113
- end
114
-
115
- # Pulls the latest reaction name from the in memory database to see
116
- # if the reaction should be awaken.
117
- def wake(name:)
118
- @reactions_mutex.synchronize do
119
- data = Marshal.load( Marshal.dump(@reactions[name]) )
120
- return unless data
121
- reaction = ReactionModel.from_json(data, name: data['name'], scope: data['scope'])
122
- reaction.awaken()
123
- @reactions[name] = reaction.as_json(:allow_nan => true)
124
- end
125
- end
126
-
127
- # Add a reaction to the in memory database
128
- def add(reaction:)
129
- reaction_name = reaction['name']
130
- @reactions_mutex.synchronize do
131
- @reactions[reaction_name] = reaction
132
- end
133
- reaction['triggers'].each do |trigger|
134
- trigger_name = trigger['name']
135
- @lookup_mutex.synchronize do
136
- if @lookup[trigger_name].nil?
137
- @lookup[trigger_name] = [reaction_name]
138
- else
139
- @lookup[trigger_name] << reaction_name
140
- end
141
- end
142
- end
143
- end
144
-
145
- # Updates a reaction to the in memory database. This current does not
146
- # update the lookup Hash for the triggers.
147
- def update(reaction:)
148
- @reactions_mutex.synchronize do
149
- model = ReactionModel.from_json(reaction, name: reaction['name'], scope: reaction['scope'])
150
- model.update()
151
- @reactions[reaction['name']] = model.as_json(:allow_nan => true)
152
- end
153
- end
154
-
155
- # Removes a reaction to the in memory database.
156
- def remove(reaction:)
157
- @reactions_mutex.synchronize do
158
- @reactions.delete(reaction['name'])
159
- ReactionModel.delete(name: reaction['name'], scope: reaction['scope'])
160
- end
161
- reaction['triggers'].each do |trigger|
162
- trigger_name = trigger['name']
163
- @lookup_mutex.synchronize do
164
- @lookup[trigger_name].delete(reaction['name'])
165
- end
166
- end
167
- end
168
- end
169
-
170
- # This should remain a thread safe implementation.
171
- class QueueBase
172
- attr_reader :queue
173
-
174
- def initialize(scope:)
175
- @queue = Queue.new
176
- end
177
-
178
- def enqueue(kind:, data:)
179
- @queue << [kind, data]
180
- end
181
- end
182
-
183
- # This should remain a thread safe implementation.
184
- class SnoozeBase
185
- def initialize(scope:)
186
- # store the round robin watch
187
- @watch_mutex = Mutex.new
188
- @watch_size = 25
189
- @watch_queue = Array.new(@watch_size)
190
- @watch_index = 0
191
- end
192
-
193
- def not_queued?(reaction:)
194
- key = "#{reaction.name}__#{reaction.snoozed_until}"
195
- @watch_mutex.synchronize do
196
- return false if @watch_queue.index(key)
197
- @watch_queue[@watch_index] = key
198
- @watch_index = @watch_index + 1 >= @watch_size ? 0 : @watch_index + 1
199
- return true
200
- end
201
- end
202
- end
203
-
204
- # Shared between the monitor thread and the manager thread to
205
- # share the resources.
206
- class ReactionShare
207
- attr_reader :reaction_base, :queue_base, :snooze_base
208
-
209
- def initialize(scope:)
210
- @reaction_base = ReactionBase.new(scope: scope)
211
- @queue_base = QueueBase.new(scope: scope)
212
- @snooze_base = SnoozeBase.new(scope: scope)
213
- end
214
- end
215
-
216
- # The Reaction worker is a very simple thread pool worker. Once the manager
217
- # queues a trigger to evaluate against the reactions. The worker will check
218
- # the reactions to see if it needs to fire any reactions.
219
- class ReactionWorker
220
- attr_reader :name, :scope, :share
221
-
222
- def initialize(name:, logger:, scope:, share:, ident:)
223
- @name = name
224
- @logger = logger
225
- @scope = scope
226
- @share = share
227
- @ident = ident
228
- end
229
-
230
- def get_token(username)
231
- if ENV['OPENC3_API_CLIENT'].nil?
232
- ENV['OPENC3_API_PASSWORD'] ||= ENV['OPENC3_SERVICE_PASSWORD']
233
- return OpenC3Authentication.new().token
234
- else
235
- # Check for offline access token
236
- model = nil
237
- model = OpenC3::OfflineAccessModel.get_model(name: username, scope: @scope) if username and username != ''
238
- if model and model.offline_access_token
239
- auth = OpenC3KeycloakAuthentication.new(ENV['OPENC3_KEYCLOAK_URL'])
240
- return auth.get_token_from_refresh_token(model.offline_access_token)
241
- else
242
- return nil
243
- end
244
- end
245
- end
246
-
247
- def reaction(data:)
248
- return ReactionModel.from_json(data, name: data['name'], scope: data['scope'])
249
- end
250
-
251
- def run
252
- @logger.info "ReactionWorker-#{@ident} running"
253
- loop do
254
- begin
255
- kind, data = @share.queue_base.queue.pop
256
- break if kind.nil? || data.nil?
257
- case kind
258
- when 'reaction'
259
- run_reaction(reaction: reaction(data: data))
260
- when 'trigger'
261
- process_true_trigger(data: data)
262
- end
263
- rescue StandardError => e
264
- @logger.error "ReactionWorker-#{@ident} failed to evaluate kind: #{kind} data: #{data}\n#{e.formatted}"
265
- end
266
- end
267
- @logger.info "ReactionWorker-#{@ident} exiting"
268
- end
269
-
270
- def process_true_trigger(data:)
271
- @share.reaction_base.get_reactions(trigger_name: data['name']).each do |reaction|
272
- run_reaction(reaction: reaction)
273
- end
274
- end
275
-
276
- def run_reaction(reaction:)
277
- reaction.actions.each do |action|
278
- run_action(reaction: reaction, action: action)
279
- end
280
- @share.reaction_base.sleep(name: reaction.name)
281
- end
282
-
283
- def run_action(reaction:, action:)
284
- reaction.updated_at = Time.now.to_nsec_from_epoch
285
- reaction_json = reaction.as_json(:allow_nan => true)
286
- # Let the frontend know which action is being run
287
- # because we can combine commands and scripts with notifications
288
- reaction_json['action'] = action['type']
289
- notification = {
290
- 'kind' => 'run',
291
- 'type' => 'reaction',
292
- 'data' => JSON.generate(reaction_json),
293
- }
294
- AutonomicTopic.write_notification(notification, scope: @scope)
295
-
296
- case action['type']
297
- when 'notify'
298
- run_notify(reaction: reaction, action: action)
299
- when 'command'
300
- run_command(reaction: reaction, action: action)
301
- when 'script'
302
- run_script(reaction: reaction, action: action)
303
- end
304
- end
305
-
306
- def run_notify(reaction:, action:)
307
- message = "ReactionWorker-#{@ident} #{reaction.name} notify action complete, body: #{action['value']}"
308
- url = "/tools/autonomic/reactions"
309
- case action['severity'].to_s.upcase
310
- when 'FATAL'
311
- @logger.fatal(message, url: url, type: Logger::ALERT)
312
- when 'ERROR', 'CRITICAL'
313
- @logger.error(message, url: url, type: Logger::ALERT)
314
- when 'WARN', 'CAUTION', 'SERIOUS'
315
- @logger.warn(message, url: url, type: Logger::NOTIFICATION)
316
- when 'INFO', 'NORMAL', 'STANDBY', 'OFF'
317
- @logger.info(message, url: url, type: Logger::NOTIFICATION)
318
- when 'DEBUG'
319
- level = @logger.level
320
- begin
321
- @logger.level = Logger::DEBUG
322
- @logger.debug(message, url: url, type: Logger::NOTIFICATION)
323
- ensure
324
- @logger.level = level
325
- end
326
- else
327
- raise "Unknown severity: #{action['severity']}"
328
- end
329
- end
330
-
331
- def run_command(reaction:, action:)
332
- begin
333
- username = reaction.username
334
- token = get_token(username)
335
- raise "No token available for username: #{username}" unless token
336
- cmd_no_hazardous_check(action['value'], scope: @scope, token: token)
337
- @logger.info "ReactionWorker-#{@ident} #{reaction.name} command action complete, command: #{action['value']}"
338
- rescue StandardError => e
339
- @logger.error "ReactionWorker-#{@ident} #{reaction.name} command action failed, #{action}\n#{e.message}"
340
- end
341
- end
342
-
343
- def run_script(reaction:, action:)
344
- begin
345
- username = reaction.username
346
- token = get_token(username)
347
- raise "No token available for username: #{username}" unless token
348
- request = Net::HTTP::Post.new(
349
- "/script-api/scripts/#{action['value']}/run?scope=#{@scope}",
350
- 'Content-Type' => 'application/json',
351
- 'Authorization' => token
352
- )
353
- request.body = JSON.generate({
354
- 'scope' => @scope,
355
- 'environment' => action['environment'],
356
- 'reaction' => reaction.name,
357
- 'id' => Time.now.to_i
358
- })
359
- hostname = ENV['OPENC3_SCRIPT_HOSTNAME'] || 'openc3-cosmos-script-runner-api'
360
- response = Net::HTTP.new(hostname, 2902).request(request)
361
- raise "failed to call #{hostname}, for script: #{action['value']}, response code: #{response.code}" if response.code != '200'
362
-
363
- @logger.info "ReactionWorker-#{@ident} #{reaction.name} script action complete, #{action['value']} => #{response.body}"
364
- rescue StandardError => e
365
- @logger.error "ReactionWorker-#{@ident} #{reaction.name} script action failed, #{action}\n#{e.message}"
366
- end
367
- end
368
- end
369
-
370
- # The reaction snooze manager starts a thread pool and keeps track of when a
371
- # reaction is activated and to evaluate triggers when the snooze is complete.
372
- class ReactionSnoozeManager
373
- attr_reader :name, :scope, :share, :thread_pool
374
-
375
- def initialize(name:, logger:, scope:, share:)
376
- @name = name
377
- @logger = logger
378
- @scope = scope
379
- @share = share
380
- @worker_count = 3
381
- @thread_pool = nil
382
- @cancel_thread = false
383
- end
384
-
385
- def generate_thread_pool()
386
- thread_pool = []
387
- @worker_count.times do |i|
388
- worker = ReactionWorker.new(name: @name, logger: @logger, scope: @scope, share: @share, ident: i)
389
- thread_pool << Thread.new { worker.run }
390
- end
391
- return thread_pool
392
- end
393
-
394
- def run
395
- @logger.info "ReactionSnoozeManager running"
396
- @thread_pool = generate_thread_pool()
397
- loop do
398
- begin
399
- current_time = Time.now.to_i
400
- manage_snoozed_reactions(current_time: current_time)
401
- rescue StandardError => e
402
- @logger.error "ReactionSnoozeManager failed to snooze reactions.\n#{e.formatted}"
403
- end
404
- break if @cancel_thread
405
- sleep(1)
406
- break if @cancel_thread
407
- end
408
- @logger.info "ReactionSnoozeManager exiting"
409
- end
410
-
411
- def active_triggers(reaction:)
412
- reaction.triggers.each do |trigger|
413
- t = TriggerModel.get(name: trigger['name'], group: trigger['group'], scope: @scope)
414
- return true if t && t.state
415
- end
416
- return false
417
- end
418
-
419
- def manage_snoozed_reactions(current_time:)
420
- @share.reaction_base.get_snoozed.each do |reaction|
421
- time_difference = reaction.snoozed_until - current_time
422
- if time_difference <= 0 && @share.snooze_base.not_queued?(reaction: reaction)
423
- # LEVEL triggers mean we run if the trigger is active
424
- if reaction.triggerLevel == 'LEVEL' and active_triggers(reaction: reaction)
425
- @share.queue_base.enqueue(kind: 'reaction', data: reaction.as_json(:allow_nan => true))
426
- else
427
- @share.reaction_base.wake(name: reaction.name)
428
- end
429
- end
430
- end
431
- end
432
-
433
- def shutdown
434
- @cancel_thread = true
435
- @worker_count.times do |_i|
436
- @share.queue_base.enqueue(kind: nil, data: nil)
437
- end
438
- end
439
- end
440
-
441
- # The reaction microservice starts a manager then gets the
442
- # reactions and triggers from redis. It then monitors the
443
- # AutonomicTopic for changes.
444
- class ReactionMicroservice < Microservice
445
- attr_reader :name, :scope, :share, :manager, :manager_thread
446
- TOPIC_LOOKUP = {
447
- 'group' => {
448
- 'created' => :no_op,
449
- 'updated' => :no_op,
450
- 'deleted' => :no_op,
451
- },
452
- 'trigger' => {
453
- 'error' => :no_op,
454
- 'created' => :no_op,
455
- 'updated' => :no_op,
456
- 'deleted' => :no_op,
457
- 'enabled' => :no_op,
458
- 'disabled' => :no_op,
459
- 'true' => :trigger_true_event,
460
- 'false' => :no_op,
461
- },
462
- 'reaction' => {
463
- 'run' => :no_op,
464
- 'deployed' => :no_op,
465
- 'undeployed' => :no_op,
466
- 'created' => :reaction_created_event,
467
- 'updated' => :reaction_updated_event,
468
- 'deleted' => :reaction_deleted_event,
469
- 'enabled' => :reaction_enabled_event,
470
- 'disabled' => :reaction_disabled_event,
471
- 'snoozed' => :no_op,
472
- 'awakened' => :no_op,
473
- 'executed' => :reaction_execute_event,
474
- }
475
- }
476
-
477
- def initialize(*args)
478
- # The name is passed in via the reaction_model as "#{scope}__OPENC3__REACTION"
479
- super(*args)
480
- @share = ReactionShare.new(scope: @scope)
481
- @manager = ReactionSnoozeManager.new(name: @name, logger: @logger, scope: @scope, share: @share)
482
- @manager_thread = nil
483
- @read_topic = true
484
- end
485
-
486
- def run
487
- @logger.info "ReactionMicroservice running"
488
- # Let the frontend know that the microservice has been deployed and is running
489
- notification = {
490
- 'kind' => 'deployed',
491
- 'type' => 'reaction',
492
- # name and updated_at fields are required for Event formatting
493
- 'data' => JSON.generate({
494
- 'name' => @name,
495
- 'updated_at' => Time.now.to_nsec_from_epoch,
496
- }),
497
- }
498
- AutonomicTopic.write_notification(notification, scope: @scope)
499
-
500
- @manager_thread = Thread.new { @manager.run }
501
- loop do
502
- reactions = ReactionModel.all(scope: @scope)
503
- @share.reaction_base.setup(reactions: reactions)
504
- break if @cancel_thread
505
- block_for_updates()
506
- break if @cancel_thread
507
- end
508
- @logger.info "ReactionMicroservice exiting"
509
- end
510
-
511
- def block_for_updates
512
- @read_topic = true
513
- while @read_topic && !@cancel_thread
514
- begin
515
- AutonomicTopic.read_topics(@topics) do |_topic, _msg_id, msg_hash, _redis|
516
- @logger.debug "ReactionMicroservice block_for_updates: #{msg_hash.to_s}"
517
- public_send(TOPIC_LOOKUP[msg_hash['type']][msg_hash['kind']], msg_hash)
518
- end
519
- rescue StandardError => e
520
- @logger.error "ReactionMicroservice failed to read topics #{@topics}\n#{e.formatted}"
521
- end
522
- end
523
- end
524
-
525
- def no_op(data)
526
- @logger.debug "ReactionMicroservice web socket event: #{data}"
527
- end
528
-
529
- def reaction_updated_event(msg_hash)
530
- @logger.debug "ReactionMicroservice reaction updated msg_hash: #{msg_hash}"
531
- reaction = JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true)
532
- @share.reaction_base.update(reaction: reaction)
533
- @read_topic = false
534
- end
535
-
536
- def trigger_true_event(msg_hash)
537
- @logger.debug "ReactionMicroservice trigger true msg_hash: #{msg_hash}"
538
- @share.queue_base.enqueue(kind: 'trigger', data: JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true))
539
- end
540
-
541
- # Add the reaction to the shared data.
542
- def reaction_created_event(msg_hash)
543
- @logger.debug "ReactionMicroservice reaction created msg_hash: #{msg_hash}"
544
- reaction = JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true)
545
- @share.reaction_base.add(reaction: reaction)
546
-
547
- # If the reaction triggerLevel is LEVEL we have to check its triggers
548
- # on add because if the trigger is active it should run
549
- if reaction['triggerLevel'] == 'LEVEL'
550
- reaction['triggers'].each do |trigger_hash|
551
- trigger = TriggerModel.get(name: trigger_hash['name'], group: trigger_hash['group'], scope: reaction['scope'])
552
- if trigger && trigger.state
553
- @logger.info "ReactionMicroservice reaction #{reaction['name']} created. Since triggerLevel is 'LEVEL' it was run due to #{trigger.name}."
554
- @share.queue_base.enqueue(kind: 'reaction', data: reaction)
555
- end
556
- end
557
- end
558
- end
559
-
560
- # Update the reaction to the shared data.
561
- def reaction_enabled_event(msg_hash)
562
- @logger.debug "ReactionMicroservice reaction enabled msg_hash: #{msg_hash}"
563
- reaction = JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true)
564
- @share.reaction_base.update(reaction: reaction)
565
-
566
- # If the reaction triggerLevel is LEVEL we have to check its triggers
567
- # on add because if the trigger is active it should run
568
- if reaction['triggerLevel'] == 'LEVEL'
569
- reaction['triggers'].each do |trigger_hash|
570
- trigger = TriggerModel.get(name: trigger_hash['name'], group: trigger_hash['group'], scope: reaction['scope'])
571
- if trigger && trigger.state
572
- @logger.info "ReactionMicroservice reaction #{reaction['name']} enabled. Since triggerLevel is 'LEVEL' it was run due to #{trigger.name}."
573
- @share.queue_base.enqueue(kind: 'reaction', data: reaction)
574
- end
575
- end
576
- end
577
- end
578
-
579
- # Update the reaction to the shared data.
580
- def reaction_disabled_event(msg_hash)
581
- @logger.debug "ReactionMicroservice reaction disabled msg_hash: #{msg_hash}"
582
- @share.reaction_base.update(reaction: JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true))
583
- end
584
-
585
- # Add the reaction to the shared data.
586
- def reaction_execute_event(msg_hash)
587
- @logger.debug "ReactionMicroservice reaction execute msg_hash: #{msg_hash}"
588
- reaction = JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true)
589
- @share.reaction_base.update(reaction: reaction)
590
- @share.queue_base.enqueue(kind: 'reaction', data: reaction)
591
- end
592
-
593
- # Remove the reaction from the shared data
594
- def reaction_deleted_event(msg_hash)
595
- @logger.debug "ReactionMicroservice reaction deleted msg_hash: #{msg_hash}"
596
- @share.reaction_base.remove(reaction: JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true))
597
- end
598
-
599
- def shutdown
600
- @read_topic = false
601
- @manager.shutdown()
602
- super
603
- end
604
- end
605
- end
606
-
607
- OpenC3::ReactionMicroservice.run if __FILE__ == $0