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.
- checksums.yaml +7 -0
- data/Gemfile +18 -0
- data/Guardfile +35 -0
- data/LICENSE.txt +727 -0
- data/README.md +37 -0
- data/Rakefile +131 -0
- data/bin/cstol_converter +1178 -0
- data/bin/openc3cli +531 -0
- data/bin/rubysloc +139 -0
- data/data/config/_array_params.yaml +23 -0
- data/data/config/_id_items.yaml +24 -0
- data/data/config/_id_params.yaml +58 -0
- data/data/config/_interfaces.yaml +214 -0
- data/data/config/_interfaces.yaml.err +1017 -0
- data/data/config/_items.yaml +20 -0
- data/data/config/_params.yaml +60 -0
- data/data/config/cmd_tlm_server.yaml +136 -0
- data/data/config/command.yaml +44 -0
- data/data/config/command_modifiers.yaml +160 -0
- data/data/config/command_telemetry.yaml +3 -0
- data/data/config/interface_modifiers.yaml +104 -0
- data/data/config/item_modifiers.yaml +221 -0
- data/data/config/microservice.yaml +78 -0
- data/data/config/param_item_modifiers.yaml +52 -0
- data/data/config/parameter_modifiers.yaml +200 -0
- data/data/config/plugins.yaml +80 -0
- data/data/config/protocols.yaml +290 -0
- data/data/config/screen.yaml +147 -0
- data/data/config/table_manager.yaml +89 -0
- data/data/config/table_parameter_modifiers.yaml +9 -0
- data/data/config/target.yaml +142 -0
- data/data/config/target_config.yaml +94 -0
- data/data/config/telemetry.yaml +87 -0
- data/data/config/telemetry_modifiers.yaml +159 -0
- data/data/config/tool.yaml +63 -0
- data/data/config/unknown.yaml +3 -0
- data/data/config/widgets.yaml +1505 -0
- data/ext/mkrf_conf.rb +49 -0
- data/ext/openc3/ext/array/array.c +122 -0
- data/ext/openc3/ext/array/extconf.rb +13 -0
- data/ext/openc3/ext/buffered_file/buffered_file.c +198 -0
- data/ext/openc3/ext/buffered_file/extconf.rb +13 -0
- data/ext/openc3/ext/config_parser/config_parser.c +280 -0
- data/ext/openc3/ext/config_parser/extconf.rb +13 -0
- data/ext/openc3/ext/crc/crc.c +351 -0
- data/ext/openc3/ext/crc/extconf.rb +13 -0
- data/ext/openc3/ext/openc3_io/extconf.rb +13 -0
- data/ext/openc3/ext/openc3_io/openc3_io.c +158 -0
- data/ext/openc3/ext/packet/extconf.rb +13 -0
- data/ext/openc3/ext/packet/packet.c +318 -0
- data/ext/openc3/ext/platform/extconf.rb +13 -0
- data/ext/openc3/ext/platform/platform.c +134 -0
- data/ext/openc3/ext/polynomial_conversion/extconf.rb +13 -0
- data/ext/openc3/ext/polynomial_conversion/polynomial_conversion.c +79 -0
- data/ext/openc3/ext/string/extconf.rb +13 -0
- data/ext/openc3/ext/string/string.c +63 -0
- data/ext/openc3/ext/structure/structure.c +1719 -0
- data/ext/openc3/ext/tabbed_plots_config/extconf.rb +13 -0
- data/ext/openc3/ext/tabbed_plots_config/tabbed_plots_config.c +62 -0
- data/ext/openc3/ext/telemetry/extconf.rb +13 -0
- data/ext/openc3/ext/telemetry/telemetry.c +336 -0
- data/lib/cosmos.rb +20 -0
- data/lib/cosmosc2.rb +20 -0
- data/lib/openc3/api/api.rb +39 -0
- data/lib/openc3/api/authorized_api.rb +30 -0
- data/lib/openc3/api/cmd_api.rb +451 -0
- data/lib/openc3/api/config_api.rb +58 -0
- data/lib/openc3/api/interface_api.rb +117 -0
- data/lib/openc3/api/limits_api.rb +375 -0
- data/lib/openc3/api/router_api.rb +117 -0
- data/lib/openc3/api/settings_api.rb +70 -0
- data/lib/openc3/api/target_api.rb +78 -0
- data/lib/openc3/api/tlm_api.rb +455 -0
- data/lib/openc3/bridge/bridge.rb +54 -0
- data/lib/openc3/bridge/bridge_config.rb +167 -0
- data/lib/openc3/bridge/bridge_interface_thread.rb +42 -0
- data/lib/openc3/bridge/bridge_router_thread.rb +42 -0
- data/lib/openc3/ccsds/ccsds_packet.rb +68 -0
- data/lib/openc3/ccsds/ccsds_parser.rb +148 -0
- data/lib/openc3/config/config_parser.rb +549 -0
- data/lib/openc3/config/meta_config_parser.rb +74 -0
- data/lib/openc3/conversions/conversion.rb +70 -0
- data/lib/openc3/conversions/generic_conversion.rb +83 -0
- data/lib/openc3/conversions/packet_time_formatted_conversion.rb +43 -0
- data/lib/openc3/conversions/packet_time_seconds_conversion.rb +43 -0
- data/lib/openc3/conversions/polynomial_conversion.rb +87 -0
- data/lib/openc3/conversions/processor_conversion.rb +70 -0
- data/lib/openc3/conversions/received_count_conversion.rb +38 -0
- data/lib/openc3/conversions/received_time_formatted_conversion.rb +42 -0
- data/lib/openc3/conversions/received_time_seconds_conversion.rb +42 -0
- data/lib/openc3/conversions/segmented_polynomial_conversion.rb +171 -0
- data/lib/openc3/conversions/unix_time_conversion.rb +68 -0
- data/lib/openc3/conversions/unix_time_formatted_conversion.rb +49 -0
- data/lib/openc3/conversions/unix_time_seconds_conversion.rb +49 -0
- data/lib/openc3/conversions.rb +34 -0
- data/lib/openc3/core_ext/array.rb +416 -0
- data/lib/openc3/core_ext/binding.rb +29 -0
- data/lib/openc3/core_ext/class.rb +72 -0
- data/lib/openc3/core_ext/exception.rb +61 -0
- data/lib/openc3/core_ext/file.rb +83 -0
- data/lib/openc3/core_ext/hash.rb +37 -0
- data/lib/openc3/core_ext/io.rb +134 -0
- data/lib/openc3/core_ext/kernel.rb +42 -0
- data/lib/openc3/core_ext/math.rb +128 -0
- data/lib/openc3/core_ext/matrix.rb +156 -0
- data/lib/openc3/core_ext/objectspace.rb +36 -0
- data/lib/openc3/core_ext/openc3_io.rb +57 -0
- data/lib/openc3/core_ext/range.rb +27 -0
- data/lib/openc3/core_ext/socket.rb +38 -0
- data/lib/openc3/core_ext/string.rb +389 -0
- data/lib/openc3/core_ext/stringio.rb +33 -0
- data/lib/openc3/core_ext/time.rb +508 -0
- data/lib/openc3/core_ext.rb +36 -0
- data/lib/openc3/interfaces/interface.rb +498 -0
- data/lib/openc3/interfaces/linc_interface.rb +475 -0
- data/lib/openc3/interfaces/protocols/burst_protocol.rb +192 -0
- data/lib/openc3/interfaces/protocols/crc_protocol.rb +193 -0
- data/lib/openc3/interfaces/protocols/fixed_protocol.rb +155 -0
- data/lib/openc3/interfaces/protocols/ignore_packet_protocol.rb +56 -0
- data/lib/openc3/interfaces/protocols/length_protocol.rb +165 -0
- data/lib/openc3/interfaces/protocols/override_protocol.rb +60 -0
- data/lib/openc3/interfaces/protocols/preidentified_protocol.rb +206 -0
- data/lib/openc3/interfaces/protocols/protocol.rb +82 -0
- data/lib/openc3/interfaces/protocols/template_protocol.rb +261 -0
- data/lib/openc3/interfaces/protocols/terminated_protocol.rb +93 -0
- data/lib/openc3/interfaces/serial_interface.rb +94 -0
- data/lib/openc3/interfaces/simulated_target_interface.rb +168 -0
- data/lib/openc3/interfaces/stream_interface.rb +81 -0
- data/lib/openc3/interfaces/tcpip_client_interface.rb +69 -0
- data/lib/openc3/interfaces/tcpip_server_interface.rb +629 -0
- data/lib/openc3/interfaces/udp_interface.rb +169 -0
- data/lib/openc3/interfaces.rb +44 -0
- data/lib/openc3/io/buffered_file.rb +109 -0
- data/lib/openc3/io/io_multiplexer.rb +80 -0
- data/lib/openc3/io/json_api_object.rb +208 -0
- data/lib/openc3/io/json_drb.rb +335 -0
- data/lib/openc3/io/json_drb_object.rb +114 -0
- data/lib/openc3/io/json_drb_rack.rb +84 -0
- data/lib/openc3/io/json_rpc.rb +420 -0
- data/lib/openc3/io/openc3_snmp.rb +58 -0
- data/lib/openc3/io/posix_serial_driver.rb +156 -0
- data/lib/openc3/io/raw_logger.rb +167 -0
- data/lib/openc3/io/raw_logger_pair.rb +77 -0
- data/lib/openc3/io/serial_driver.rb +105 -0
- data/lib/openc3/io/stderr.rb +43 -0
- data/lib/openc3/io/stdout.rb +43 -0
- data/lib/openc3/io/udp_sockets.rb +194 -0
- data/lib/openc3/io/win32_serial_driver.rb +196 -0
- data/lib/openc3/logs/log_writer.rb +302 -0
- data/lib/openc3/logs/packet_log_constants.rb +62 -0
- data/lib/openc3/logs/packet_log_reader.rb +345 -0
- data/lib/openc3/logs/packet_log_writer.rb +299 -0
- data/lib/openc3/logs/text_log_writer.rb +68 -0
- data/lib/openc3/logs.rb +25 -0
- data/lib/openc3/microservices/cleanup_microservice.rb +68 -0
- data/lib/openc3/microservices/decom_microservice.rb +136 -0
- data/lib/openc3/microservices/interface_microservice.rb +532 -0
- data/lib/openc3/microservices/log_microservice.rb +108 -0
- data/lib/openc3/microservices/microservice.rb +204 -0
- data/lib/openc3/microservices/plugin_microservice.rb +43 -0
- data/lib/openc3/microservices/reaction_microservice.rb +541 -0
- data/lib/openc3/microservices/reducer_microservice.rb +313 -0
- data/lib/openc3/microservices/router_microservice.rb +44 -0
- data/lib/openc3/microservices/text_log_microservice.rb +84 -0
- data/lib/openc3/microservices/timeline_microservice.rb +363 -0
- data/lib/openc3/microservices/trigger_group_microservice.rb +638 -0
- data/lib/openc3/models/activity_model.rb +319 -0
- data/lib/openc3/models/auth_model.rb +65 -0
- data/lib/openc3/models/cvt_model.rb +185 -0
- data/lib/openc3/models/environment_model.rb +58 -0
- data/lib/openc3/models/gem_model.rb +137 -0
- data/lib/openc3/models/info_model.rb +31 -0
- data/lib/openc3/models/interface_model.rb +281 -0
- data/lib/openc3/models/interface_status_model.rb +117 -0
- data/lib/openc3/models/metadata_model.rb +139 -0
- data/lib/openc3/models/metric_model.rb +59 -0
- data/lib/openc3/models/microservice_model.rb +206 -0
- data/lib/openc3/models/microservice_status_model.rb +74 -0
- data/lib/openc3/models/model.rb +204 -0
- data/lib/openc3/models/note_model.rb +122 -0
- data/lib/openc3/models/notification_model.rb +40 -0
- data/lib/openc3/models/ping_model.rb +35 -0
- data/lib/openc3/models/plugin_model.rb +292 -0
- data/lib/openc3/models/process_status_model.rb +76 -0
- data/lib/openc3/models/reaction_model.rb +322 -0
- data/lib/openc3/models/reducer_model.rb +65 -0
- data/lib/openc3/models/router_model.rb +35 -0
- data/lib/openc3/models/router_status_model.rb +27 -0
- data/lib/openc3/models/scope_model.rb +153 -0
- data/lib/openc3/models/settings_model.rb +55 -0
- data/lib/openc3/models/sorted_model.rb +167 -0
- data/lib/openc3/models/target_model.rb +759 -0
- data/lib/openc3/models/timeline_model.rb +154 -0
- data/lib/openc3/models/tool_config_model.rb +38 -0
- data/lib/openc3/models/tool_model.rb +262 -0
- data/lib/openc3/models/trigger_group_model.rb +186 -0
- data/lib/openc3/models/trigger_model.rb +330 -0
- data/lib/openc3/models/widget_model.rb +138 -0
- data/lib/openc3/operators/microservice_operator.rb +128 -0
- data/lib/openc3/operators/operator.rb +277 -0
- data/lib/openc3/packets/binary_accessor.rb +1207 -0
- data/lib/openc3/packets/commands.rb +373 -0
- data/lib/openc3/packets/json_packet.rb +134 -0
- data/lib/openc3/packets/limits.rb +271 -0
- data/lib/openc3/packets/limits_response.rb +53 -0
- data/lib/openc3/packets/packet.rb +1168 -0
- data/lib/openc3/packets/packet_config.rb +625 -0
- data/lib/openc3/packets/packet_item.rb +586 -0
- data/lib/openc3/packets/packet_item_limits.rb +162 -0
- data/lib/openc3/packets/parsers/format_string_parser.rb +65 -0
- data/lib/openc3/packets/parsers/limits_parser.rb +159 -0
- data/lib/openc3/packets/parsers/limits_response_parser.rb +61 -0
- data/lib/openc3/packets/parsers/packet_item_parser.rb +272 -0
- data/lib/openc3/packets/parsers/packet_parser.rb +134 -0
- data/lib/openc3/packets/parsers/processor_parser.rb +73 -0
- data/lib/openc3/packets/parsers/state_parser.rb +127 -0
- data/lib/openc3/packets/parsers/xtce_converter.rb +442 -0
- data/lib/openc3/packets/parsers/xtce_parser.rb +722 -0
- data/lib/openc3/packets/structure.rb +553 -0
- data/lib/openc3/packets/structure_item.rb +365 -0
- data/lib/openc3/packets/telemetry.rb +487 -0
- data/lib/openc3/processors/processor.rb +86 -0
- data/lib/openc3/processors/statistics_processor.rb +82 -0
- data/lib/openc3/processors/watermark_processor.rb +58 -0
- data/lib/openc3/processors.rb +24 -0
- data/lib/openc3/script/api_shared.rb +828 -0
- data/lib/openc3/script/calendar.rb +89 -0
- data/lib/openc3/script/commands.rb +227 -0
- data/lib/openc3/script/exceptions.rb +29 -0
- data/lib/openc3/script/extract.rb +161 -0
- data/lib/openc3/script/limits.rb +60 -0
- data/lib/openc3/script/script.rb +299 -0
- data/lib/openc3/script/script_runner.rb +238 -0
- data/lib/openc3/script/storage.rb +146 -0
- data/lib/openc3/script/suite.rb +542 -0
- data/lib/openc3/script/suite_results.rb +196 -0
- data/lib/openc3/script/suite_runner.rb +217 -0
- data/lib/openc3/script.rb +21 -0
- data/lib/openc3/streams/serial_stream.rb +167 -0
- data/lib/openc3/streams/stream.rb +63 -0
- data/lib/openc3/streams/tcpip_client_stream.rb +116 -0
- data/lib/openc3/streams/tcpip_socket_stream.rb +195 -0
- data/lib/openc3/system/system.rb +127 -0
- data/lib/openc3/system/system_config.rb +411 -0
- data/lib/openc3/system/target.rb +269 -0
- data/lib/openc3/system.rb +24 -0
- data/lib/openc3/tools/cmd_tlm_server/api.rb +20 -0
- data/lib/openc3/tools/cmd_tlm_server/cmd_tlm_server_config.rb +320 -0
- data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +294 -0
- data/lib/openc3/tools/table_manager/table.rb +77 -0
- data/lib/openc3/tools/table_manager/table_config.rb +273 -0
- data/lib/openc3/tools/table_manager/table_item.rb +90 -0
- data/lib/openc3/tools/table_manager/table_item_parser.rb +66 -0
- data/lib/openc3/tools/table_manager/table_manager_core.rb +333 -0
- data/lib/openc3/tools/table_manager/table_parser.rb +93 -0
- data/lib/openc3/tools/test_runner/test.rb +67 -0
- data/lib/openc3/top_level.rb +595 -0
- data/lib/openc3/topics/autonomic_topic.rb +52 -0
- data/lib/openc3/topics/calendar_topic.rb +44 -0
- data/lib/openc3/topics/command_decom_topic.rb +76 -0
- data/lib/openc3/topics/command_topic.rb +83 -0
- data/lib/openc3/topics/config_topic.rb +68 -0
- data/lib/openc3/topics/interface_topic.rb +73 -0
- data/lib/openc3/topics/limits_event_topic.rb +109 -0
- data/lib/openc3/topics/notifications_topic.rb +28 -0
- data/lib/openc3/topics/router_topic.rb +85 -0
- data/lib/openc3/topics/telemetry_decom_topic.rb +54 -0
- data/lib/openc3/topics/telemetry_topic.rb +36 -0
- data/lib/openc3/topics/timeline_topic.rb +45 -0
- data/lib/openc3/topics/topic.rb +53 -0
- data/lib/openc3/utilities/authentication.rb +141 -0
- data/lib/openc3/utilities/authorization.rb +51 -0
- data/lib/openc3/utilities/crc.rb +278 -0
- data/lib/openc3/utilities/csv.rb +153 -0
- data/lib/openc3/utilities/logger.rb +187 -0
- data/lib/openc3/utilities/message_log.rb +91 -0
- data/lib/openc3/utilities/metric.rb +141 -0
- data/lib/openc3/utilities/process_manager.rb +139 -0
- data/lib/openc3/utilities/quaternion.rb +257 -0
- data/lib/openc3/utilities/ruby_lex_utils.rb +568 -0
- data/lib/openc3/utilities/s3.rb +202 -0
- data/lib/openc3/utilities/s3_autoload.rb +9 -0
- data/lib/openc3/utilities/s3_file_cache.rb +274 -0
- data/lib/openc3/utilities/simulated_target.rb +117 -0
- data/lib/openc3/utilities/sleeper.rb +51 -0
- data/lib/openc3/utilities/store.rb +23 -0
- data/lib/openc3/utilities/store_autoload.rb +237 -0
- data/lib/openc3/utilities/zip.rb +21 -0
- data/lib/openc3/utilities.rb +35 -0
- data/lib/openc3/version.rb +14 -0
- data/lib/openc3/win32/excel.rb +132 -0
- data/lib/openc3/win32/win32.rb +402 -0
- data/lib/openc3/win32/win32_main.rb +333 -0
- data/lib/openc3.rb +49 -0
- data/tasks/gemfile_stats.rake +113 -0
- data/tasks/spec.rake +30 -0
- data/templates/plugin-template/README.md +15 -0
- data/templates/plugin-template/Rakefile +12 -0
- data/templates/plugin-template/plugin.gemspec +23 -0
- data/templates/plugin-template/plugin.txt +9 -0
- data/templates/plugin-template/targets/TARGET/cmd_tlm/cmd.txt +8 -0
- data/templates/plugin-template/targets/TARGET/cmd_tlm/tlm.txt +8 -0
- data/templates/plugin-template/targets/TARGET/lib/target.rb +10 -0
- data/templates/plugin-template/targets/TARGET/procedures/procedure.rb +3 -0
- data/templates/plugin-template/targets/TARGET/screens/status.txt +9 -0
- data/templates/plugin-template/targets/TARGET/target.txt +5 -0
- 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
|