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,638 @@
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/notification_model'
22
+ require 'openc3/models/trigger_model'
23
+ require 'openc3/topics/autonomic_topic'
24
+ require 'openc3/utilities/authentication'
25
+
26
+ require 'openc3/script'
27
+
28
+ module OpenC3
29
+
30
+ class TriggerLoopError < TriggerError; end
31
+
32
+ # Stored in the TriggerGroupShare this should be a thread safe
33
+ # hash that triggers will be added, updated, and removed from
34
+ class PacketBase
35
+
36
+ def initialize(scope:)
37
+ @scope = scope
38
+ @mutex = Mutex.new
39
+ @packets = Hash.new
40
+ end
41
+
42
+ # ["#{@scope}__DECOM__{#{@target}}__#{@packet}"]
43
+ def packet(target:, packet:)
44
+ topic = "#{@scope}__DECOM__{#{target}}__#{packet}"
45
+ @mutex.synchronize do
46
+ return Marshal.load( Marshal.dump(@packets[topic]) )
47
+ end
48
+ end
49
+
50
+ def get(topic:)
51
+ @mutex.synchronize do
52
+ return Marshal.load( Marshal.dump(@packets[topic]) )
53
+ end
54
+ end
55
+
56
+ def add(topic:, packet:)
57
+ @mutex.synchronize do
58
+ @packets[topic] = packet
59
+ end
60
+ end
61
+
62
+ def remove(topic:)
63
+ @mutex.synchronize do
64
+ @packets.delete(topic)
65
+ end
66
+ end
67
+ end
68
+
69
+ # Stored in the TriggerGroupShare this should be a thread safe
70
+ # hash that triggers will be added, updated, and removed from.
71
+ class TriggerBase
72
+
73
+ attr_reader :autonomic_topic
74
+
75
+ def initialize(scope:)
76
+ @scope = scope
77
+ @autonomic_topic = "#{@scope}__openc3_autonomic".freeze
78
+ @triggers_mutex = Mutex.new
79
+ @triggers = Hash.new
80
+ @lookup_mutex = Mutex.new
81
+ @lookup = Hash.new
82
+ end
83
+
84
+ # Get triggers to evaluate based on the topic. IF the
85
+ # topic is the equal to the autonomic topic it will
86
+ # return only triggers with roots
87
+ def get_triggers(topic:)
88
+ if @autonomic_topic == topic
89
+ return triggers_with_roots()
90
+ else
91
+ return triggers_from(topic: topic)
92
+ end
93
+ end
94
+
95
+ # update trigger state after evaluated
96
+ # -1 (the value is considered an error used to disable the trigger)
97
+ # 0 (the value is considered as a false value)
98
+ # 1 (the value is considered as a true value)
99
+ def update_state(name:, value:)
100
+ @triggers_mutex.synchronize do
101
+ data = @triggers[name]
102
+ return unless data
103
+ trigger = TriggerModel.from_json(data, name: data['name'], scope: data['scope'])
104
+ if value == -1 && trigger.active
105
+ trigger.deactivate()
106
+ elsif value == 1 && trigger.state == false
107
+ trigger.enable()
108
+ elsif value == 0 && trigger.state == true
109
+ trigger.disable()
110
+ end
111
+ @triggers[name] = trigger.as_json(:allow_nan => true)
112
+ end
113
+ end
114
+
115
+ # returns a Hash of ALL active Trigger objects
116
+ def triggers
117
+ val = nil
118
+ @triggers_mutex.synchronize do
119
+ val = Marshal.load( Marshal.dump(@triggers) )
120
+ end
121
+ ret = Hash.new
122
+ val.each do | name, data |
123
+ trigger = TriggerModel.from_json(data, name: data['name'], scope: data['scope'])
124
+ ret[name] = trigger if trigger.active
125
+ end
126
+ return ret
127
+ end
128
+
129
+ # returns an Array of active Trigger objects that have roots to other triggers
130
+ def triggers_with_roots
131
+ val = nil
132
+ @triggers_mutex.synchronize do
133
+ val = Marshal.load( Marshal.dump(@triggers) )
134
+ end
135
+ ret = []
136
+ val.each do | _name, data |
137
+ trigger = TriggerModel.from_json(data, name: data['name'], scope: data['scope'])
138
+ ret << trigger if trigger.active && ! trigger.roots.empty?
139
+ end
140
+ return ret
141
+ end
142
+
143
+ # returns an Array of active Trigger objects that use a topic
144
+ def triggers_from(topic:)
145
+ val = nil
146
+ @lookup_mutex.synchronize do
147
+ val = Marshal.load( Marshal.dump(@lookup[topic]) )
148
+ end
149
+ return [] if val.nil?
150
+ ret = []
151
+ @triggers_mutex.synchronize do
152
+ val.each do | trigger_name, _v |
153
+ data = Marshal.load( Marshal.dump(@triggers[trigger_name]) )
154
+ trigger = TriggerModel.from_json(data, name: data['name'], scope: data['scope'])
155
+ ret << trigger if trigger.active
156
+ end
157
+ end
158
+ return ret
159
+ end
160
+
161
+ # get all topics group is working with
162
+ def topics
163
+ @lookup_mutex.synchronize do
164
+ return Marshal.load( Marshal.dump(@lookup.keys()) )
165
+ end
166
+ end
167
+
168
+ # database update of all triggers in the group
169
+ def update(triggers:)
170
+ @triggers_mutex.synchronize do
171
+ @triggers = Marshal.load( Marshal.dump(triggers) )
172
+ end
173
+ @lookup_mutex.synchronize do
174
+ @lookup = {@autonomic_topic => {}}
175
+ triggers.each do | _name, data |
176
+ trigger = TriggerModel.from_json(data, name: data['name'], scope: data['scope'])
177
+ trigger.generate_topics.each do | topic |
178
+ if @lookup[topic].nil?
179
+ @lookup[topic] = { trigger.name => 1 }
180
+ else
181
+ @lookup[topic][trigger.name] = 1
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ # add a trigger from TriggerBase
189
+ def add(trigger:)
190
+ @triggers_mutex.synchronize do
191
+ @triggers[trigger['name']] = Marshal.load( Marshal.dump(trigger) )
192
+ end
193
+ t = TriggerModel.from_json(trigger, name: trigger['name'], scope: trigger['scope'])
194
+ @lookup_mutex.synchronize do
195
+ t.generate_topics.each do | topic |
196
+ if @lookup[topic].nil?
197
+ @lookup[topic] = { t.name => 1 }
198
+ else
199
+ @lookup[topic][t.name] = 1
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ # remove a trigger from TriggerBase
206
+ def remove(trigger:)
207
+ @triggers_mutex.synchronize do
208
+ @triggers.delete(trigger['name'])
209
+ end
210
+ t = TriggerModel.from_json(trigger, name: trigger['name'], scope: trigger['scope'])
211
+ @lookup_mutex.synchronize do
212
+ t.generate_topics.each do | topic |
213
+ unless @lookup[topic].nil?
214
+ @lookup[topic].delete(t.name)
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ # Shared between the monitor thread and the manager thread to
222
+ # share the triggers. This should remain a thread
223
+ # safe implamentation.
224
+ class TriggerGroupShare
225
+
226
+ def self.get_group(name:)
227
+ return name.split('__')[2]
228
+ end
229
+
230
+ attr_reader :trigger_base, :packet_base
231
+
232
+ def initialize(scope:)
233
+ @scope = scope
234
+ @trigger_base = TriggerBase.new(scope: scope)
235
+ @packet_base = PacketBase.new(scope: scope)
236
+ end
237
+ end
238
+
239
+ # The TriggerGroupWorker is a very simple thread pool worker. Once
240
+ # the trigger manager has pushed a packet to the queue one of
241
+ # these workers will evaluate the triggers in the kit and
242
+ # evaluate triggers for that packet.
243
+ class TriggerGroupWorker
244
+ TRIGGER_METRIC_NAME = 'trigger_eval_duration_seconds'.freeze
245
+
246
+ TYPE = 'type'.freeze
247
+ ITEM_RAW = 'raw'.freeze
248
+ ITEM_TARGET = 'target'.freeze
249
+ ITEM_PACKET = 'packet'.freeze
250
+ ITEM_TYPE = 'item'.freeze
251
+ FLOAT_TYPE = 'float'.freeze
252
+ STRING_TYPE = 'string'.freeze
253
+ LIMIT_TYPE = 'limit'.freeze
254
+ TRIGGER_TYPE = 'trigger'.freeze
255
+
256
+ attr_reader :name, :scope, :target, :packet, :group
257
+
258
+ def initialize(name:, scope:, group:, queue:, share:, ident:)
259
+ @name = name
260
+ @scope = scope
261
+ @group = group
262
+ @queue = queue
263
+ @share = share
264
+ @ident = ident
265
+ @metric = Metric.new(microservice: @name, scope: @scope)
266
+ @metric_output_time = 0
267
+ end
268
+
269
+ def run
270
+ Logger.info "TriggerGroupWorker-#{@ident} running"
271
+ loop do
272
+ topic = @queue.pop
273
+ break if topic.nil?
274
+ begin
275
+ evaluate_wrapper(topic: topic)
276
+ current_time = Time.now.to_i
277
+ if @metric_output_time < current_time
278
+ @metric.output
279
+ @metric_output_time = current_time + 120
280
+ end
281
+ rescue StandardError => e
282
+ Logger.error "TriggerGroupWorker-#{@ident} failed to evaluate data packet from topic: #{topic}\n#{e.formatted}"
283
+ end
284
+ end
285
+ Logger.info "TriggerGroupWorker-#{@ident} exiting"
286
+ end
287
+
288
+ # time how long each packet takes to eval and produce a metric to public
289
+ def evaluate_wrapper(topic:)
290
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
291
+ evaluate_data_packet(topic: topic, triggers: @share.trigger_base.triggers)
292
+ diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
293
+ metric_labels = { 'trigger_group' => @group, 'thread' => "worker-#{@ident}" }
294
+ @metric.add_sample(name: TRIGGER_METRIC_NAME, value: diff, labels: metric_labels)
295
+ end
296
+
297
+ # Each packet will be evaluated to all triggers and use the result to send
298
+ # the results back to the topic to be used by the reaction microservice.
299
+ def evaluate_data_packet(topic:, triggers:)
300
+ visited = Hash.new
301
+ Logger.debug "TriggerGroupWorker-#{@ident} topic: #{topic}"
302
+ triggers_to_eval = @share.trigger_base.get_triggers(topic: topic)
303
+ Logger.debug "TriggerGroupWorker-#{@ident} triggers_to_eval: #{triggers_to_eval}"
304
+ triggers_to_eval.each do | trigger |
305
+ Logger.debug "TriggerGroupWorker-#{@ident} eval head: #{trigger}"
306
+ value = evaluate_trigger(
307
+ head: trigger,
308
+ trigger: trigger,
309
+ visited: visited,
310
+ triggers: triggers
311
+ )
312
+ Logger.debug "TriggerGroupWorker-#{@ident} trigger: #{trigger} value: #{value}"
313
+ # value MUST be -1, 0, or 1
314
+ @share.trigger_base.update_state(name: trigger.name, value: value)
315
+ end
316
+ end
317
+
318
+ # extract the value outlined in the operand to get the packet item limit
319
+ # IF operand limit does not include _LOW or _HIGH this will match the
320
+ # COLOR and return COLOR_LOW || COLOR_HIGH
321
+ # operand item: GREEN_LOW == other operand limit: GREEN
322
+ def get_packet_limit(operand:, other:)
323
+ packet = @share.packet_base.packet(
324
+ target: operand[ITEM_TARGET],
325
+ packet: operand[ITEM_PACKET]
326
+ )
327
+ return nil if packet.nil?
328
+ limit = packet["#{operand[ITEM_TYPE]}__L"]
329
+ if limit.nil? == false && limit.include?('_')
330
+ return other[LIMIT_TYPE] if limit.include?(other[LIMIT_TYPE])
331
+ end
332
+ return limit
333
+ end
334
+
335
+ # extract the value outlined in the operand to get the packet item value
336
+ # IF raw in operand it will pull the raw value over the converted
337
+ def get_packet_value(operand:)
338
+ packet = @share.packet_base.packet(
339
+ target: operand[ITEM_TARGET],
340
+ packet: operand[ITEM_PACKET]
341
+ )
342
+ return nil if packet.nil?
343
+
344
+ value_type = operand[ITEM_RAW] ? '' : '__C'
345
+ return packet["#{operand[ITEM_TYPE]}#{value_type}"]
346
+ end
347
+
348
+ # extract the value of the operand from the packet
349
+ def operand_value(operand:, other:, visited:)
350
+ if operand[TYPE] == ITEM_TYPE && other[TYPE] == LIMIT_TYPE
351
+ return get_packet_limit(operand: operand, other: other)
352
+ elsif operand[TYPE] == ITEM_TYPE
353
+ return get_packet_value(operand: operand)
354
+ elsif operand[TYPE] == TRIGGER_TYPE
355
+ return visited["#{operand[TRIGGER_TYPE]}__R"] == 1
356
+ else
357
+ return operand[operand[TYPE]]
358
+ end
359
+ end
360
+
361
+ # the base evaluate method used by evaluate_trigger
362
+ # -1 (the value is considered an error used to disable the trigger)
363
+ # 0 (the value is considered as a false value)
364
+ # 1 (the value is considered as a true value)
365
+ #
366
+ def evaluate(left:, operator:, right:)
367
+ Logger.debug "TriggerGroupWorker-#{@ident} evaluate: (#{left} #{operator} #{right})"
368
+ begin
369
+ case operator
370
+ when '>'
371
+ return left > right ? 1 : 0
372
+ when '<'
373
+ return left < right ? 1 : 0
374
+ when '>='
375
+ return left >= right ? 1 : 0
376
+ when '<='
377
+ return left <= right ? 1 : 0
378
+ when '!='
379
+ return left != right ? 1 : 0
380
+ when '=='
381
+ return left == right ? 1 : 0
382
+ when 'AND'
383
+ return left && right ? 1 : 0
384
+ when 'OR'
385
+ return left || right ? 1 : 0
386
+ end
387
+ rescue ArgumentError
388
+ Logger.error "invalid evaluate: (#{left} #{operator} #{right})"
389
+ return -1
390
+ end
391
+ end
392
+
393
+ # This could be confusing... So this is a recursive method for the
394
+ # TriggerGroupWorkers to call. It will use the trigger name and append a
395
+ # __P for path or __R for result. The Path is a Hash that contains
396
+ # a key for each node traveled to get results. When the result has
397
+ # been found it will be stored in the result key __R in the vistied Hash
398
+ # and eval_trigger will return a number.
399
+ # -1 (the value is considered an error used to disable the trigger)
400
+ # 0 (the value is considered as a false value)
401
+ # 1 (the value is considered as a true value)
402
+ #
403
+ # IF an operand is evaluated as nil it will log an error and return -1
404
+ # IF a loop is detected it will log an error and return -1
405
+ def evaluate_trigger(head:, trigger:, visited:, triggers:)
406
+ if visited["#{trigger.name}__R"]
407
+ return visited["#{trigger.name}__R"]
408
+ end
409
+ if visited["#{trigger.name}__P"].nil?
410
+ visited["#{trigger.name}__P"] = Hash.new
411
+ end
412
+ if visited["#{head.name}__P"][trigger.name]
413
+ # Not sure if this is posible as on create it validates that the dependents are already created
414
+ Logger.error "loop detected from #{head} -> #{trigger} path: #{visited["#{head.name}__P"]}"
415
+ return visited["#{trigger.name}__R"] = -1
416
+ end
417
+ trigger.roots.each do | root_trigger_name |
418
+ next if visited["#{root_trigger_name}__R"]
419
+ root_trigger = triggers[root_trigger_name]
420
+ if head.name == root_trigger.name
421
+ Logger.error "loop detected from #{head} -> #{root_trigger} path: #{visited["#{head.name}__P"]}"
422
+ return visited["#{trigger.name}__R"] = -1
423
+ end
424
+ result = evaluate_trigger(
425
+ head: head,
426
+ trigger: root_trigger,
427
+ visited: visited,
428
+ triggers: triggers
429
+ )
430
+ Logger.debug "TriggerGroupWorker-#{@ident} #{root_trigger.name} result: #{result}"
431
+ visited["#{root_trigger.name}__R"] = visited["#{head.name}__P"][root_trigger.name] = result
432
+ end
433
+ left = operand_value(operand: trigger.left, other: trigger.right, visited: visited)
434
+ right = operand_value(operand: trigger.right, other: trigger.left, visited: visited)
435
+ if left.nil? || right.nil?
436
+ return visited["#{trigger.name}__R"] = 0
437
+ end
438
+ result = evaluate(left: left, operator: trigger.operator, right: right)
439
+ return visited["#{trigger.name}__R"] = result
440
+ end
441
+
442
+ end
443
+
444
+ # The trigger manager starts a thread pool and subscribes
445
+ # to the telemtry decom topic add the packet to a queue.
446
+ # TriggerGroupManager adds the "packet" to the thread pool queue
447
+ # and the thread will evaluate the "trigger".
448
+ class TriggerGroupManager
449
+
450
+ attr_reader :name, :scope, :share, :group, :topics, :thread_pool
451
+
452
+ def initialize(name:, scope:, group:, share:)
453
+ @name = name
454
+ @scope = scope
455
+ @group = group
456
+ @share = share
457
+ @worker_count = 3
458
+ @queue = Queue.new
459
+ @read_topic = true
460
+ @topics = []
461
+ @thread_pool = nil
462
+ @cancel_thread = false
463
+ end
464
+
465
+ def generate_thread_pool()
466
+ thread_pool = []
467
+ @worker_count.times do | i |
468
+ worker = TriggerGroupWorker.new(
469
+ name: @name,
470
+ scope: @scope,
471
+ group: @group,
472
+ queue: @queue,
473
+ share: @share,
474
+ ident: i,
475
+ )
476
+ thread_pool << Thread.new { worker.run }
477
+ end
478
+ return thread_pool
479
+ end
480
+
481
+ def run
482
+ Logger.info "TriggerGroupManager running"
483
+ @thread_pool = generate_thread_pool()
484
+ loop do
485
+ begin
486
+ update_topics()
487
+ rescue StandardError => e
488
+ Logger.error "TriggerGroupManager failed to update topics.\n#{e.formatted}"
489
+ end
490
+ break if @cancel_thread
491
+
492
+ block_for_updates()
493
+ break if @cancel_thread
494
+ end
495
+ Logger.info "TriggerGroupManager exiting"
496
+ end
497
+
498
+ def update_topics
499
+ past_topics = @topics
500
+ @topics = @share.trigger_base.topics()
501
+ Logger.debug "TriggerGroupManager past_topics: #{past_topics} topics: #{@topics}"
502
+ (past_topics - @topics).each do | removed_topic |
503
+ @share.packet_base.remove(topic: removed_topic)
504
+ end
505
+ end
506
+
507
+ def block_for_updates
508
+ @read_topic = true
509
+ while @read_topic
510
+ begin
511
+ Topic.read_topics(@topics) do |topic, _msg_id, msg_hash, _redis|
512
+ Logger.debug "TriggerGroupManager block_for_updates: #{topic} #{msg_hash.to_s}"
513
+ if topic != @share.trigger_base.autonomic_topic
514
+ packet = JSON.parse(msg_hash['json_data'], :allow_nan => true, :create_additions => true)
515
+ @share.packet_base.add(topic: topic, packet: packet)
516
+ end
517
+ @queue << "#{topic}"
518
+ end
519
+ rescue StandardError => e
520
+ Logger.error "TriggerGroupManager failed to read topics #{@topics}\n#{e.formatted}"
521
+ end
522
+ end
523
+ end
524
+
525
+ def refresh
526
+ @read_topic = false
527
+ end
528
+
529
+ def shutdown
530
+ @read_topic = false
531
+ @cancel_thread = true
532
+ @worker_count.times do | i |
533
+ @queue << nil
534
+ end
535
+ end
536
+ end
537
+
538
+ # The trigger microservice starts a manager then gets the activities
539
+ # from the sorted set in redis and updates the schedule for the
540
+ # manager. Timeline will then wait for an update on the timeline
541
+ # stream this will trigger an update again to the schedule.
542
+ class TriggerGroupMicroservice < Microservice
543
+ TRIGGER_METRIC_NAME = 'update_triggers_duration_seconds'.freeze
544
+
545
+ attr_reader :name, :scope, :share, :group, :manager, :manager_thread
546
+
547
+ def initialize(*args)
548
+ super(*args)
549
+ @group = TriggerGroupShare.get_group(name: @name)
550
+ @share = TriggerGroupShare.new(scope: @scope)
551
+ @manager = TriggerGroupManager.new(name: @name, scope: @scope, group: @group, share: @share)
552
+ @manager_thread = nil
553
+ @read_topic = true
554
+ end
555
+
556
+ def run
557
+ Logger.info "TriggerGroupMicroservice running"
558
+ @manager_thread = Thread.new { @manager.run }
559
+ loop do
560
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
561
+ triggers = TriggerModel.all(scope: @scope, group: @group)
562
+ @share.trigger_base.update(triggers: triggers)
563
+ diff = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start # seconds as a float
564
+ metric_labels = { 'trigger_group' => @group, 'thread' => 'microservice' }
565
+ @metric.add_sample(name: TRIGGER_METRIC_NAME, value: diff, labels: metric_labels)
566
+ break if @cancel_thread
567
+
568
+ block_for_updates()
569
+ break if @cancel_thread
570
+ end
571
+ Logger.info "TriggerGroupMicroservice exiting"
572
+ end
573
+
574
+ def topic_lookup_functions
575
+ return {
576
+ 'created' => :created_trigger_event,
577
+ 'updated' => :created_trigger_event,
578
+ 'deleted' => :deleted_trigger_event,
579
+ 'enabled' => :created_trigger_event,
580
+ 'disabled' => :created_trigger_event,
581
+ 'activated' => :created_trigger_event,
582
+ 'deactivated' => :created_trigger_event,
583
+ }
584
+ end
585
+
586
+ def block_for_updates
587
+ @read_topic = true
588
+ while @read_topic
589
+ begin
590
+ AutonomicTopic.read_topics(@topics) do |_topic, _msg_id, msg_hash, _redis|
591
+ Logger.debug "TriggerGroupMicroservice block_for_updates: #{msg_hash.to_s}"
592
+ if msg_hash['type'] == 'trigger'
593
+ data = JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true)
594
+ public_send(topic_lookup_functions[msg_hash['kind']], data)
595
+ end
596
+ end
597
+ rescue StandardError => e
598
+ Logger.error "TriggerGroupMicroservice failed to read topics #{@topics}\n#{e.formatted}"
599
+ end
600
+ end
601
+ end
602
+
603
+ def no_op(data)
604
+ Logger.debug "TriggerGroupMicroservice web socket event: #{data}"
605
+ end
606
+
607
+ def refresh_event(data)
608
+ Logger.debug "TriggerGroupMicroservice web socket schedule refresh: #{data}"
609
+ @read_topic = false
610
+ end
611
+
612
+ # Add the trigger to the share.
613
+ def created_trigger_event(data)
614
+ Logger.debug "TriggerGroupMicroservice created_trigger_event #{data}"
615
+ if data['group'] == @group
616
+ @share.trigger_base.add(trigger: data)
617
+ @manager.refresh()
618
+ end
619
+ end
620
+
621
+ # Remove the trigger from the share.
622
+ def deleted_trigger_event(data)
623
+ Logger.debug "TriggerGroupMicroservice deleted_trigger_event #{data}"
624
+ if data['group'] == @group
625
+ @share.trigger_base.remove(trigger: data)
626
+ @manager.refresh()
627
+ end
628
+ end
629
+
630
+ def shutdown
631
+ @read_topic = false
632
+ @manager.shutdown()
633
+ super
634
+ end
635
+ end
636
+ end
637
+
638
+ OpenC3::TriggerGroupMicroservice.run if __FILE__ == $0