openc3 5.0.6

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 (307) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +18 -0
  3. data/Guardfile +35 -0
  4. data/LICENSE.txt +727 -0
  5. data/README.md +37 -0
  6. data/Rakefile +131 -0
  7. data/bin/cstol_converter +1178 -0
  8. data/bin/openc3cli +531 -0
  9. data/bin/rubysloc +139 -0
  10. data/data/config/_array_params.yaml +23 -0
  11. data/data/config/_id_items.yaml +24 -0
  12. data/data/config/_id_params.yaml +58 -0
  13. data/data/config/_interfaces.yaml +214 -0
  14. data/data/config/_interfaces.yaml.err +1017 -0
  15. data/data/config/_items.yaml +20 -0
  16. data/data/config/_params.yaml +60 -0
  17. data/data/config/cmd_tlm_server.yaml +136 -0
  18. data/data/config/command.yaml +44 -0
  19. data/data/config/command_modifiers.yaml +160 -0
  20. data/data/config/command_telemetry.yaml +3 -0
  21. data/data/config/interface_modifiers.yaml +104 -0
  22. data/data/config/item_modifiers.yaml +221 -0
  23. data/data/config/microservice.yaml +78 -0
  24. data/data/config/param_item_modifiers.yaml +52 -0
  25. data/data/config/parameter_modifiers.yaml +200 -0
  26. data/data/config/plugins.yaml +80 -0
  27. data/data/config/protocols.yaml +290 -0
  28. data/data/config/screen.yaml +147 -0
  29. data/data/config/table_manager.yaml +89 -0
  30. data/data/config/table_parameter_modifiers.yaml +9 -0
  31. data/data/config/target.yaml +142 -0
  32. data/data/config/target_config.yaml +94 -0
  33. data/data/config/telemetry.yaml +87 -0
  34. data/data/config/telemetry_modifiers.yaml +159 -0
  35. data/data/config/tool.yaml +63 -0
  36. data/data/config/unknown.yaml +3 -0
  37. data/data/config/widgets.yaml +1505 -0
  38. data/ext/mkrf_conf.rb +49 -0
  39. data/ext/openc3/ext/array/array.c +122 -0
  40. data/ext/openc3/ext/array/extconf.rb +13 -0
  41. data/ext/openc3/ext/buffered_file/buffered_file.c +198 -0
  42. data/ext/openc3/ext/buffered_file/extconf.rb +13 -0
  43. data/ext/openc3/ext/config_parser/config_parser.c +280 -0
  44. data/ext/openc3/ext/config_parser/extconf.rb +13 -0
  45. data/ext/openc3/ext/crc/crc.c +351 -0
  46. data/ext/openc3/ext/crc/extconf.rb +13 -0
  47. data/ext/openc3/ext/openc3_io/extconf.rb +13 -0
  48. data/ext/openc3/ext/openc3_io/openc3_io.c +158 -0
  49. data/ext/openc3/ext/packet/extconf.rb +13 -0
  50. data/ext/openc3/ext/packet/packet.c +318 -0
  51. data/ext/openc3/ext/platform/extconf.rb +13 -0
  52. data/ext/openc3/ext/platform/platform.c +134 -0
  53. data/ext/openc3/ext/polynomial_conversion/extconf.rb +13 -0
  54. data/ext/openc3/ext/polynomial_conversion/polynomial_conversion.c +79 -0
  55. data/ext/openc3/ext/string/extconf.rb +13 -0
  56. data/ext/openc3/ext/string/string.c +63 -0
  57. data/ext/openc3/ext/structure/structure.c +1719 -0
  58. data/ext/openc3/ext/tabbed_plots_config/extconf.rb +13 -0
  59. data/ext/openc3/ext/tabbed_plots_config/tabbed_plots_config.c +62 -0
  60. data/ext/openc3/ext/telemetry/extconf.rb +13 -0
  61. data/ext/openc3/ext/telemetry/telemetry.c +336 -0
  62. data/lib/cosmos.rb +20 -0
  63. data/lib/cosmosc2.rb +20 -0
  64. data/lib/openc3/api/api.rb +39 -0
  65. data/lib/openc3/api/authorized_api.rb +30 -0
  66. data/lib/openc3/api/cmd_api.rb +451 -0
  67. data/lib/openc3/api/config_api.rb +58 -0
  68. data/lib/openc3/api/interface_api.rb +117 -0
  69. data/lib/openc3/api/limits_api.rb +375 -0
  70. data/lib/openc3/api/router_api.rb +117 -0
  71. data/lib/openc3/api/settings_api.rb +70 -0
  72. data/lib/openc3/api/target_api.rb +78 -0
  73. data/lib/openc3/api/tlm_api.rb +455 -0
  74. data/lib/openc3/bridge/bridge.rb +54 -0
  75. data/lib/openc3/bridge/bridge_config.rb +167 -0
  76. data/lib/openc3/bridge/bridge_interface_thread.rb +42 -0
  77. data/lib/openc3/bridge/bridge_router_thread.rb +42 -0
  78. data/lib/openc3/ccsds/ccsds_packet.rb +68 -0
  79. data/lib/openc3/ccsds/ccsds_parser.rb +148 -0
  80. data/lib/openc3/config/config_parser.rb +549 -0
  81. data/lib/openc3/config/meta_config_parser.rb +74 -0
  82. data/lib/openc3/conversions/conversion.rb +70 -0
  83. data/lib/openc3/conversions/generic_conversion.rb +83 -0
  84. data/lib/openc3/conversions/packet_time_formatted_conversion.rb +43 -0
  85. data/lib/openc3/conversions/packet_time_seconds_conversion.rb +43 -0
  86. data/lib/openc3/conversions/polynomial_conversion.rb +87 -0
  87. data/lib/openc3/conversions/processor_conversion.rb +70 -0
  88. data/lib/openc3/conversions/received_count_conversion.rb +38 -0
  89. data/lib/openc3/conversions/received_time_formatted_conversion.rb +42 -0
  90. data/lib/openc3/conversions/received_time_seconds_conversion.rb +42 -0
  91. data/lib/openc3/conversions/segmented_polynomial_conversion.rb +171 -0
  92. data/lib/openc3/conversions/unix_time_conversion.rb +68 -0
  93. data/lib/openc3/conversions/unix_time_formatted_conversion.rb +49 -0
  94. data/lib/openc3/conversions/unix_time_seconds_conversion.rb +49 -0
  95. data/lib/openc3/conversions.rb +34 -0
  96. data/lib/openc3/core_ext/array.rb +416 -0
  97. data/lib/openc3/core_ext/binding.rb +29 -0
  98. data/lib/openc3/core_ext/class.rb +72 -0
  99. data/lib/openc3/core_ext/exception.rb +61 -0
  100. data/lib/openc3/core_ext/file.rb +83 -0
  101. data/lib/openc3/core_ext/hash.rb +37 -0
  102. data/lib/openc3/core_ext/io.rb +134 -0
  103. data/lib/openc3/core_ext/kernel.rb +42 -0
  104. data/lib/openc3/core_ext/math.rb +128 -0
  105. data/lib/openc3/core_ext/matrix.rb +156 -0
  106. data/lib/openc3/core_ext/objectspace.rb +36 -0
  107. data/lib/openc3/core_ext/openc3_io.rb +57 -0
  108. data/lib/openc3/core_ext/range.rb +27 -0
  109. data/lib/openc3/core_ext/socket.rb +38 -0
  110. data/lib/openc3/core_ext/string.rb +389 -0
  111. data/lib/openc3/core_ext/stringio.rb +33 -0
  112. data/lib/openc3/core_ext/time.rb +508 -0
  113. data/lib/openc3/core_ext.rb +36 -0
  114. data/lib/openc3/interfaces/interface.rb +498 -0
  115. data/lib/openc3/interfaces/linc_interface.rb +475 -0
  116. data/lib/openc3/interfaces/protocols/burst_protocol.rb +192 -0
  117. data/lib/openc3/interfaces/protocols/crc_protocol.rb +193 -0
  118. data/lib/openc3/interfaces/protocols/fixed_protocol.rb +155 -0
  119. data/lib/openc3/interfaces/protocols/ignore_packet_protocol.rb +56 -0
  120. data/lib/openc3/interfaces/protocols/length_protocol.rb +165 -0
  121. data/lib/openc3/interfaces/protocols/override_protocol.rb +60 -0
  122. data/lib/openc3/interfaces/protocols/preidentified_protocol.rb +206 -0
  123. data/lib/openc3/interfaces/protocols/protocol.rb +82 -0
  124. data/lib/openc3/interfaces/protocols/template_protocol.rb +261 -0
  125. data/lib/openc3/interfaces/protocols/terminated_protocol.rb +93 -0
  126. data/lib/openc3/interfaces/serial_interface.rb +94 -0
  127. data/lib/openc3/interfaces/simulated_target_interface.rb +168 -0
  128. data/lib/openc3/interfaces/stream_interface.rb +81 -0
  129. data/lib/openc3/interfaces/tcpip_client_interface.rb +69 -0
  130. data/lib/openc3/interfaces/tcpip_server_interface.rb +629 -0
  131. data/lib/openc3/interfaces/udp_interface.rb +169 -0
  132. data/lib/openc3/interfaces.rb +44 -0
  133. data/lib/openc3/io/buffered_file.rb +109 -0
  134. data/lib/openc3/io/io_multiplexer.rb +80 -0
  135. data/lib/openc3/io/json_api_object.rb +208 -0
  136. data/lib/openc3/io/json_drb.rb +335 -0
  137. data/lib/openc3/io/json_drb_object.rb +114 -0
  138. data/lib/openc3/io/json_drb_rack.rb +84 -0
  139. data/lib/openc3/io/json_rpc.rb +420 -0
  140. data/lib/openc3/io/openc3_snmp.rb +58 -0
  141. data/lib/openc3/io/posix_serial_driver.rb +156 -0
  142. data/lib/openc3/io/raw_logger.rb +167 -0
  143. data/lib/openc3/io/raw_logger_pair.rb +77 -0
  144. data/lib/openc3/io/serial_driver.rb +105 -0
  145. data/lib/openc3/io/stderr.rb +43 -0
  146. data/lib/openc3/io/stdout.rb +43 -0
  147. data/lib/openc3/io/udp_sockets.rb +194 -0
  148. data/lib/openc3/io/win32_serial_driver.rb +196 -0
  149. data/lib/openc3/logs/log_writer.rb +302 -0
  150. data/lib/openc3/logs/packet_log_constants.rb +62 -0
  151. data/lib/openc3/logs/packet_log_reader.rb +345 -0
  152. data/lib/openc3/logs/packet_log_writer.rb +299 -0
  153. data/lib/openc3/logs/text_log_writer.rb +68 -0
  154. data/lib/openc3/logs.rb +25 -0
  155. data/lib/openc3/microservices/cleanup_microservice.rb +68 -0
  156. data/lib/openc3/microservices/decom_microservice.rb +136 -0
  157. data/lib/openc3/microservices/interface_microservice.rb +532 -0
  158. data/lib/openc3/microservices/log_microservice.rb +108 -0
  159. data/lib/openc3/microservices/microservice.rb +204 -0
  160. data/lib/openc3/microservices/plugin_microservice.rb +43 -0
  161. data/lib/openc3/microservices/reaction_microservice.rb +541 -0
  162. data/lib/openc3/microservices/reducer_microservice.rb +313 -0
  163. data/lib/openc3/microservices/router_microservice.rb +44 -0
  164. data/lib/openc3/microservices/text_log_microservice.rb +84 -0
  165. data/lib/openc3/microservices/timeline_microservice.rb +363 -0
  166. data/lib/openc3/microservices/trigger_group_microservice.rb +638 -0
  167. data/lib/openc3/models/activity_model.rb +319 -0
  168. data/lib/openc3/models/auth_model.rb +65 -0
  169. data/lib/openc3/models/cvt_model.rb +185 -0
  170. data/lib/openc3/models/environment_model.rb +58 -0
  171. data/lib/openc3/models/gem_model.rb +137 -0
  172. data/lib/openc3/models/info_model.rb +31 -0
  173. data/lib/openc3/models/interface_model.rb +281 -0
  174. data/lib/openc3/models/interface_status_model.rb +117 -0
  175. data/lib/openc3/models/metadata_model.rb +139 -0
  176. data/lib/openc3/models/metric_model.rb +59 -0
  177. data/lib/openc3/models/microservice_model.rb +206 -0
  178. data/lib/openc3/models/microservice_status_model.rb +74 -0
  179. data/lib/openc3/models/model.rb +204 -0
  180. data/lib/openc3/models/note_model.rb +122 -0
  181. data/lib/openc3/models/notification_model.rb +40 -0
  182. data/lib/openc3/models/ping_model.rb +35 -0
  183. data/lib/openc3/models/plugin_model.rb +292 -0
  184. data/lib/openc3/models/process_status_model.rb +76 -0
  185. data/lib/openc3/models/reaction_model.rb +322 -0
  186. data/lib/openc3/models/reducer_model.rb +65 -0
  187. data/lib/openc3/models/router_model.rb +35 -0
  188. data/lib/openc3/models/router_status_model.rb +27 -0
  189. data/lib/openc3/models/scope_model.rb +153 -0
  190. data/lib/openc3/models/settings_model.rb +55 -0
  191. data/lib/openc3/models/sorted_model.rb +167 -0
  192. data/lib/openc3/models/target_model.rb +759 -0
  193. data/lib/openc3/models/timeline_model.rb +154 -0
  194. data/lib/openc3/models/tool_config_model.rb +38 -0
  195. data/lib/openc3/models/tool_model.rb +262 -0
  196. data/lib/openc3/models/trigger_group_model.rb +186 -0
  197. data/lib/openc3/models/trigger_model.rb +330 -0
  198. data/lib/openc3/models/widget_model.rb +138 -0
  199. data/lib/openc3/operators/microservice_operator.rb +128 -0
  200. data/lib/openc3/operators/operator.rb +277 -0
  201. data/lib/openc3/packets/binary_accessor.rb +1207 -0
  202. data/lib/openc3/packets/commands.rb +373 -0
  203. data/lib/openc3/packets/json_packet.rb +134 -0
  204. data/lib/openc3/packets/limits.rb +271 -0
  205. data/lib/openc3/packets/limits_response.rb +53 -0
  206. data/lib/openc3/packets/packet.rb +1168 -0
  207. data/lib/openc3/packets/packet_config.rb +625 -0
  208. data/lib/openc3/packets/packet_item.rb +586 -0
  209. data/lib/openc3/packets/packet_item_limits.rb +162 -0
  210. data/lib/openc3/packets/parsers/format_string_parser.rb +65 -0
  211. data/lib/openc3/packets/parsers/limits_parser.rb +159 -0
  212. data/lib/openc3/packets/parsers/limits_response_parser.rb +61 -0
  213. data/lib/openc3/packets/parsers/packet_item_parser.rb +272 -0
  214. data/lib/openc3/packets/parsers/packet_parser.rb +134 -0
  215. data/lib/openc3/packets/parsers/processor_parser.rb +73 -0
  216. data/lib/openc3/packets/parsers/state_parser.rb +127 -0
  217. data/lib/openc3/packets/parsers/xtce_converter.rb +442 -0
  218. data/lib/openc3/packets/parsers/xtce_parser.rb +722 -0
  219. data/lib/openc3/packets/structure.rb +553 -0
  220. data/lib/openc3/packets/structure_item.rb +365 -0
  221. data/lib/openc3/packets/telemetry.rb +487 -0
  222. data/lib/openc3/processors/processor.rb +86 -0
  223. data/lib/openc3/processors/statistics_processor.rb +82 -0
  224. data/lib/openc3/processors/watermark_processor.rb +58 -0
  225. data/lib/openc3/processors.rb +24 -0
  226. data/lib/openc3/script/api_shared.rb +828 -0
  227. data/lib/openc3/script/calendar.rb +89 -0
  228. data/lib/openc3/script/commands.rb +227 -0
  229. data/lib/openc3/script/exceptions.rb +29 -0
  230. data/lib/openc3/script/extract.rb +161 -0
  231. data/lib/openc3/script/limits.rb +60 -0
  232. data/lib/openc3/script/script.rb +299 -0
  233. data/lib/openc3/script/script_runner.rb +238 -0
  234. data/lib/openc3/script/storage.rb +146 -0
  235. data/lib/openc3/script/suite.rb +542 -0
  236. data/lib/openc3/script/suite_results.rb +196 -0
  237. data/lib/openc3/script/suite_runner.rb +217 -0
  238. data/lib/openc3/script.rb +21 -0
  239. data/lib/openc3/streams/serial_stream.rb +167 -0
  240. data/lib/openc3/streams/stream.rb +63 -0
  241. data/lib/openc3/streams/tcpip_client_stream.rb +116 -0
  242. data/lib/openc3/streams/tcpip_socket_stream.rb +195 -0
  243. data/lib/openc3/system/system.rb +127 -0
  244. data/lib/openc3/system/system_config.rb +411 -0
  245. data/lib/openc3/system/target.rb +269 -0
  246. data/lib/openc3/system.rb +24 -0
  247. data/lib/openc3/tools/cmd_tlm_server/api.rb +20 -0
  248. data/lib/openc3/tools/cmd_tlm_server/cmd_tlm_server_config.rb +320 -0
  249. data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +294 -0
  250. data/lib/openc3/tools/table_manager/table.rb +77 -0
  251. data/lib/openc3/tools/table_manager/table_config.rb +273 -0
  252. data/lib/openc3/tools/table_manager/table_item.rb +90 -0
  253. data/lib/openc3/tools/table_manager/table_item_parser.rb +66 -0
  254. data/lib/openc3/tools/table_manager/table_manager_core.rb +333 -0
  255. data/lib/openc3/tools/table_manager/table_parser.rb +93 -0
  256. data/lib/openc3/tools/test_runner/test.rb +67 -0
  257. data/lib/openc3/top_level.rb +595 -0
  258. data/lib/openc3/topics/autonomic_topic.rb +52 -0
  259. data/lib/openc3/topics/calendar_topic.rb +44 -0
  260. data/lib/openc3/topics/command_decom_topic.rb +76 -0
  261. data/lib/openc3/topics/command_topic.rb +83 -0
  262. data/lib/openc3/topics/config_topic.rb +68 -0
  263. data/lib/openc3/topics/interface_topic.rb +73 -0
  264. data/lib/openc3/topics/limits_event_topic.rb +109 -0
  265. data/lib/openc3/topics/notifications_topic.rb +28 -0
  266. data/lib/openc3/topics/router_topic.rb +85 -0
  267. data/lib/openc3/topics/telemetry_decom_topic.rb +54 -0
  268. data/lib/openc3/topics/telemetry_topic.rb +36 -0
  269. data/lib/openc3/topics/timeline_topic.rb +45 -0
  270. data/lib/openc3/topics/topic.rb +53 -0
  271. data/lib/openc3/utilities/authentication.rb +141 -0
  272. data/lib/openc3/utilities/authorization.rb +51 -0
  273. data/lib/openc3/utilities/crc.rb +278 -0
  274. data/lib/openc3/utilities/csv.rb +153 -0
  275. data/lib/openc3/utilities/logger.rb +187 -0
  276. data/lib/openc3/utilities/message_log.rb +91 -0
  277. data/lib/openc3/utilities/metric.rb +141 -0
  278. data/lib/openc3/utilities/process_manager.rb +139 -0
  279. data/lib/openc3/utilities/quaternion.rb +257 -0
  280. data/lib/openc3/utilities/ruby_lex_utils.rb +568 -0
  281. data/lib/openc3/utilities/s3.rb +202 -0
  282. data/lib/openc3/utilities/s3_autoload.rb +9 -0
  283. data/lib/openc3/utilities/s3_file_cache.rb +274 -0
  284. data/lib/openc3/utilities/simulated_target.rb +117 -0
  285. data/lib/openc3/utilities/sleeper.rb +51 -0
  286. data/lib/openc3/utilities/store.rb +23 -0
  287. data/lib/openc3/utilities/store_autoload.rb +237 -0
  288. data/lib/openc3/utilities/zip.rb +21 -0
  289. data/lib/openc3/utilities.rb +35 -0
  290. data/lib/openc3/version.rb +14 -0
  291. data/lib/openc3/win32/excel.rb +132 -0
  292. data/lib/openc3/win32/win32.rb +402 -0
  293. data/lib/openc3/win32/win32_main.rb +333 -0
  294. data/lib/openc3.rb +49 -0
  295. data/tasks/gemfile_stats.rake +113 -0
  296. data/tasks/spec.rake +30 -0
  297. data/templates/plugin-template/README.md +15 -0
  298. data/templates/plugin-template/Rakefile +12 -0
  299. data/templates/plugin-template/plugin.gemspec +23 -0
  300. data/templates/plugin-template/plugin.txt +9 -0
  301. data/templates/plugin-template/targets/TARGET/cmd_tlm/cmd.txt +8 -0
  302. data/templates/plugin-template/targets/TARGET/cmd_tlm/tlm.txt +8 -0
  303. data/templates/plugin-template/targets/TARGET/lib/target.rb +10 -0
  304. data/templates/plugin-template/targets/TARGET/procedures/procedure.rb +3 -0
  305. data/templates/plugin-template/targets/TARGET/screens/status.txt +9 -0
  306. data/templates/plugin-template/targets/TARGET/target.txt +5 -0
  307. metadata +849 -0
@@ -0,0 +1,541 @@
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
+ require 'openc3/microservices/microservice'
21
+ require 'openc3/models/reaction_model'
22
+ require 'openc3/models/notification_model'
23
+ require 'openc3/models/trigger_model'
24
+ require 'openc3/topics/autonomic_topic'
25
+ require 'openc3/utilities/authentication'
26
+
27
+ require 'openc3/script'
28
+
29
+ module OpenC3
30
+
31
+ # This should remain a thread safe implamentation. This is the in memory
32
+ # cache that should mirror the database. This will update two hash
33
+ # variables and will track triggers to lookup what triggers link to what
34
+ # reactions.
35
+ class ReactionBase
36
+
37
+ def initialize(scope:)
38
+ @scope = scope
39
+ @reactions_mutex = Mutex.new
40
+ @reactions = Hash.new
41
+ @lookup_mutex = Mutex.new
42
+ @lookup = Hash.new
43
+ end
44
+
45
+ # RETURNS an Array of active and not snoozed reactions
46
+ def get_snoozed
47
+ data = nil
48
+ @reactions_mutex.synchronize do
49
+ data = Marshal.load( Marshal.dump(@reactions) )
50
+ end
51
+ ret = Array.new
52
+ return ret unless data
53
+ data.each do | _name, r_hash |
54
+ data = Marshal.load( Marshal.dump(r_hash) )
55
+ reaction = ReactionModel.from_json(data, name: data['name'], scope: data['scope'])
56
+ ret << reaction if reaction.active && reaction.snoozed_until
57
+ end
58
+ return ret
59
+ end
60
+
61
+ # RETURNS an Array of active and not snoozed reactions
62
+ def get_reactions(trigger_name:)
63
+ array_value = nil
64
+ @lookup_mutex.synchronize do
65
+ array_value = Marshal.load( Marshal.dump(@lookup[trigger_name]) )
66
+ end
67
+ ret = Array.new
68
+ return ret unless array_value
69
+ array_value.each do | name |
70
+ @reactions_mutex.synchronize do
71
+ data = Marshal.load( Marshal.dump(@reactions[name]) )
72
+ reaction = ReactionModel.from_json(data, name: data['name'], scope: data['scope'])
73
+ ret << reaction if reaction.active && reaction.snoozed_until.nil?
74
+ end
75
+ end
76
+ return ret
77
+ end
78
+
79
+ # Update the memeory database with a HASH of reactions from the external
80
+ # database
81
+ def setup(reactions:)
82
+ @reactions_mutex.synchronize do
83
+ @reactions = Marshal.load( Marshal.dump(reactions) )
84
+ end
85
+ @lookup_mutex.synchronize do
86
+ @lookup = Hash.new
87
+ reactions.each do | reaction_name, reaction |
88
+ reaction['triggers'].each do | trigger |
89
+ trigger_name = trigger['name']
90
+ if @lookup[trigger_name].nil?
91
+ @lookup[trigger_name] = [reaction_name]
92
+ else
93
+ @lookup[trigger_name] << reaction_name
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ # Pulls the latest reaction name from the in memory database to see
101
+ # if the reaction should be put to sleep.
102
+ def sleep(name:)
103
+ @reactions_mutex.synchronize do
104
+ data = Marshal.load( Marshal.dump(@reactions[name]) )
105
+ return unless data
106
+ reaction = ReactionModel.from_json(data, name: data['name'], scope: data['scope'])
107
+ if reaction.snoozed_until.nil? || Time.now.to_i >= reaction.snoozed_until
108
+ reaction.sleep()
109
+ end
110
+ @reactions[name] = reaction.as_json(:allow_nan => true)
111
+ end
112
+ end
113
+
114
+ # Pulls the latest reaction name from the in memory database to see
115
+ # if the reaction should be awaken.
116
+ def wake(name:)
117
+ @reactions_mutex.synchronize do
118
+ data = Marshal.load( Marshal.dump(@reactions[name]) )
119
+ return unless data
120
+ reaction = ReactionModel.from_json(data, name: data['name'], scope: data['scope'])
121
+ reaction.awaken()
122
+ @reactions[name] = reaction.as_json(:allow_nan => true)
123
+ end
124
+ end
125
+
126
+ # Add a reaction to the in memory database
127
+ def add(reaction:)
128
+ reaction_name = reaction['name']
129
+ @reactions_mutex.synchronize do
130
+ @reactions[reaction_name] = reaction
131
+ end
132
+ reaction['triggers'].each do | trigger |
133
+ trigger_name = trigger['name']
134
+ @lookup_mutex.synchronize do
135
+ if @lookup[trigger_name].nil?
136
+ @lookup[trigger_name] = [reaction_name]
137
+ else
138
+ @lookup[trigger_name] << reaction_name
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ # Updates a reaction to the in memory database. This current does not
145
+ # update the lookup Hash for the triggers.
146
+ def update(reaction:)
147
+ reaction_name = reaction['name']
148
+ @reactions_mutex.synchronize do
149
+ @reactions[reaction_name] = reaction
150
+ end
151
+ end
152
+
153
+ # Removes a reaction to the in memory database.
154
+ def remove(reaction:)
155
+ reaction_name = reaction['name']
156
+ @reactions_mutex.synchronize do
157
+ @reactions.delete(reaction_name)
158
+ end
159
+ reaction['triggers'].each do | trigger |
160
+ trigger_name = trigger['name']
161
+ @lookup_mutex.synchronize do
162
+ @lookup[trigger_name].delete(reaction_name)
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ # This should remain a thread safe implamentation.
169
+ class QueueBase
170
+
171
+ attr_reader :queue
172
+
173
+ def initialize(scope:)
174
+ @queue = Queue.new
175
+ end
176
+
177
+ def enqueue(kind:, data:)
178
+ @queue << [kind, data]
179
+ end
180
+ end
181
+
182
+ # This should remain a thread safe implamentation.
183
+ class SnoozeBase
184
+
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
+
208
+ attr_reader :reaction_base, :queue_base, :snooze_base
209
+
210
+ def initialize(scope:)
211
+ @reaction_base = ReactionBase.new(scope: scope)
212
+ @queue_base = QueueBase.new(scope: scope)
213
+ @snooze_base = SnoozeBase.new(scope: scope)
214
+ end
215
+
216
+ end
217
+
218
+ # The Reaction worker is a very simple thread pool worker. Once the manager
219
+ # queues a trigger to evaluate against the reactions. The worker will check
220
+ # the reactions to see if it needs to fire any reactions.
221
+ class ReactionWorker
222
+ REACTION_METRIC_NAME = 'reaction_duration_seconds'.freeze
223
+
224
+ attr_reader :name, :scope, :share
225
+
226
+ def initialize(name:, scope:, share:, ident:)
227
+ @name = name
228
+ @scope = scope
229
+ @share = share
230
+ @ident = ident
231
+ @metric_output_time = 0
232
+ @metric = Metric.new(microservice: @name, scope: @scope)
233
+ @authentication = generate_auth()
234
+ end
235
+
236
+ # generate the auth object
237
+ def generate_auth
238
+ if ENV['OPENC3_API_USER'].nil? || ENV['OPENC3_API_CLIENT'].nil?
239
+ return OpenC3Authentication.new()
240
+ else
241
+ return OpenC3KeycloakAuthentication.new(ENV['OPENC3_KEYCLOAK_URL'])
242
+ end
243
+ end
244
+
245
+ def reaction(data:)
246
+ return ReactionModel.from_json(data, name: data['name'], scope: data['scope'])
247
+ end
248
+
249
+ def run
250
+ Logger.info "ReactionWorker-#{@ident} running"
251
+ loop do
252
+ begin
253
+ kind, data = @share.queue_base.queue.pop
254
+ break if kind.nil? || data.nil?
255
+ case kind
256
+ when 'reaction'
257
+ run_reaction(reaction: reaction(data: data))
258
+ when 'trigger'
259
+ process_enabled_trigger(data: data)
260
+ end
261
+ current_time = Time.now.to_i
262
+ if @metric_output_time < current_time
263
+ @metric.output
264
+ @metric_output_time = current_time + 120
265
+ end
266
+ rescue StandardError => e
267
+ Logger.error "ReactionWorker-#{@ident} failed to evaluate kind: #{kind} data: #{data}\n#{e.formatted}"
268
+ end
269
+ end
270
+ Logger.info "ReactionWorker-#{@ident} exiting"
271
+ end
272
+
273
+ def process_enabled_trigger(data:)
274
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
275
+ @share.reaction_base.get_reactions(trigger_name: data['name']).each do | reaction |
276
+ run_reaction(reaction: reaction)
277
+ end
278
+ diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
279
+ metric_labels = { 'type' => 'trigger', 'thread' => "worker-#{@ident}" }
280
+ @metric.add_sample(name: REACTION_METRIC_NAME, value: diff, labels: metric_labels)
281
+ end
282
+
283
+ def run_reaction(reaction:)
284
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
285
+ reaction.actions.each do |action|
286
+ run_action(reaction: reaction, action: action)
287
+ end
288
+ @share.reaction_base.sleep(name: reaction.name)
289
+ diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
290
+ metric_labels = { 'type' => 'reaction', 'thread' => "worker-#{@ident}" }
291
+ @metric.add_sample(name: REACTION_METRIC_NAME, value: diff, labels: metric_labels)
292
+ end
293
+
294
+ def run_action(reaction:, action:)
295
+ case action['type']
296
+ when 'command'
297
+ run_command(reaction: reaction, action: action)
298
+ when 'script'
299
+ run_script(reaction: reaction, action: action)
300
+ end
301
+ end
302
+
303
+ def run_command(reaction:, action:)
304
+ Logger.debug "ReactionWorker-#{@ident} running reaction #{reaction.name}, command: '#{action['value']}' "
305
+ begin
306
+ cmd_no_hazardous_check(action['value'], scope: @scope)
307
+ Logger.info "ReactionWorker-#{@ident} #{reaction.name} command action complete, #{action['value']}"
308
+ rescue StandardError => e
309
+ Logger.error "ReactionWorker-#{@ident} #{reaction.name} command action failed, #{action}\n#{e.message}"
310
+ end
311
+ end
312
+
313
+ def run_script(reaction:, action:)
314
+ Logger.debug "ReactionWorker-#{@ident} running reaction #{reaction.name}, script: '#{action['value']}'"
315
+ begin
316
+ request = Net::HTTP::Post.new(
317
+ "/script-api/scripts/#{action['value']}/run?scope=#{@scope}",
318
+ 'Content-Type' => 'application/json',
319
+ 'Authorization' => @authentication.token()
320
+ )
321
+ request.body = JSON.generate({
322
+ 'scope' => @scope,
323
+ 'environment' => action['environment'],
324
+ 'reaction' => reaction.name,
325
+ 'id' => Time.now.to_i
326
+ })
327
+ hostname = ENV['OPENC3_SCRIPT_HOSTNAME'] || 'openc3-script-runner-api'
328
+ response = Net::HTTP.new(hostname, 2902).request(request)
329
+ raise "failed to call #{hostname}, for script: #{action['value']}, response code: #{response.code}" if response.code != '200'
330
+
331
+ Logger.info "ReactionWorker-#{@ident} #{reaction.name} script action complete, #{action['value']} => #{response.body}"
332
+ rescue StandardError => e
333
+ Logger.error "ReactionWorker-#{@ident} #{reaction.name} script action failed, #{action}\n#{e.message}"
334
+ end
335
+ end
336
+ end
337
+
338
+ # The reaction snooze manager starts a thread pool and keeps track of when a
339
+ # reaction is activated and to evalute triggers when the snooze is complete.
340
+ class ReactionSnoozeManager
341
+ SNOOZE_METRIC_NAME = 'snooze_manager_duration_seconds'.freeze
342
+
343
+ attr_reader :name, :scope, :share, :thread_pool
344
+
345
+ def initialize(name:, scope:, share:)
346
+ @name = name
347
+ @scope = scope
348
+ @share = share
349
+ @worker_count = 3
350
+ @thread_pool = nil
351
+ @cancel_thread = false
352
+ @metric = Metric.new(microservice: @name, scope: @scope)
353
+ @metric_output_time = 0
354
+ end
355
+
356
+ def generate_thread_pool()
357
+ thread_pool = []
358
+ @worker_count.times do | i |
359
+ worker = ReactionWorker.new(name: @name, scope: @scope, share: @share, ident: i)
360
+ thread_pool << Thread.new { worker.run }
361
+ end
362
+ return thread_pool
363
+ end
364
+
365
+ def run
366
+ Logger.info "ReactionSnoozeManager running"
367
+ @thread_pool = generate_thread_pool()
368
+ loop do
369
+ begin
370
+ current_time = Time.now.to_i
371
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
372
+ manage_snoozed_reactions(current_time: current_time)
373
+ diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
374
+ metric_labels = { 'type' => 'snooze', 'thread' => "manager" }
375
+ @metric.add_sample(name: SNOOZE_METRIC_NAME, value: diff, labels: metric_labels)
376
+ if @metric_output_time < current_time
377
+ @metric.output
378
+ @metric_output_time = current_time + 120
379
+ end
380
+ rescue StandardError => e
381
+ Logger.error "ReactionSnoozeManager failed to snooze reactions.\n#{e.formatted}"
382
+ end
383
+ break if @cancel_thread
384
+ sleep(1)
385
+ break if @cancel_thread
386
+ end
387
+ Logger.info "ReactionSnoozeManager exiting"
388
+ end
389
+
390
+ def active_triggers(reaction:)
391
+ reaction.triggers.each do | trigger |
392
+ t = TriggerModel.get(name: trigger['name'], group: trigger['group'], scope: @scope)
393
+ return true if t && t.state
394
+ end
395
+ return false
396
+ end
397
+
398
+ def manage_snoozed_reactions(current_time:)
399
+ @share.reaction_base.get_snoozed.each do | reaction |
400
+ time_difference = reaction.snoozed_until - current_time
401
+ if time_difference <= 0 && @share.snooze_base.not_queued?(reaction: reaction)
402
+ Logger.info "#{reaction.name} current: #{current_time}, vs #{reaction.snoozed_until}, #{time_difference}"
403
+ unless reaction.review
404
+ Logger.debug "#{reaction.name} review set to false, setting snoozed_until back to nil"
405
+ @share.reaction_base.wake(name: reaction.name)
406
+ next
407
+ end
408
+ if active_triggers(reaction: reaction)
409
+ @share.queue_base.enqueue(kind: 'reaction', data: reaction.as_json(:allow_nan => true))
410
+ else
411
+ @share.reaction_base.wake(name: reaction.name)
412
+ end
413
+ end
414
+ end
415
+ end
416
+
417
+ def shutdown
418
+ @cancel_thread = true
419
+ @worker_count.times do | i |
420
+ @share.queue_base.enqueue(kind: nil, data: nil)
421
+ end
422
+ end
423
+ end
424
+
425
+ # The reaction microservice starts a manager then gets the
426
+ # reactions and triggers from redis. It then monitors the
427
+ # AutonomicTopic for changes.
428
+ class ReactionMicroservice < Microservice
429
+ ACTION_METRIC_NAME = 'reactions_duration_seconds'.freeze
430
+
431
+ attr_reader :name, :scope, :share, :manager, :manager_thread
432
+
433
+ def initialize(*args)
434
+ super(*args)
435
+ @share = ReactionShare.new(scope: @scope)
436
+ @manager = ReactionSnoozeManager.new(name: @name, scope: @scope, share: @share)
437
+ @manager_thread = nil
438
+ @read_topic = true
439
+ end
440
+
441
+ def run
442
+ Logger.info "ReactionMicroservice running"
443
+ @manager_thread = Thread.new { @manager.run }
444
+ loop do
445
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
446
+ reactions = ReactionModel.all(scope: @scope)
447
+ @share.reaction_base.setup(reactions: reactions)
448
+ diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
449
+ @metric.add_sample(name: ACTION_METRIC_NAME, value: diff, labels: { 'thread' => 'microservice' })
450
+ break if @cancel_thread
451
+
452
+ block_for_updates()
453
+ break if @cancel_thread
454
+ end
455
+ Logger.info "ReactionMicroservice exiting"
456
+ end
457
+
458
+ def topic_lookup_functions
459
+ return {
460
+ 'group' => {
461
+ 'created' => :no_op,
462
+ 'updated' => :no_op,
463
+ 'deleted' => :no_op,
464
+ },
465
+ 'trigger' => {
466
+ 'created' => :no_op,
467
+ 'updated' => :no_op,
468
+ 'deleted' => :no_op,
469
+ 'enabled' => :trigger_enabled_event,
470
+ 'disabled' => :no_op,
471
+ 'activated' => :no_op,
472
+ 'deactivated' => :no_op,
473
+ },
474
+ 'reaction' => {
475
+ 'created' => :reaction_created_event,
476
+ 'updated' => :refresh_event,
477
+ 'deleted' => :reaction_deleted_event,
478
+ 'sleep' => :no_op,
479
+ 'awaken' => :no_op,
480
+ 'activated' => :reaction_updated_event,
481
+ 'deactivated' => :reaction_updated_event,
482
+ }
483
+ }
484
+ end
485
+
486
+ def block_for_updates
487
+ @read_topic = true
488
+ while @read_topic
489
+ begin
490
+ AutonomicTopic.read_topics(@topics) do |_topic, _msg_id, msg_hash, _redis|
491
+ Logger.debug "ReactionMicroservice block_for_updates: #{msg_hash.to_s}"
492
+ public_send(topic_lookup_functions[msg_hash['type']][msg_hash['kind']], msg_hash)
493
+ end
494
+ rescue StandardError => e
495
+ Logger.error "ReactionMicroservice failed to read topics #{@topics}\n#{e.formatted}"
496
+ end
497
+ end
498
+ end
499
+
500
+ def no_op(data)
501
+ Logger.debug "ReactionMicroservice web socket event: #{data}"
502
+ end
503
+
504
+ def refresh_event(data)
505
+ Logger.debug "ReactionMicroservice web socket schedule refresh: #{data}"
506
+ @read_topic = false
507
+ end
508
+
509
+ #
510
+ def trigger_enabled_event(msg_hash)
511
+ Logger.debug "ReactionMicroservice trigger event msg_hash: #{msg_hash}"
512
+ @share.queue_base.enqueue(kind: 'trigger', data: JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true))
513
+ end
514
+
515
+ # Add the reaction to the shared data.
516
+ def reaction_created_event(msg_hash)
517
+ Logger.debug "ReactionMicroservice reaction created msg_hash: #{msg_hash}"
518
+ @share.reaction_base.add(reaction: JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true))
519
+ end
520
+
521
+ # Update the reaction to the shared data.
522
+ def reaction_updated_event(msg_hash)
523
+ Logger.debug "ReactionMicroservice reaction updated msg_hash: #{msg_hash}"
524
+ @share.reaction_base.update(reaction: JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true))
525
+ end
526
+
527
+ # Remove the reaction from the shared data
528
+ def reaction_deleted_event(msg_hash)
529
+ Logger.debug "ReactionMicroservice reaction deleted msg_hash: #{msg_hash}"
530
+ @share.reaction_base.remove(reaction: JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true))
531
+ end
532
+
533
+ def shutdown
534
+ @read_topic = false
535
+ @manager.shutdown()
536
+ super
537
+ end
538
+ end
539
+ end
540
+
541
+ OpenC3::ReactionMicroservice.run if __FILE__ == $0