openc3 5.2.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of openc3 might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/bin/openc3cli +108 -105
- data/data/config/interface_modifiers.yaml +22 -4
- data/data/config/item_modifiers.yaml +4 -2
- data/data/config/microservice.yaml +18 -0
- data/data/config/table_manager.yaml +2 -2
- data/data/config/tool.yaml +1 -1
- data/ext/openc3/ext/config_parser/config_parser.c +17 -2
- data/lib/openc3/api/api.rb +1 -0
- data/lib/openc3/api/interface_api.rb +12 -0
- data/lib/openc3/api/metrics_api.rb +97 -0
- data/lib/openc3/api/router_api.rb +14 -2
- data/lib/openc3/api/target_api.rb +24 -3
- data/lib/openc3/api/tlm_api.rb +5 -4
- data/lib/openc3/config/config_parser.rb +29 -4
- data/lib/openc3/core_ext/time.rb +6 -1
- data/lib/openc3/interfaces/interface.rb +27 -26
- data/lib/openc3/interfaces/mqtt_interface.rb +240 -0
- data/lib/openc3/interfaces/protocols/override_protocol.rb +2 -61
- data/lib/openc3/interfaces/protocols/protocol.rb +6 -1
- data/lib/openc3/interfaces/simulated_target_interface.rb +1 -3
- data/lib/openc3/interfaces/tcpip_server_interface.rb +0 -11
- data/lib/openc3/interfaces.rb +2 -3
- data/lib/openc3/logs/buffered_packet_log_reader.rb +2 -2
- data/lib/openc3/microservices/cleanup_microservice.rb +17 -1
- data/lib/openc3/microservices/decom_microservice.rb +12 -9
- data/lib/openc3/microservices/interface_microservice.rb +93 -9
- data/lib/openc3/microservices/log_microservice.rb +11 -5
- data/lib/openc3/microservices/microservice.rb +10 -9
- data/lib/openc3/microservices/periodic_microservice.rb +7 -0
- data/lib/openc3/microservices/reaction_microservice.rb +0 -33
- data/lib/openc3/microservices/reducer_microservice.rb +14 -10
- data/lib/openc3/microservices/text_log_microservice.rb +12 -3
- data/lib/openc3/microservices/timeline_microservice.rb +0 -6
- data/lib/openc3/microservices/trigger_group_microservice.rb +0 -20
- data/lib/openc3/models/cvt_model.rb +103 -47
- data/lib/openc3/models/interface_model.rb +23 -0
- data/lib/openc3/models/metric_model.rb +53 -6
- data/lib/openc3/models/microservice_model.rb +15 -1
- data/lib/openc3/models/model.rb +1 -1
- data/lib/openc3/models/plugin_model.rb +6 -1
- data/lib/openc3/models/secret_model.rb +53 -0
- data/lib/openc3/models/target_model.rb +2 -2
- data/lib/openc3/models/tool_model.rb +17 -8
- data/lib/openc3/operators/microservice_operator.rb +25 -0
- data/lib/openc3/operators/operator.rb +5 -1
- data/lib/openc3/packets/packet.rb +21 -7
- data/lib/openc3/packets/packet_item.rb +3 -2
- data/lib/openc3/script/api_shared.rb +18 -2
- data/lib/openc3/script/script.rb +8 -0
- data/lib/openc3/script/script_runner.rb +1 -2
- data/lib/openc3/script/storage.rb +2 -1
- data/lib/openc3/script/suite.rb +15 -11
- data/lib/openc3/system/system.rb +6 -3
- data/lib/openc3/topics/interface_topic.rb +17 -1
- data/lib/openc3/topics/router_topic.rb +17 -1
- data/lib/openc3/utilities/aws_bucket.rb +20 -3
- data/lib/openc3/utilities/bucket.rb +1 -1
- data/lib/openc3/utilities/bucket_file_cache.rb +1 -1
- data/lib/openc3/utilities/bucket_utilities.rb +1 -1
- data/lib/openc3/utilities/local_mode.rb +1 -0
- data/lib/openc3/utilities/metric.rb +77 -101
- data/lib/openc3/utilities/redis_secrets.rb +46 -0
- data/lib/openc3/utilities/s3_autoload.rb +19 -9
- data/lib/openc3/utilities/secrets.rb +63 -0
- data/lib/openc3/utilities/target_file.rb +3 -1
- data/lib/openc3/version.rb +5 -5
- data/templates/plugin-template/LICENSE.txt +7 -0
- data/templates/plugin-template/README.md +4 -3
- data/templates/plugin-template/plugin.gemspec +4 -4
- metadata +22 -3
- data/data/config/_interfaces.yaml.err +0 -1017
| @@ -28,7 +28,8 @@ module OpenC3 | |
| 28 28 | 
             
                WHITELIST.concat([
         | 
| 29 29 | 
             
                                   'get_target_list',
         | 
| 30 30 | 
             
                                   'get_target',
         | 
| 31 | 
            -
                                   ' | 
| 31 | 
            +
                                   'get_target_interfaces',
         | 
| 32 | 
            +
                                   'get_all_target_info', # DEPRECATED
         | 
| 32 33 | 
             
                                 ])
         | 
| 33 34 |  | 
| 34 35 | 
             
                # Returns the list of all target names
         | 
| @@ -49,9 +50,29 @@ module OpenC3 | |
| 49 50 | 
             
                  TargetModel.get(name: target_name, scope: scope)
         | 
| 50 51 | 
             
                end
         | 
| 51 52 |  | 
| 52 | 
            -
                # Get  | 
| 53 | 
            +
                # Get all targets and their interfaces
         | 
| 53 54 | 
             
                #
         | 
| 54 | 
            -
                # @return [Array<Array<String,  | 
| 55 | 
            +
                # @return [Array<Array<String, String] Array of Arrays \[name, interfaces]
         | 
| 56 | 
            +
                def get_target_interfaces(scope: $openc3_scope, token: $openc3_token)
         | 
| 57 | 
            +
                  authorize(permission: 'system', scope: scope, token: token)
         | 
| 58 | 
            +
                  info = []
         | 
| 59 | 
            +
                  interfaces = InterfaceModel.all(scope: scope)
         | 
| 60 | 
            +
                  get_target_list(scope: scope, token: token).each do |target_name|
         | 
| 61 | 
            +
                    interface_names = []
         | 
| 62 | 
            +
                    interfaces.each do |name, interface|
         | 
| 63 | 
            +
                      if interface['target_names'].include? target_name
         | 
| 64 | 
            +
                        interface_names << interface['name']
         | 
| 65 | 
            +
                      end
         | 
| 66 | 
            +
                    end
         | 
| 67 | 
            +
                    info << [target_name, interface_names.join(",")]
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
                  info
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                # DEPRECATED: Get information about all targets
         | 
| 73 | 
            +
                # Warning this call can take a long time with many defined packets
         | 
| 74 | 
            +
                #
         | 
| 75 | 
            +
                # @return [Array<Array<String, String, Numeric, Numeric>] Array of Arrays \[name, interface, cmd_cnt, tlm_cnt]
         | 
| 55 76 | 
             
                def get_all_target_info(scope: $openc3_scope, token: $openc3_token)
         | 
| 56 77 | 
             
                  authorize(permission: 'system', scope: scope, token: token)
         | 
| 57 78 | 
             
                  info = []
         | 
    
        data/lib/openc3/api/tlm_api.rb
    CHANGED
    
    | @@ -118,7 +118,8 @@ module OpenC3 | |
| 118 118 | 
             
                # @param type [Symbol] Telemetry type, :RAW, :CONVERTED (default), :FORMATTED, or :WITH_UNITS
         | 
| 119 119 | 
             
                def inject_tlm(target_name, packet_name, item_hash = nil, type: :CONVERTED, scope: $openc3_scope, token: $openc3_token)
         | 
| 120 120 | 
             
                  authorize(permission: 'tlm_set', target_name: target_name, packet_name: packet_name, scope: scope, token: token)
         | 
| 121 | 
            -
                   | 
| 121 | 
            +
                  type = type.to_s.intern
         | 
| 122 | 
            +
                  unless CvtModel::VALUE_TYPES.include?(type)
         | 
| 122 123 | 
             
                    raise "Unknown type '#{type}' for #{target_name} #{packet_name}"
         | 
| 123 124 | 
             
                  end
         | 
| 124 125 |  | 
| @@ -161,8 +162,8 @@ module OpenC3 | |
| 161 162 | 
             
                # @param args The args must either be a string followed by a value or
         | 
| 162 163 | 
             
                #   three strings followed by a value (see the calling style in the
         | 
| 163 164 | 
             
                #   description).
         | 
| 164 | 
            -
                # @param type [Symbol] Telemetry type, :RAW, :CONVERTED | 
| 165 | 
            -
                def override_tlm(*args, type: : | 
| 165 | 
            +
                # @param type [Symbol] Telemetry type, :ALL (default), :RAW, :CONVERTED, :FORMATTED, :WITH_UNITS
         | 
| 166 | 
            +
                def override_tlm(*args, type: :ALL, scope: $openc3_scope, token: $openc3_token)
         | 
| 166 167 | 
             
                  target_name, packet_name, item_name, value = set_tlm_process_args(args, __method__, scope: scope)
         | 
| 167 168 | 
             
                  authorize(permission: 'tlm_set', target_name: target_name, packet_name: packet_name, scope: scope, token: token)
         | 
| 168 169 | 
             
                  CvtModel.override(target_name, packet_name, item_name, value, type: type.intern, scope: scope)
         | 
| @@ -179,7 +180,7 @@ module OpenC3 | |
| 179 180 | 
             
                #
         | 
| 180 181 | 
             
                # @param args The args must either be a string or three strings
         | 
| 181 182 | 
             
                #   (see the calling style in the description).
         | 
| 182 | 
            -
                # @param type [Symbol] Telemetry type, :RAW, :CONVERTED | 
| 183 | 
            +
                # @param type [Symbol] Telemetry type, :ALL (default), :RAW, :CONVERTED, :FORMATTED, :WITH_UNITS
         | 
| 183 184 | 
             
                #   Also takes :ALL which means to normalize all telemetry types
         | 
| 184 185 | 
             
                def normalize_tlm(*args, type: :ALL, scope: $openc3_scope, token: $openc3_token)
         | 
| 185 186 | 
             
                  target_name, packet_name, item_name = tlm_process_args(args, __method__, scope: scope)
         | 
| @@ -219,8 +219,6 @@ module OpenC3 | |
| 219 219 | 
             
                               size,
         | 
| 220 220 | 
             
                               PARSING_REGEX,
         | 
| 221 221 | 
             
                               &block)
         | 
| 222 | 
            -
                  rescue Exception => e # Catch EVERYTHING so we can re-raise with additional info
         | 
| 223 | 
            -
                    raise e, "#{e}\n\nParsed output in #{file.path}", e.backtrace
         | 
| 224 222 | 
             
                  ensure
         | 
| 225 223 | 
             
                    file.close unless file.closed?
         | 
| 226 224 | 
             
                  end
         | 
| @@ -446,6 +444,22 @@ module OpenC3 | |
| 446 444 | 
             
                  value
         | 
| 447 445 | 
             
                end
         | 
| 448 446 |  | 
| 447 | 
            +
                def parse_errors(errors)
         | 
| 448 | 
            +
                  return if errors.empty?
         | 
| 449 | 
            +
                  message = ''
         | 
| 450 | 
            +
                  errors.each do |error|
         | 
| 451 | 
            +
                    if error.is_a? OpenC3::ConfigParser::Error
         | 
| 452 | 
            +
                      message += "\n#{File.basename(error.filename)}:#{error.line_number}: #{error.line}"
         | 
| 453 | 
            +
                      message += "\nError: #{error.message}"
         | 
| 454 | 
            +
                      message += "\nUsage: #{error.usage}" unless error.usage.empty?
         | 
| 455 | 
            +
                    else
         | 
| 456 | 
            +
                      message += "\n#{error.message}"
         | 
| 457 | 
            +
                    end
         | 
| 458 | 
            +
                    message += "\n"
         | 
| 459 | 
            +
                  end
         | 
| 460 | 
            +
                  raise message
         | 
| 461 | 
            +
                end
         | 
| 462 | 
            +
             | 
| 449 463 | 
             
                if RUBY_ENGINE != 'ruby' or ENV['OPENC3_NO_EXT']
         | 
| 450 464 | 
             
                  # Iterates over each line of the io object and yields the keyword and parameters
         | 
| 451 465 | 
             
                  def parse_loop(io, yield_non_keyword_lines, remove_quotes, size, rx)
         | 
| @@ -454,6 +468,7 @@ module OpenC3 | |
| 454 468 | 
             
                    @keyword = nil
         | 
| 455 469 | 
             
                    @parameters = []
         | 
| 456 470 | 
             
                    @line = ''
         | 
| 471 | 
            +
                    errors = []
         | 
| 457 472 |  | 
| 458 473 | 
             
                    while true
         | 
| 459 474 | 
             
                      @line_number += 1
         | 
| @@ -516,7 +531,11 @@ module OpenC3 | |
| 516 531 | 
             
                      # Ignore lines without keywords: comments and blank lines
         | 
| 517 532 | 
             
                      if @keyword.nil?
         | 
| 518 533 | 
             
                        if yield_non_keyword_lines
         | 
| 519 | 
            -
                           | 
| 534 | 
            +
                          begin
         | 
| 535 | 
            +
                            yield(@keyword, @parameters)
         | 
| 536 | 
            +
                          rescue => error
         | 
| 537 | 
            +
                            errors << error
         | 
| 538 | 
            +
                          end
         | 
| 520 539 | 
             
                        end
         | 
| 521 540 | 
             
                        @line = ''
         | 
| 522 541 | 
             
                        next
         | 
| @@ -545,10 +564,16 @@ module OpenC3 | |
| 545 564 | 
             
                        end
         | 
| 546 565 | 
             
                      end
         | 
| 547 566 |  | 
| 548 | 
            -
                       | 
| 567 | 
            +
                      begin
         | 
| 568 | 
            +
                        yield(@keyword, @parameters)
         | 
| 569 | 
            +
                      rescue => error
         | 
| 570 | 
            +
                        errors << error
         | 
| 571 | 
            +
                      end
         | 
| 549 572 | 
             
                      @line = ''
         | 
| 550 573 | 
             
                    end
         | 
| 551 574 |  | 
| 575 | 
            +
                    parse_errors(errors)
         | 
| 576 | 
            +
             | 
| 552 577 | 
             
                    @@progress_callback.call(1.0) if @@progress_callback
         | 
| 553 578 |  | 
| 554 579 | 
             
                    return nil
         | 
    
        data/lib/openc3/core_ext/time.rb
    CHANGED
    
    | @@ -17,7 +17,7 @@ | |
| 17 17 | 
             
            # All changes Copyright 2022, OpenC3, Inc.
         | 
| 18 18 | 
             
            # All Rights Reserved
         | 
| 19 19 | 
             
            #
         | 
| 20 | 
            -
            # This file may also be used under the terms of a commercial license | 
| 20 | 
            +
            # This file may also be used under the terms of a commercial license
         | 
| 21 21 | 
             
            # if purchased from OpenC3, Inc.
         | 
| 22 22 |  | 
| 23 23 | 
             
            require 'date'
         | 
| @@ -58,6 +58,7 @@ class Time | |
| 58 58 | 
             
              MINUTES_PER_HOUR = 60
         | 
| 59 59 | 
             
              HOURS_PER_DAY = 24
         | 
| 60 60 | 
             
              NSEC_PER_SECOND = 1_000_000_000
         | 
| 61 | 
            +
              NSEC_PER_MSEC = 1_000_000
         | 
| 61 62 | 
             
              USEC_PER_SECOND = USEC_PER_MSEC * MSEC_PER_SECOND
         | 
| 62 63 | 
             
              MSEC_PER_MINUTE = 60 * MSEC_PER_SECOND
         | 
| 63 64 | 
             
              MSEC_PER_HOUR = 60 * MSEC_PER_MINUTE
         | 
| @@ -508,4 +509,8 @@ class Time | |
| 508 509 | 
             
                nanoseconds = nsec_from_epoch % NSEC_PER_SECOND
         | 
| 509 510 | 
             
                Time.at(seconds, nanoseconds, :nsec)
         | 
| 510 511 | 
             
              end
         | 
| 512 | 
            +
             | 
| 513 | 
            +
              def to_msec_from_epoch
         | 
| 514 | 
            +
                (self.tv_sec * MSEC_PER_SECOND) + (self.tv_nsec / NSEC_PER_MSEC)
         | 
| 515 | 
            +
              end
         | 
| 511 516 | 
             
            end
         | 
| @@ -22,6 +22,7 @@ | |
| 22 22 |  | 
| 23 23 | 
             
            require 'openc3/api/api'
         | 
| 24 24 | 
             
            require 'openc3/io/raw_logger_pair'
         | 
| 25 | 
            +
            require 'openc3/utilities/secrets'
         | 
| 25 26 |  | 
| 26 27 | 
             
            module OpenC3
         | 
| 27 28 | 
             
              # Defines all the attributes and methods common to all interface classes
         | 
| @@ -112,9 +113,6 @@ module OpenC3 | |
| 112 113 | 
             
                # @return [Array<[Protocol Class, Protocol Args, Protocol kind (:READ, :WRITE, :READ_WRITE)>] Info to recreate protocols
         | 
| 113 114 | 
             
                attr_accessor :protocol_info
         | 
| 114 115 |  | 
| 115 | 
            -
                # @return [Hash or nil] Hash of overridden telemetry points
         | 
| 116 | 
            -
                attr_accessor :override_tlm
         | 
| 117 | 
            -
             | 
| 118 116 | 
             
                # @return [String] Most recently read raw data
         | 
| 119 117 | 
             
                attr_accessor :read_raw_data
         | 
| 120 118 |  | 
| @@ -134,6 +132,9 @@ module OpenC3 | |
| 134 132 | 
             
                #   (when used as a BridgeRouter)
         | 
| 135 133 | 
             
                attr_accessor :interfaces
         | 
| 136 134 |  | 
| 135 | 
            +
                # @return [Secrets] Interface secrets manager class
         | 
| 136 | 
            +
                attr_accessor :secrets
         | 
| 137 | 
            +
             | 
| 137 138 | 
             
                # Initialize default attribute values
         | 
| 138 139 | 
             
                def initialize
         | 
| 139 140 | 
             
                  @name = self.class.to_s.split("::")[-1] # Remove namespacing if present
         | 
| @@ -166,13 +167,13 @@ module OpenC3 | |
| 166 167 | 
             
                  @read_protocols = []
         | 
| 167 168 | 
             
                  @write_protocols = []
         | 
| 168 169 | 
             
                  @protocol_info = []
         | 
| 169 | 
            -
                  @override_tlm = nil
         | 
| 170 170 | 
             
                  @read_raw_data = ''
         | 
| 171 171 | 
             
                  @written_raw_data = ''
         | 
| 172 172 | 
             
                  @read_raw_data_time = nil
         | 
| 173 173 | 
             
                  @written_raw_data_time = nil
         | 
| 174 174 | 
             
                  @config_params = []
         | 
| 175 175 | 
             
                  @interfaces = []
         | 
| 176 | 
            +
                  @secrets = Secrets.getClient
         | 
| 176 177 | 
             
                end
         | 
| 177 178 |  | 
| 178 179 | 
             
                # Connects the interface to its target(s). Must be implemented by a
         | 
| @@ -406,13 +407,13 @@ module OpenC3 | |
| 406 407 | 
             
                  # num_clients is per interface so don't copy
         | 
| 407 408 | 
             
                  # read_queue_size is the number of packets in the queue so don't copy
         | 
| 408 409 | 
             
                  # write_queue_size is the number of packets in the queue so don't copy
         | 
| 409 | 
            -
                   | 
| 410 | 
            +
                  self.options.each do |option_name, option_values|
         | 
| 411 | 
            +
                    other_interface.set_option(option_name, option_values)
         | 
| 412 | 
            +
                  end
         | 
| 410 413 | 
             
                  other_interface.protocol_info = []
         | 
| 411 414 | 
             
                  self.protocol_info.each do |protocol_class, protocol_args, read_write|
         | 
| 412 415 | 
             
                    other_interface.add_protocol(protocol_class, protocol_args, read_write)
         | 
| 413 416 | 
             
                  end
         | 
| 414 | 
            -
                  other_interface.override_tlm = nil
         | 
| 415 | 
            -
                  other_interface.override_tlm = self.override_tlm.clone if self.override_tlm
         | 
| 416 417 | 
             
                end
         | 
| 417 418 |  | 
| 418 419 | 
             
                # Set an interface or router specific option
         | 
| @@ -484,28 +485,28 @@ module OpenC3 | |
| 484 485 | 
             
                  protocol.interface = self
         | 
| 485 486 | 
             
                end
         | 
| 486 487 |  | 
| 487 | 
            -
                def  | 
| 488 | 
            -
                   | 
| 488 | 
            +
                def interface_cmd(cmd_name, *cmd_args)
         | 
| 489 | 
            +
                  # Default do nothing - Implemented by subclasses
         | 
| 490 | 
            +
                  return false
         | 
| 489 491 | 
             
                end
         | 
| 490 492 |  | 
| 491 | 
            -
                def  | 
| 492 | 
            -
                   | 
| 493 | 
            -
             | 
| 494 | 
            -
             | 
| 495 | 
            -
             | 
| 496 | 
            -
             | 
| 497 | 
            -
                   | 
| 498 | 
            -
             | 
| 499 | 
            -
             | 
| 500 | 
            -
                     | 
| 493 | 
            +
                def protocol_cmd(cmd_name, *cmd_args, read_write: :READ_WRITE, index: -1)
         | 
| 494 | 
            +
                  read_write = read_write.to_s.upcase.intern
         | 
| 495 | 
            +
                  protocols = nil
         | 
| 496 | 
            +
                  case read_write
         | 
| 497 | 
            +
                  when :READ, :READ_WRITE
         | 
| 498 | 
            +
                    protocols = @read_protocols
         | 
| 499 | 
            +
                  when :WRITE
         | 
| 500 | 
            +
                    protocols = @write_protocols.reverse # Reverse so ordering matches configuration ordering
         | 
| 501 | 
            +
                  else
         | 
| 502 | 
            +
                    raise "Unknown protocol descriptor: #{read_write}. Must be :READ, :WRITE, or :READ_WRITE."
         | 
| 501 503 | 
             
                  end
         | 
| 502 | 
            -
             | 
| 503 | 
            -
             | 
| 504 | 
            -
             | 
| 505 | 
            -
             | 
| 506 | 
            -
                   | 
| 507 | 
            -
                   | 
| 508 | 
            -
                  @override_tlm[target_name][packet_name][item_name] = [value, type]
         | 
| 504 | 
            +
                  handled = false
         | 
| 505 | 
            +
                  protocols.each_with_index do |protocol, protocol_index|
         | 
| 506 | 
            +
                    result = protocol.protocol_cmd(cmd_name, @cmd_args) if index == protocol_index or index == -1
         | 
| 507 | 
            +
                    handled = true if result
         | 
| 508 | 
            +
                  end
         | 
| 509 | 
            +
                  return handled
         | 
| 509 510 | 
             
                end
         | 
| 510 511 | 
             
              end
         | 
| 511 512 | 
             
            end
         | 
| @@ -0,0 +1,240 @@ | |
| 1 | 
            +
            # encoding: ascii-8bit
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # Copyright 2022 OpenC3, Inc.
         | 
| 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 | 
            +
            # This file may also be used under the terms of a commercial license
         | 
| 17 | 
            +
            # if purchased from OpenC3, Inc.
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            # You can quickly setup an unauthenticated MQTT server in Docker with
         | 
| 20 | 
            +
            # docker run -it -p 1883:1883 eclipse-mosquitto:2.0.15 mosquitto -c /mosquitto-no-auth.conf
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            require 'openc3/interfaces/interface'
         | 
| 23 | 
            +
            require 'openc3/config/config_parser'
         | 
| 24 | 
            +
            require 'mqtt'
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            # Patches to the Ruby MQTT library so that it will work reliably with COSMOS
         | 
| 27 | 
            +
            saved_verbose = $VERBOSE
         | 
| 28 | 
            +
            $VERBOSE = nil
         | 
| 29 | 
            +
            module MQTT
         | 
| 30 | 
            +
              class Client
         | 
| 31 | 
            +
                def get(topic = nil, options = {})
         | 
| 32 | 
            +
                  if block_given?
         | 
| 33 | 
            +
                    get_packet(topic) do |packet|
         | 
| 34 | 
            +
                      yield(packet.topic, packet.payload) unless packet.retain && options[:omit_retained]
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                  else
         | 
| 37 | 
            +
                    loop do
         | 
| 38 | 
            +
                      # Wait for one packet to be available
         | 
| 39 | 
            +
                      packet = get_packet(topic)
         | 
| 40 | 
            +
                      return nil unless packet # Patch for COSMOS
         | 
| 41 | 
            +
                      return packet.topic, packet.payload unless packet.retain && options[:omit_retained]
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def get_packet(topic = nil)
         | 
| 47 | 
            +
                  # Subscribe to a topic, if an argument is given
         | 
| 48 | 
            +
                  subscribe(topic) unless topic.nil?
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  if block_given?
         | 
| 51 | 
            +
                    # Loop forever!
         | 
| 52 | 
            +
                    loop do
         | 
| 53 | 
            +
                      packet = @read_queue.pop
         | 
| 54 | 
            +
                      return nil unless packet # Patch for COSMOS
         | 
| 55 | 
            +
                      yield(packet)
         | 
| 56 | 
            +
                      puback_packet(packet) if packet.qos > 0
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                  else
         | 
| 59 | 
            +
                    # Wait for one packet to be available
         | 
| 60 | 
            +
                    packet = @read_queue.pop
         | 
| 61 | 
            +
                    return nil unless packet # Patch for COSMOS
         | 
| 62 | 
            +
                    puback_packet(packet) if packet.qos > 0
         | 
| 63 | 
            +
                    return packet
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def disconnect(send_msg = true)
         | 
| 68 | 
            +
                  # Stop reading packets from the socket first
         | 
| 69 | 
            +
                  @read_thread.kill if @read_thread && @read_thread.alive?
         | 
| 70 | 
            +
                  @read_thread = nil
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  @read_queue << nil # Patch for COSMOS
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  # Close the socket if it is open
         | 
| 75 | 
            +
                  if connected?
         | 
| 76 | 
            +
                    if send_msg
         | 
| 77 | 
            +
                      packet = MQTT::Packet::Disconnect.new
         | 
| 78 | 
            +
                      send_packet(packet)
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
                    @socket.close unless @socket.nil?
         | 
| 81 | 
            +
                    @socket = nil
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
            end
         | 
| 87 | 
            +
            $VERBOSE = saved_verbose
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            module OpenC3
         | 
| 90 | 
            +
              # Base class for interfaces that send and receive messages over MQTT
         | 
| 91 | 
            +
              class MqttInterface < Interface
         | 
| 92 | 
            +
                # @param hostname [String] MQTT server to connect to
         | 
| 93 | 
            +
                # @param port [Integer] MQTT port
         | 
| 94 | 
            +
                # @param ssl [Boolean] Use SSL true/false
         | 
| 95 | 
            +
                def initialize(hostname, port = 1883, ssl = false)
         | 
| 96 | 
            +
                  super()
         | 
| 97 | 
            +
                  @hostname = hostname
         | 
| 98 | 
            +
                  @port = Integer(port)
         | 
| 99 | 
            +
                  @ssl = ConfigParser.handle_true_false(ssl)
         | 
| 100 | 
            +
                  @username = nil
         | 
| 101 | 
            +
                  @password = nil
         | 
| 102 | 
            +
                  @cert = nil
         | 
| 103 | 
            +
                  @key = nil
         | 
| 104 | 
            +
                  @ca_file = nil
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  @write_topics = []
         | 
| 107 | 
            +
                  @read_topics = []
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  # Build list of packets by topic
         | 
| 110 | 
            +
                  @read_packets_by_topic = {}
         | 
| 111 | 
            +
                  System.telemetry.all.each do |target_name, target_packets|
         | 
| 112 | 
            +
                    target_packets.each do |packet_name, packet|
         | 
| 113 | 
            +
                      topics = packet.meta['TOPIC']
         | 
| 114 | 
            +
                      topics = packet.meta['TOPICS'] unless topics
         | 
| 115 | 
            +
                      if topics
         | 
| 116 | 
            +
                        topics.each do |topic|
         | 
| 117 | 
            +
                          @read_packets_by_topic[topic] = packet
         | 
| 118 | 
            +
                        end
         | 
| 119 | 
            +
                      end
         | 
| 120 | 
            +
                    end
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                # Connects the interface to its target(s)
         | 
| 125 | 
            +
                def connect
         | 
| 126 | 
            +
                  @write_topics = []
         | 
| 127 | 
            +
                  @read_topics = []
         | 
| 128 | 
            +
                  @client = MQTT::Client.new
         | 
| 129 | 
            +
                  @client.host = @hostname
         | 
| 130 | 
            +
                  @client.port = @port
         | 
| 131 | 
            +
                  @client.ssl = @ssl
         | 
| 132 | 
            +
                  @client.username = @username if @username
         | 
| 133 | 
            +
                  @client.password = @password if @password
         | 
| 134 | 
            +
                  @client.cert = @cert if @cert
         | 
| 135 | 
            +
                  @client.key = @key if @key
         | 
| 136 | 
            +
                  @client.ca_file = @ca_file.path if @ca_file
         | 
| 137 | 
            +
                  @client.connect
         | 
| 138 | 
            +
                  @read_packets_by_topic.each do |topic, _|
         | 
| 139 | 
            +
                    Logger.info "#{@name}: Subscribing to #{topic}"
         | 
| 140 | 
            +
                    @client.subscribe(topic)
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
                  super()
         | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                # @return [Boolean] Whether the active ports (read and/or write) have
         | 
| 146 | 
            +
                #   created sockets. Since UDP is connectionless, creation of the sockets
         | 
| 147 | 
            +
                #   is used to determine connection.
         | 
| 148 | 
            +
                def connected?
         | 
| 149 | 
            +
                  if @client
         | 
| 150 | 
            +
                    return @client.connected?
         | 
| 151 | 
            +
                  else
         | 
| 152 | 
            +
                    return false
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
                end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                # Disconnects the interface from its target(s)
         | 
| 157 | 
            +
                def disconnect
         | 
| 158 | 
            +
                  @client.disconnect
         | 
| 159 | 
            +
                  @client = nil
         | 
| 160 | 
            +
                  super()
         | 
| 161 | 
            +
                end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                def read
         | 
| 164 | 
            +
                  topic = @read_topics.shift
         | 
| 165 | 
            +
                  packet = super()
         | 
| 166 | 
            +
                  return nil unless packet
         | 
| 167 | 
            +
                  identified_packet = @read_packets_by_topic[topic]
         | 
| 168 | 
            +
                  if identified_packet
         | 
| 169 | 
            +
                    identified_packet = identified_packet.dup
         | 
| 170 | 
            +
                    identified_packet.buffer = packet.buffer
         | 
| 171 | 
            +
                    packet = identified_packet
         | 
| 172 | 
            +
                  end
         | 
| 173 | 
            +
                  packet.received_time = nil
         | 
| 174 | 
            +
                  return packet
         | 
| 175 | 
            +
                end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                def write(packet)
         | 
| 178 | 
            +
                  topics = packet.meta['TOPIC']
         | 
| 179 | 
            +
                  topics = packet.meta['TOPICS'] unless topics
         | 
| 180 | 
            +
                  if topics
         | 
| 181 | 
            +
                    topics.each do |topic|
         | 
| 182 | 
            +
                      @write_topics << topic
         | 
| 183 | 
            +
                      super(packet)
         | 
| 184 | 
            +
                    end
         | 
| 185 | 
            +
                  else
         | 
| 186 | 
            +
                    raise "Command packet #{packet.target_name} #{packet.packet_name} requires a META TOPIC or TOPICS"
         | 
| 187 | 
            +
                  end
         | 
| 188 | 
            +
                end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                # Reads from the socket if the read_port is defined
         | 
| 191 | 
            +
                def read_interface
         | 
| 192 | 
            +
                  topic, data = @client.get
         | 
| 193 | 
            +
                  if data.nil? or data.length <= 0
         | 
| 194 | 
            +
                    Logger.info "#{@name}: read returned nil" if data.nil?
         | 
| 195 | 
            +
                    Logger.info "#{@name}: read returned 0 bytes" if not data.nil? and data.length <= 0
         | 
| 196 | 
            +
                    return nil
         | 
| 197 | 
            +
                  end
         | 
| 198 | 
            +
                  @read_topics << topic
         | 
| 199 | 
            +
                  read_interface_base(data)
         | 
| 200 | 
            +
                  return data
         | 
| 201 | 
            +
                rescue IOError # Disconnected
         | 
| 202 | 
            +
                  return nil
         | 
| 203 | 
            +
                end
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                # Writes to the socket
         | 
| 206 | 
            +
                # @param data [String] Raw packet data
         | 
| 207 | 
            +
                def write_interface(data)
         | 
| 208 | 
            +
                  write_interface_base(data)
         | 
| 209 | 
            +
                  topic = @write_topics.shift
         | 
| 210 | 
            +
                  @client.publish(topic, data)
         | 
| 211 | 
            +
                  data
         | 
| 212 | 
            +
                end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                # Supported Options
         | 
| 215 | 
            +
                # USERNAME - Username for Mqtt Server
         | 
| 216 | 
            +
                # PASSWORD - Password for Mqtt Server
         | 
| 217 | 
            +
                # CERT - Public Key for Client Cert Auth
         | 
| 218 | 
            +
                # KEY - Private Key for Client Cert Auth
         | 
| 219 | 
            +
                # CA_FILE - Certificate Authority for Client Cert Auth
         | 
| 220 | 
            +
                # (see Interface#set_option)
         | 
| 221 | 
            +
                def set_option(option_name, option_values)
         | 
| 222 | 
            +
                  super(option_name, option_values)
         | 
| 223 | 
            +
                  case option_name.upcase
         | 
| 224 | 
            +
                  when 'USERNAME'
         | 
| 225 | 
            +
                    @username = option_values[0]
         | 
| 226 | 
            +
                  when 'PASSWORD'
         | 
| 227 | 
            +
                    @password = option_values[0]
         | 
| 228 | 
            +
                  when 'CERT'
         | 
| 229 | 
            +
                    @cert = option_values[0]
         | 
| 230 | 
            +
                  when 'KEY'
         | 
| 231 | 
            +
                    @key = option_values[0]
         | 
| 232 | 
            +
                  when 'CA_FILE'
         | 
| 233 | 
            +
                    # CA_FILE must be given as a file
         | 
| 234 | 
            +
                    @ca_file = Tempfile.new('ca_file')
         | 
| 235 | 
            +
                    @ca_file.write(option_values[0])
         | 
| 236 | 
            +
                    @ca_file.close
         | 
| 237 | 
            +
                  end
         | 
| 238 | 
            +
                end
         | 
| 239 | 
            +
              end
         | 
| 240 | 
            +
            end
         | 
| @@ -1,63 +1,4 @@ | |
| 1 1 | 
             
            # encoding: ascii-8bit
         | 
| 2 2 |  | 
| 3 | 
            -
            #  | 
| 4 | 
            -
            #  | 
| 5 | 
            -
            #
         | 
| 6 | 
            -
            # This program is free software; you can modify and/or redistribute it
         | 
| 7 | 
            -
            # under the terms of the GNU Affero General Public License
         | 
| 8 | 
            -
            # as published by the Free Software Foundation; version 3 with
         | 
| 9 | 
            -
            # attribution addendums as found in the LICENSE.txt
         | 
| 10 | 
            -
            #
         | 
| 11 | 
            -
            # This program is distributed in the hope that it will be useful,
         | 
| 12 | 
            -
            # but WITHOUT ANY WARRANTY; without even the implied warranty of
         | 
| 13 | 
            -
            # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
         | 
| 14 | 
            -
            # GNU Affero General Public License for more details.
         | 
| 15 | 
            -
             | 
| 16 | 
            -
            # Modified by OpenC3, Inc.
         | 
| 17 | 
            -
            # All changes Copyright 2022, OpenC3, Inc.
         | 
| 18 | 
            -
            # All Rights Reserved
         | 
| 19 | 
            -
            #
         | 
| 20 | 
            -
            # This file may also be used under the terms of a commercial license
         | 
| 21 | 
            -
            # if purchased from OpenC3, Inc.
         | 
| 22 | 
            -
             | 
| 23 | 
            -
            require 'openc3/interfaces/protocols/protocol'
         | 
| 24 | 
            -
             | 
| 25 | 
            -
            module OpenC3
         | 
| 26 | 
            -
              # Protocol which permanently overrides an item value such that reading the
         | 
| 27 | 
            -
              # item returns the overriden value. Methods are prefixed with underscores
         | 
| 28 | 
            -
              # so the API can include the original name which calls out to these
         | 
| 29 | 
            -
              # methods. Clearing the override requires calling normalize_tlm.
         | 
| 30 | 
            -
              class OverrideProtocol < Protocol
         | 
| 31 | 
            -
                # @param allow_empty_data [true/false/nil] See Protocol#initialize
         | 
| 32 | 
            -
                def initialize(allow_empty_data = nil)
         | 
| 33 | 
            -
                  super(allow_empty_data)
         | 
| 34 | 
            -
                end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                # Called to perform modifications on a read packet before it is given to the user
         | 
| 37 | 
            -
                #
         | 
| 38 | 
            -
                # @param packet [Packet] Original packet
         | 
| 39 | 
            -
                # @return [Packet] Potentially modified packet
         | 
| 40 | 
            -
                def read_packet(packet)
         | 
| 41 | 
            -
                  if @interface.override_tlm && !@interface.override_tlm.empty?
         | 
| 42 | 
            -
                    # Need to make sure packet is identified and defined
         | 
| 43 | 
            -
                    target_names = nil
         | 
| 44 | 
            -
                    target_names = @interface.tlm_target_names if @interface
         | 
| 45 | 
            -
                    identified_packet = System.telemetry.identify_and_define_packet(packet, target_names)
         | 
| 46 | 
            -
                    if identified_packet
         | 
| 47 | 
            -
                      packet = identified_packet
         | 
| 48 | 
            -
                      packets = @interface.override_tlm[packet.target_name]
         | 
| 49 | 
            -
                      if packets
         | 
| 50 | 
            -
                        items = packets[packet.packet_name]
         | 
| 51 | 
            -
                        if items
         | 
| 52 | 
            -
                          items.each do |item_name, value|
         | 
| 53 | 
            -
                            # This should be safe because we check at the API level it exists
         | 
| 54 | 
            -
                            packet.write(item_name, value[0], value[1])
         | 
| 55 | 
            -
                          end
         | 
| 56 | 
            -
                        end
         | 
| 57 | 
            -
                      end
         | 
| 58 | 
            -
                    end
         | 
| 59 | 
            -
                  end
         | 
| 60 | 
            -
                  return packet
         | 
| 61 | 
            -
                end
         | 
| 62 | 
            -
              end
         | 
| 63 | 
            -
            end
         | 
| 3 | 
            +
            # This class is deprecated and this file exists only to satisfy existing code requiring it
         | 
| 4 | 
            +
            # TODO: Remove this in a future release
         | 
| @@ -17,7 +17,7 @@ | |
| 17 17 | 
             
            # All changes Copyright 2022, OpenC3, Inc.
         | 
| 18 18 | 
             
            # All Rights Reserved
         | 
| 19 19 | 
             
            #
         | 
| 20 | 
            -
            # This file may also be used under the terms of a commercial license | 
| 20 | 
            +
            # This file may also be used under the terms of a commercial license
         | 
| 21 21 | 
             
            # if purchased from OpenC3, Inc.
         | 
| 22 22 |  | 
| 23 23 | 
             
            require 'openc3/config/config_parser'
         | 
| @@ -81,5 +81,10 @@ module OpenC3 | |
| 81 81 | 
             
                def post_write_interface(packet, data)
         | 
| 82 82 | 
             
                  return packet, data
         | 
| 83 83 | 
             
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def protocol_cmd(cmd_name, *cmd_args)
         | 
| 86 | 
            +
                  # Default do nothing - Implemented by subclasses
         | 
| 87 | 
            +
                  return false
         | 
| 88 | 
            +
                end
         | 
| 84 89 | 
             
              end
         | 
| 85 90 | 
             
            end
         | 
| @@ -17,11 +17,10 @@ | |
| 17 17 | 
             
            # All changes Copyright 2022, OpenC3, Inc.
         | 
| 18 18 | 
             
            # All Rights Reserved
         | 
| 19 19 | 
             
            #
         | 
| 20 | 
            -
            # This file may also be used under the terms of a commercial license | 
| 20 | 
            +
            # This file may also be used under the terms of a commercial license
         | 
| 21 21 | 
             
            # if purchased from OpenC3, Inc.
         | 
| 22 22 |  | 
| 23 23 | 
             
            require 'openc3/interfaces/interface'
         | 
| 24 | 
            -
            require 'openc3/interfaces/protocols/override_protocol'
         | 
| 25 24 |  | 
| 26 25 | 
             
            module OpenC3
         | 
| 27 26 | 
             
              # An interface class that provides simulated telemetry and command responses
         | 
| @@ -38,7 +37,6 @@ module OpenC3 | |
| 38 37 | 
             
                  @sim_target = nil
         | 
| 39 38 | 
             
                  @write_raw_allowed = false
         | 
| 40 39 | 
             
                  @raw_logger_pair = nil
         | 
| 41 | 
            -
                  add_protocol(OverrideProtocol, [], :READ)
         | 
| 42 40 | 
             
                end
         | 
| 43 41 |  | 
| 44 42 | 
             
                # Initialize the simulated target object and "connect" to the target
         | 
| @@ -58,8 +58,6 @@ module OpenC3 | |
| 58 58 | 
             
                attr_accessor :raw_logger_pair
         | 
| 59 59 | 
             
                # @return [String] The ip address to bind to.  Default to ANY (0.0.0.0)
         | 
| 60 60 | 
             
                attr_accessor :listen_address
         | 
| 61 | 
            -
                # @return [boolean] Automatically send SYSTEM META on connect - Default false - Can be CMD/TLM
         | 
| 62 | 
            -
                attr_accessor :auto_system_meta
         | 
| 63 61 |  | 
| 64 62 | 
             
                # @param write_port [Integer] The server write port. Clients should connect
         | 
| 65 63 | 
             
                #   and expect to receive data from this port.
         | 
| @@ -114,7 +112,6 @@ module OpenC3 | |
| 114 112 | 
             
                  @raw_logging_enabled = false
         | 
| 115 113 | 
             
                  @connection_mutex = Mutex.new
         | 
| 116 114 | 
             
                  @listen_address = "0.0.0.0"
         | 
| 117 | 
            -
                  @auto_system_meta = false
         | 
| 118 115 |  | 
| 119 116 | 
             
                  @read_allowed = false unless ConfigParser.handle_nil(read_port)
         | 
| 120 117 | 
             
                  @write_allowed = false unless ConfigParser.handle_nil(write_port)
         | 
| @@ -282,15 +279,12 @@ module OpenC3 | |
| 282 279 |  | 
| 283 280 | 
             
                # Supported Options
         | 
| 284 281 | 
             
                # LISTEN_ADDRESS - Ip address of the interface to accept connections on - Default: 0.0.0.0
         | 
| 285 | 
            -
                # AUTO_SYSTEM_META - Automatically send SYSTEM META on connect - Default false
         | 
| 286 282 | 
             
                # (see Interface#set_option)
         | 
| 287 283 | 
             
                def set_option(option_name, option_values)
         | 
| 288 284 | 
             
                  super(option_name, option_values)
         | 
| 289 285 | 
             
                  case option_name.upcase
         | 
| 290 286 | 
             
                  when 'LISTEN_ADDRESS'
         | 
| 291 287 | 
             
                    @listen_address = option_values[0]
         | 
| 292 | 
            -
                  when 'AUTO_SYSTEM_META'
         | 
| 293 | 
            -
                    @auto_system_meta = ConfigParser.handle_true_false(option_values[0])
         | 
| 294 288 | 
             
                  end
         | 
| 295 289 | 
             
                end
         | 
| 296 290 |  | 
| @@ -411,11 +405,6 @@ module OpenC3 | |
| 411 405 | 
             
                  interface.connect
         | 
| 412 406 |  | 
| 413 407 | 
             
                  if listen_write
         | 
| 414 | 
            -
                    if @auto_system_meta
         | 
| 415 | 
            -
                      meta_packet = System.telemetry.packet('SYSTEM', 'META').clone
         | 
| 416 | 
            -
                      interface.write(meta_packet)
         | 
| 417 | 
            -
                    end
         | 
| 418 | 
            -
             | 
| 419 408 | 
             
                    @write_connection_callback.call(interface) if @write_connection_callback
         | 
| 420 409 | 
             
                    @connection_mutex.synchronize do
         | 
| 421 410 | 
             
                      @write_interface_infos << InterfaceInfo.new(interface, hostname, host_ip, port)
         |