rsmp 0.29.0 → 0.31.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +6 -6
- data/documentation/collecting_message.md +25 -10
- data/documentation/message_distribution.md +8 -8
- data/lib/rsmp/collect/ack_collector.rb +7 -5
- data/lib/rsmp/collect/aggregated_status_collector.rb +4 -2
- data/lib/rsmp/collect/alarm_collector.rb +10 -10
- data/lib/rsmp/collect/{alarm_query.rb → alarm_matcher.rb} +2 -2
- data/lib/rsmp/collect/collector.rb +69 -66
- data/lib/rsmp/collect/{command_query.rb → command_matcher.rb} +2 -2
- data/lib/rsmp/collect/command_response_collector.rb +3 -3
- data/lib/rsmp/collect/distributor.rb +65 -0
- data/lib/rsmp/collect/filter.rb +10 -5
- data/lib/rsmp/collect/{query.rb → matcher.rb} +1 -1
- data/lib/rsmp/collect/queue.rb +39 -0
- data/lib/rsmp/collect/receiver.rb +40 -0
- data/lib/rsmp/collect/state_collector.rb +57 -57
- data/lib/rsmp/collect/status_collector.rb +9 -6
- data/lib/rsmp/collect/{status_query.rb → status_matcher.rb} +2 -2
- data/lib/rsmp/component.rb +4 -0
- data/lib/rsmp/node.rb +1 -1
- data/lib/rsmp/proxy.rb +17 -17
- data/lib/rsmp/site_proxy.rb +4 -4
- data/lib/rsmp/supervisor.rb +4 -4
- data/lib/rsmp/supervisor_proxy.rb +2 -1
- data/lib/rsmp/tlc/signal_priority.rb +19 -3
- data/lib/rsmp/tlc/traffic_controller.rb +28 -14
- data/lib/rsmp/tlc/traffic_controller_site.rb +1 -1
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +7 -6
- metadata +9 -8
- data/lib/rsmp/collect/listener.rb +0 -23
- data/lib/rsmp/collect/notifier.rb +0 -65
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: fd4daeeb9fb712ab275d4b0711a6ea69e766124a2fcd55ea955eeb1c46f05ed3
         | 
| 4 | 
            +
              data.tar.gz: a47c38c49b46fb2ad412ced388f6e654653292dd439ea31d2bbaf33988f024f0
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: ec4a4057b53a27d12e1c81af4623b03bcd7cf93b39bad72f1d228c1a0dfd74495541abae496a3fa783c159a16df2e161eb0f5fbaa13d96daa8d8c99a847a0f9f
         | 
| 7 | 
            +
              data.tar.gz: 704c11a6d854a0fb48f9cb6d84514ac40791bb85fe56186b9ff217e876458be6a214fbb69b8c3aaf09db35bd334345f7ca6ee31da8f9fbf717a3ae790e77252a
         | 
    
        data/Gemfile.lock
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            PATH
         | 
| 2 2 | 
             
              remote: .
         | 
| 3 3 | 
             
              specs:
         | 
| 4 | 
            -
                rsmp (0. | 
| 4 | 
            +
                rsmp (0.31.0)
         | 
| 5 5 | 
             
                  async (~> 2.12.0)
         | 
| 6 6 | 
             
                  async-io (~> 1.43.0)
         | 
| 7 7 | 
             
                  colorize (~> 1.1)
         | 
| @@ -16,16 +16,16 @@ GEM | |
| 16 16 | 
             
                  cucumber (>= 8.0, < 10.0)
         | 
| 17 17 | 
             
                  rspec-expectations (~> 3.4)
         | 
| 18 18 | 
             
                  thor (~> 1.0)
         | 
| 19 | 
            -
                async (2.12. | 
| 19 | 
            +
                async (2.12.1)
         | 
| 20 20 | 
             
                  console (~> 1.25, >= 1.25.2)
         | 
| 21 21 | 
             
                  fiber-annotation
         | 
| 22 | 
            -
                  io-event (~> 1.6)
         | 
| 22 | 
            +
                  io-event (~> 1.6, >= 1.6.5)
         | 
| 23 23 | 
             
                async-io (1.43.2)
         | 
| 24 24 | 
             
                  async
         | 
| 25 25 | 
             
                bigdecimal (3.1.8)
         | 
| 26 26 | 
             
                builder (3.3.0)
         | 
| 27 27 | 
             
                colorize (1.1.0)
         | 
| 28 | 
            -
                console (1. | 
| 28 | 
            +
                console (1.27.0)
         | 
| 29 29 | 
             
                  fiber-annotation
         | 
| 30 30 | 
             
                  fiber-local (~> 1.1)
         | 
| 31 31 | 
             
                  json
         | 
| @@ -64,9 +64,9 @@ GEM | |
| 64 64 | 
             
                fiber-annotation (0.2.0)
         | 
| 65 65 | 
             
                fiber-local (1.1.0)
         | 
| 66 66 | 
             
                  fiber-storage
         | 
| 67 | 
            -
                fiber-storage (0. | 
| 67 | 
            +
                fiber-storage (1.0.0)
         | 
| 68 68 | 
             
                hana (1.3.7)
         | 
| 69 | 
            -
                io-event (1.6. | 
| 69 | 
            +
                io-event (1.6.5)
         | 
| 70 70 | 
             
                json (2.7.2)
         | 
| 71 71 | 
             
                json_schemer (2.3.0)
         | 
| 72 72 | 
             
                  bigdecimal
         | 
| @@ -3,30 +3,45 @@ You often need to collect messages or responses. The collector classes are used | |
| 3 3 |  | 
| 4 4 | 
             
            A collector can collect ingoing and/or outgoing messages.
         | 
| 5 5 |  | 
| 6 | 
            -
            An object that includes the  | 
| 6 | 
            +
            An object that includes the Distributor module (or implements the same functionality) must be provided when you construct a Collected. The collector will attach itself to this distributor when it starts collecting, to receive messages. The SiteProxy and SupervisorProxy classes both include the Distributor module, and can therefore be used as message sources.
         | 
| 7 7 |  | 
| 8 8 | 
             
            Messages that match the relevant criteria are stored by the collector.
         | 
| 9 9 |  | 
| 10 | 
            -
            When the collection is done, the collector detaches from the  | 
| 10 | 
            +
            When the collection is done, the collector detaches from the distributor, and returns the status.
         | 
| 11 11 |  | 
| 12 12 |  | 
| 13 13 | 
             
            ## Collector
         | 
| 14 | 
            -
            Class  | 
| 14 | 
            +
            Class used for collecting messages filtered by message type, direction and/or component id. A block can be used for custom filtering.
         | 
| 15 15 |  | 
| 16 16 | 
             
            You can choose to collect a specific number of message and/or for a specific duration.
         | 
| 17 17 |  | 
| 18 18 | 
             
            A collector has a status, which is `:ready` initialialy. When you start collecting, it changes to `:collecting`. It will be `:ok` once collection completes successfully, or `:cancel` if it was cancelled to to some error or by a filter block.
         | 
| 19 19 |  | 
| 20 20 | 
             
            ### Initialization
         | 
| 21 | 
            -
            When you create a collector, you specify the messages types you want to collect.
         | 
| 22 | 
            -
            You can also specify ingoing and/or outgoing direction and the RSMP component.
         | 
| 21 | 
            +
            When you create a collector, you provide a Filter to specify the messages types you want to collect. You can also specify ingoing and/or outgoing direction and the RSMP component.
         | 
| 23 22 |  | 
| 24 23 | 
             
            ```ruby
         | 
| 25 | 
            -
            collector = MessageCollector.new | 
| 24 | 
            +
            collector = MessageCollector.new(distributor,
         | 
| 25 | 
            +
            	num: 10,
         | 
| 26 | 
            +
            	filter: Filter.new(ingoing: true, outgoing: true)
         | 
| 26 27 | 
             
            ```
         | 
| 27 28 |  | 
| 28 29 | 
             
            num: The number of messages to collect. If not provided, a timeout must be set instead.
         | 
| 29 | 
            -
             | 
| 30 | 
            +
            filter: filter to identify the types of messages to look for.
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            ### Filter
         | 
| 33 | 
            +
            The Filter class is used to filter messages according to message type, direction and component.
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            ```ruby
         | 
| 36 | 
            +
            filter = Filter.new(
         | 
| 37 | 
            +
            	type: 'Alarm',
         | 
| 38 | 
            +
            	ingoing: true,
         | 
| 39 | 
            +
            	outgoing: false,
         | 
| 40 | 
            +
            	component: 'DL1'
         | 
| 41 | 
            +
            	)
         | 
| 42 | 
            +
            ```
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            type: a string, or an array of string, specifiying one or more RSMP message types.
         | 
| 30 45 | 
             
            ingoing: Whether to collect ingoing messages. Defaults to true
         | 
| 31 46 | 
             
            outgoing: Whether to collect outgoing messages. Defaults to true
         | 
| 32 47 | 
             
            component: An RSMP component id.
         | 
| @@ -48,7 +63,7 @@ result = collector.wait | |
| 48 63 | 
             
            ```
         | 
| 49 64 |  | 
| 50 65 | 
             
            ### Custom filtering
         | 
| 51 | 
            -
            You can use a block to do extra filtering. The block will be callled for each messages that  | 
| 66 | 
            +
            You can use a block to do extra filtering. The block will be callled for each messages that passes the Filter provided when initializing the collector.
         | 
| 52 67 |  | 
| 53 68 | 
             
            The block must return nil or a list of symbols to indicate whether the message should be kept, and whether collection should be cancelled.
         | 
| 54 69 |  | 
| @@ -69,7 +84,7 @@ Exceptions in the block will cause the collector to abort. If the collect! or wa | |
| 69 84 | 
             
            The method collect!() will raise exceptions in case of errors, and will return the collect message directly.
         | 
| 70 85 |  | 
| 71 86 | 
             
            ```ruby
         | 
| 72 | 
            -
            message = collector.collect # => collected message.
         | 
| 87 | 
            +
            message = collector.collect! # => collected message.
         | 
| 73 88 | 
             
            ```
         | 
| 74 89 |  | 
| 75 90 | 
             
            Similar, `wait!()` will raise an exception in case of timeouts or errors:
         | 
| @@ -181,7 +196,7 @@ result[:collector].messages # => list of collected messages | |
| 181 196 |  | 
| 182 197 | 
             
            ### Processing responses
         | 
| 183 198 | 
             
            If you pass a block, the block will be used to construct a collector. The block will be called for each matching  status item received.
         | 
| 184 | 
            -
            Collection will continue until the block returns :cancel, or it times.
         | 
| 199 | 
            +
            Collection will continue until the block returns :cancel, or it times out.
         | 
| 185 200 |  | 
| 186 201 | 
             
            ```ruby
         | 
| 187 202 | 
             
            options = {
         | 
| @@ -1,23 +1,23 @@ | |
| 1 1 | 
             
            # Message distribution
         | 
| 2 2 |  | 
| 3 | 
            -
            Proxy - -  | 
| 3 | 
            +
            Proxy - - Distributor --> Receivers
         | 
| 4 4 |  | 
| 5 | 
            -
            A proxy distributes messages to  | 
| 5 | 
            +
            A proxy distributes messages to receivers, when they are installed.
         | 
| 6 6 |  | 
| 7 | 
            -
            Collectors are special  | 
| 7 | 
            +
            Collectors are special receiverws that waits for specific message, and are used to implement methods for waiting for RMSP responses, statuses, alarms, etc.
         | 
| 8 8 |  | 
| 9 | 
            -
            Note that Archive is not a  | 
| 9 | 
            +
            Note that Archive is not a receiver, and does not receive messages via the Distributor. Instead the Archive gets and stores messages via the log() interface in the Logging module. The reason is that the items that the Archive and the Logger contain other data as well as the message, like error messages, warnings, text descriptions, colors codes, etc. The Distributor and Receiver handles only Message objects.
         | 
| 10 10 |  | 
| 11 | 
            -
            ##  | 
| 11 | 
            +
            ## Distributor
         | 
| 12 12 | 
             
            A module that handles distributing messages to receivers.
         | 
| 13 13 |  | 
| 14 | 
            -
            ##  | 
| 14 | 
            +
            ## Receiver
         | 
| 15 15 | 
             
            Receives messages as long as it's installed into a distributor.
         | 
| 16 16 |  | 
| 17 17 | 
             
            ## Collector
         | 
| 18 | 
            -
             | 
| 18 | 
            +
            Includes the Receiver module to wait for specific messages. Once received
         | 
| 19 19 | 
             
            the client receives the collection.
         | 
| 20 20 |  | 
| 21 21 | 
             
            ## Proxy
         | 
| 22 | 
            -
            A proxy includes the  | 
| 22 | 
            +
            A proxy includes the Distributor module and distributes each message to receivers after processing it.
         | 
| 23 23 |  | 
| @@ -3,14 +3,16 @@ module RSMP | |
| 3 3 | 
             
              class AckCollector < Collector
         | 
| 4 4 | 
             
                def initialize proxy, options={}
         | 
| 5 5 | 
             
                  raise ArgumentError.new("m_id must be provided") unless options[:m_id]
         | 
| 6 | 
            -
                   | 
| 7 | 
            -
             | 
| 6 | 
            +
                  super proxy, options.merge(
         | 
| 7 | 
            +
                    filter: RSMP::Filter.new(ingoing: true, outgoing: false, type: 'MessageAck'),
         | 
| 8 | 
            +
                    num: 1,
         | 
| 9 | 
            +
                    title: 'message acknowledgement'
         | 
| 10 | 
            +
                  )
         | 
| 8 11 | 
             
                end
         | 
| 9 12 |  | 
| 10 13 | 
             
                # Check if we the MessageAck related to initiating request, identified by @m_id.
         | 
| 11 | 
            -
                def  | 
| 12 | 
            -
                   | 
| 13 | 
            -
                  return message.attribute('oMId') == @options[:m_id]
         | 
| 14 | 
            +
                def acceptable? message
         | 
| 15 | 
            +
                  super(message) && message.attribute('oMId') == @m_id
         | 
| 14 16 | 
             
                end
         | 
| 15 17 | 
             
              end
         | 
| 16 18 | 
             
            end
         | 
| @@ -2,8 +2,10 @@ module RSMP | |
| 2 2 | 
             
              # Class for waiting for an aggregated status response
         | 
| 3 3 | 
             
              class AggregatedStatusCollector < Collector
         | 
| 4 4 | 
             
                def initialize proxy, options={}
         | 
| 5 | 
            -
                   | 
| 6 | 
            -
             | 
| 5 | 
            +
                  super proxy, options.merge(
         | 
| 6 | 
            +
                    filter: RSMP::Filter.new(ingoing: true, outgoing: false, type: 'AggregatedStatus'),
         | 
| 7 | 
            +
                    title: 'aggregated status'
         | 
| 8 | 
            +
                  )
         | 
| 7 9 | 
             
                end
         | 
| 8 10 | 
             
              end
         | 
| 9 11 | 
             
            end
         | 
| @@ -2,20 +2,20 @@ module RSMP | |
| 2 2 | 
             
              # Class for waiting for specific command responses
         | 
| 3 3 | 
             
              class AlarmCollector < Collector
         | 
| 4 4 | 
             
                def initialize proxy,options={}
         | 
| 5 | 
            -
                  @ | 
| 5 | 
            +
                  @matcher = options[:matcher] || {}
         | 
| 6 6 | 
             
                  super proxy, options.merge(
         | 
| 7 | 
            -
                    type: 'Alarm',
         | 
| 7 | 
            +
                    filter: RSMP::Filter.new(ingoing: true, outgoing: false, type: 'Alarm'),
         | 
| 8 8 | 
             
                    title:'alarm'
         | 
| 9 9 | 
             
                  )
         | 
| 10 10 | 
             
                end
         | 
| 11 11 |  | 
| 12 12 | 
             
                # match alarm attributes
         | 
| 13 | 
            -
                def  | 
| 13 | 
            +
                def acceptable? message
         | 
| 14 14 | 
             
                  return false if super(message) == false
         | 
| 15 15 |  | 
| 16 16 | 
             
                  # match fixed attributes
         | 
| 17 17 | 
             
                  %w{cId aCId aSp ack aS sS cat pri}.each do |key|
         | 
| 18 | 
            -
                    want = @ | 
| 18 | 
            +
                    want = @matcher[key]
         | 
| 19 19 | 
             
                    got = message.attribute(key)
         | 
| 20 20 | 
             
                    case want
         | 
| 21 21 | 
             
                    when Regexp
         | 
| @@ -26,13 +26,13 @@ module RSMP | |
| 26 26 | 
             
                  end
         | 
| 27 27 |  | 
| 28 28 | 
             
                  # match rvs items
         | 
| 29 | 
            -
                  if @ | 
| 30 | 
            -
                     | 
| 29 | 
            +
                  if @matcher['rvs']
         | 
| 30 | 
            +
                    matcher_rvs = @matcher['rvs']
         | 
| 31 31 | 
             
                    message_rvs = message.attributes['rvs']
         | 
| 32 32 | 
             
                    return false unless message_rvs
         | 
| 33 | 
            -
                    return false unless  | 
| 33 | 
            +
                    return false unless matcher_rvs.all? do |matcher_item|
         | 
| 34 34 | 
             
                      return false unless message_rvs.any? do |message_item|
         | 
| 35 | 
            -
                        next message_item['n'] ==  | 
| 35 | 
            +
                        next message_item['n'] == matcher_item['n'] && message_item['v'] == matcher_item['v']
         | 
| 36 36 | 
             
                      end
         | 
| 37 37 | 
             
                      next true
         | 
| 38 38 | 
             
                    end
         | 
| @@ -41,8 +41,8 @@ module RSMP | |
| 41 41 | 
             
                end
         | 
| 42 42 |  | 
| 43 43 | 
             
                # return a string that describes what we're collecting
         | 
| 44 | 
            -
                def  | 
| 45 | 
            -
                  "#{describe_num_and_type} #{ {component: @options[:component]}.merge(@ | 
| 44 | 
            +
                def describe_matcher
         | 
| 45 | 
            +
                  "#{describe_num_and_type} #{ {component: @options[:component]}.merge(@matcher).compact }"
         | 
| 46 46 | 
             
                end
         | 
| 47 47 |  | 
| 48 48 | 
             
              end
         | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            module RSMP
         | 
| 2 2 | 
             
              # Match a specific alarm
         | 
| 3 | 
            -
              class  | 
| 4 | 
            -
                # Match an alarm value against a  | 
| 3 | 
            +
              class AlarmMatcher < Matcher
         | 
| 4 | 
            +
                # Match an alarm value against a matcher
         | 
| 5 5 | 
             
                def match? item
         | 
| 6 6 | 
             
                  return false if @want['n'] && @want['n'] != item['n']
         | 
| 7 7 | 
             
                  if @want['v'].is_a? Regexp
         | 
| @@ -1,40 +1,53 @@ | |
| 1 1 | 
             
            module RSMP
         | 
| 2 2 |  | 
| 3 | 
            -
              # Collects messages from a  | 
| 3 | 
            +
              # Collects messages from a distributor.
         | 
| 4 4 | 
             
              # Can filter by message type, componet and direction.
         | 
| 5 5 | 
             
              # Wakes up the once the desired number of messages has been collected.
         | 
| 6 | 
            -
              class Collector | 
| 7 | 
            -
                 | 
| 6 | 
            +
              class Collector
         | 
| 7 | 
            +
                include Receiver
         | 
| 8 | 
            +
                attr_reader :condition, :messages, :status, :error, :task, :m_id
         | 
| 8 9 |  | 
| 9 | 
            -
                def initialize  | 
| 10 | 
            -
                   | 
| 10 | 
            +
                def initialize distributor, options={}
         | 
| 11 | 
            +
                  initialize_receiver distributor, filter: options[:filter]
         | 
| 11 12 | 
             
                  @options = {
         | 
| 12 13 | 
             
                    cancel: {
         | 
| 13 14 | 
             
                      schema_error: true,
         | 
| 14 15 | 
             
                      disconnect: false,
         | 
| 15 16 | 
             
                    }
         | 
| 16 17 | 
             
                  }.deep_merge options
         | 
| 17 | 
            -
                  @ | 
| 18 | 
            -
                  @ | 
| 18 | 
            +
                  @timeout = options[:timeout]
         | 
| 19 | 
            +
                  @num = options[:num]
         | 
| 20 | 
            +
                  @m_id = options[:m_id]
         | 
| 19 21 | 
             
                  @condition = Async::Notification.new
         | 
| 20 | 
            -
                   | 
| 21 | 
            -
                   | 
| 22 | 
            -
             | 
| 22 | 
            +
                  make_title options[:title]
         | 
| 23 | 
            +
                  
         | 
| 24 | 
            +
                  if task
         | 
| 25 | 
            +
                    @task = task
         | 
| 23 26 | 
             
                  else
         | 
| 24 | 
            -
                     # if  | 
| 27 | 
            +
                     # if distributor is a Proxy, or some other object that implements task(),
         | 
| 25 28 | 
             
                     # then try to get the task that way
         | 
| 26 | 
            -
                    if  | 
| 27 | 
            -
                      @task =  | 
| 29 | 
            +
                    if distributor.respond_to? 'task'
         | 
| 30 | 
            +
                      @task = distributor.task
         | 
| 28 31 | 
             
                    end
         | 
| 29 32 | 
             
                  end
         | 
| 30 33 | 
             
                  reset
         | 
| 31 34 | 
             
                end
         | 
| 32 35 |  | 
| 36 | 
            +
                def make_title title
         | 
| 37 | 
            +
                  if title
         | 
| 38 | 
            +
                    @title = title
         | 
| 39 | 
            +
                  elsif @filter
         | 
| 40 | 
            +
                    @title = [@filter.type].flatten.join('/')
         | 
| 41 | 
            +
                  else
         | 
| 42 | 
            +
                    @title = ""
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 33 46 | 
             
                def use_task task
         | 
| 34 47 | 
             
                  @task = task
         | 
| 35 48 | 
             
                end
         | 
| 36 49 |  | 
| 37 | 
            -
                # Clear all  | 
| 50 | 
            +
                # Clear all matcher results
         | 
| 38 51 | 
             
                def reset
         | 
| 39 52 | 
             
                  @messages = []
         | 
| 40 53 | 
             
                  @error = nil
         | 
| @@ -95,7 +108,7 @@ module RSMP | |
| 95 108 | 
             
                  wait
         | 
| 96 109 | 
             
                  @status
         | 
| 97 110 | 
             
                ensure
         | 
| 98 | 
            -
                  @ | 
| 111 | 
            +
                  @distributor.remove_receiver self if @distributor
         | 
| 99 112 | 
             
                end
         | 
| 100 113 |  | 
| 101 114 | 
             
                # Collect message
         | 
| @@ -110,8 +123,8 @@ module RSMP | |
| 110 123 | 
             
                # the desired messages have been collected, or timeout is reached.
         | 
| 111 124 | 
             
                def wait
         | 
| 112 125 | 
             
                  if collecting?
         | 
| 113 | 
            -
                    if @ | 
| 114 | 
            -
                      @task.with_timeout(@ | 
| 126 | 
            +
                    if @timeout
         | 
| 127 | 
            +
                      @task.with_timeout(@timeout) { @condition.wait }
         | 
| 115 128 | 
             
                    else
         | 
| 116 129 | 
             
                      @condition.wait
         | 
| 117 130 | 
             
                    end
         | 
| @@ -136,39 +149,41 @@ module RSMP | |
| 136 149 | 
             
                def start &block
         | 
| 137 150 | 
             
                  raise RuntimeError.new("Can't start collectimng unless ready (currently #{@status})") unless ready?
         | 
| 138 151 | 
             
                  @block = block
         | 
| 139 | 
            -
                  raise ArgumentError.new("Num, timeout or block must be provided") unless @ | 
| 152 | 
            +
                  raise ArgumentError.new("Num, timeout or block must be provided") unless @num || @timeout || @block
         | 
| 140 153 | 
             
                  reset
         | 
| 141 154 | 
             
                  @status = :collecting
         | 
| 142 155 | 
             
                  log_start
         | 
| 143 | 
            -
                  @ | 
| 156 | 
            +
                  @distributor.add_receiver self if @distributor
         | 
| 144 157 | 
             
                end
         | 
| 145 158 |  | 
| 146 159 | 
             
                # Build a string describing how how progress reached before timeout
         | 
| 147 160 | 
             
                def describe_progress
         | 
| 148 161 | 
             
                  str = "#{identifier}: #{@title.capitalize} collection "
         | 
| 149 | 
            -
                  str << "in response to #{@ | 
| 150 | 
            -
                  str << "didn't complete within #{@ | 
| 151 | 
            -
                  str << "reached #{@messages.size}/#{@ | 
| 162 | 
            +
                  str << "in response to #{@m_id} " if @m_id
         | 
| 163 | 
            +
                  str << "didn't complete within #{@timeout}s, "
         | 
| 164 | 
            +
                  str << "reached #{@messages.size}/#{@num}"
         | 
| 152 165 | 
             
                  str
         | 
| 153 166 | 
             
                end
         | 
| 154 167 |  | 
| 155 168 | 
             
                # Check if we receive a NotAck related to initiating request, identified by @m_id.
         | 
| 156 169 | 
             
                def reject_not_ack message
         | 
| 157 | 
            -
                  return unless @ | 
| 170 | 
            +
                  return unless @m_id
         | 
| 158 171 | 
             
                  if message.is_a?(MessageNotAck)
         | 
| 159 | 
            -
                    if message.attribute('oMId') == @ | 
| 160 | 
            -
                      m_id_short = RSMP::Message.shorten_m_id @ | 
| 172 | 
            +
                    if message.attribute('oMId') == @m_id
         | 
| 173 | 
            +
                      m_id_short = RSMP::Message.shorten_m_id @m_id, 8
         | 
| 161 174 | 
             
                      cancel RSMP::MessageRejected.new("#{@title} #{m_id_short} was rejected with '#{message.attribute('rea')}'")
         | 
| 162 | 
            -
                      @ | 
| 175 | 
            +
                      @distributor.log "#{identifier}: cancelled due to a NotAck", level: :debug
         | 
| 163 176 | 
             
                      true
         | 
| 164 177 | 
             
                    end
         | 
| 165 178 | 
             
                  end
         | 
| 166 179 | 
             
                end
         | 
| 167 180 |  | 
| 168 181 | 
             
                # Handle message. and return true when we're done collecting
         | 
| 169 | 
            -
                def  | 
| 182 | 
            +
                def receive message
         | 
| 170 183 | 
             
                  raise ArgumentError unless message
         | 
| 171 | 
            -
                   | 
| 184 | 
            +
                  unless ready? || collecting?
         | 
| 185 | 
            +
                    raise RuntimeError.new("can't process message when status is :#{@status}, title: #{@title}, desc: #{describe}") 
         | 
| 186 | 
            +
                  end
         | 
| 172 187 | 
             
                  if perform_match message
         | 
| 173 188 | 
             
                    if done?
         | 
| 174 189 | 
             
                      complete
         | 
| @@ -185,8 +200,8 @@ module RSMP | |
| 185 200 | 
             
                # Match message against our collection criteria
         | 
| 186 201 | 
             
                def perform_match message
         | 
| 187 202 | 
             
                  return false if reject_not_ack(message)
         | 
| 188 | 
            -
                  return false unless  | 
| 189 | 
            -
                  #@ | 
| 203 | 
            +
                  return false unless acceptable?(message)
         | 
| 204 | 
            +
                  #@distributor.log "#{identifier}: Looking at #{message.type} #{message.m_id_short}", level: :collect
         | 
| 190 205 | 
             
                  if @block
         | 
| 191 206 | 
             
                    status = [@block.call(message)].flatten
         | 
| 192 207 | 
             
                    return unless collecting?
         | 
| @@ -198,10 +213,10 @@ module RSMP | |
| 198 213 |  | 
| 199 214 | 
             
                # Have we collected the required number of messages?
         | 
| 200 215 | 
             
                def done?
         | 
| 201 | 
            -
                  @ | 
| 216 | 
            +
                  @num && @messages.size >= @num
         | 
| 202 217 | 
             
                end
         | 
| 203 218 |  | 
| 204 | 
            -
                # Called when we're done collecting. Remove ourself as a  | 
| 219 | 
            +
                # Called when we're done collecting. Remove ourself as a receiver,
         | 
| 205 220 | 
             
                # se we don't receive message notifications anymore
         | 
| 206 221 | 
             
                def complete
         | 
| 207 222 | 
             
                  @status = :ok
         | 
| @@ -214,39 +229,39 @@ module RSMP | |
| 214 229 | 
             
                  log_incomplete
         | 
| 215 230 | 
             
                end
         | 
| 216 231 |  | 
| 217 | 
            -
                # Remove ourself as a  | 
| 232 | 
            +
                # Remove ourself as a receiver, so we don't receive message notifications anymore,
         | 
| 218 233 | 
             
                # and wake up the async condition
         | 
| 219 234 | 
             
                def do_stop
         | 
| 220 | 
            -
                  @ | 
| 235 | 
            +
                  @distributor.remove_receiver self
         | 
| 221 236 | 
             
                  @condition.signal
         | 
| 222 237 | 
             
                end
         | 
| 223 238 |  | 
| 224 239 | 
             
                # An error occured upstream.
         | 
| 225 240 | 
             
                # Check if we should cancel.
         | 
| 226 | 
            -
                def  | 
| 241 | 
            +
                def receive_error error, options={}
         | 
| 227 242 | 
             
                  case error
         | 
| 228 243 | 
             
                  when RSMP::SchemaError
         | 
| 229 | 
            -
                     | 
| 244 | 
            +
                    receive_schema_error error, options
         | 
| 230 245 | 
             
                  when RSMP::DisconnectError
         | 
| 231 | 
            -
                     | 
| 246 | 
            +
                    receive_disconnect error, options
         | 
| 232 247 | 
             
                  end
         | 
| 233 248 | 
             
                end
         | 
| 234 249 |  | 
| 235 250 | 
             
                # Cancel if we received e schema error for a message type we're collecting
         | 
| 236 | 
            -
                def  | 
| 251 | 
            +
                def receive_schema_error error, options
         | 
| 237 252 | 
             
                  return unless @options.dig(:cancel,:schema_error)
         | 
| 238 253 | 
             
                  message = options[:message]
         | 
| 239 254 | 
             
                  return unless message
         | 
| 240 255 | 
             
                  klass = message.class.name.split('::').last
         | 
| 241 | 
            -
                  return unless @ | 
| 242 | 
            -
                  @ | 
| 256 | 
            +
                  return unless @filter&.type == nil || [@filter&.type].flatten.include?(klass)
         | 
| 257 | 
            +
                  @distributor.log "#{identifier}: cancelled due to schema error in #{klass} #{message.m_id_short}", level: :debug
         | 
| 243 258 | 
             
                  cancel error
         | 
| 244 259 | 
             
                end
         | 
| 245 260 |  | 
| 246 261 | 
             
                # Cancel if we received e notificaiton about a disconnect
         | 
| 247 | 
            -
                def  | 
| 262 | 
            +
                def receive_disconnect error, options
         | 
| 248 263 | 
             
                  return unless @options.dig(:cancel,:disconnect)
         | 
| 249 | 
            -
                  @ | 
| 264 | 
            +
                  @distributor.log "#{identifier}: cancelled due to a connection error: #{error.to_s}", level: :debug
         | 
| 250 265 | 
             
                  cancel error
         | 
| 251 266 | 
             
                end
         | 
| 252 267 |  | 
| @@ -264,39 +279,27 @@ module RSMP | |
| 264 279 |  | 
| 265 280 | 
             
                # Check a message against our match criteria
         | 
| 266 281 | 
             
                # Return true if there's a match, false if not
         | 
| 267 | 
            -
                def  | 
| 268 | 
            -
                   | 
| 269 | 
            -
                  return false if message.direction == :out && @outgoing == false
         | 
| 270 | 
            -
                  if @options[:type]
         | 
| 271 | 
            -
                    if @options[:type].is_a? Array
         | 
| 272 | 
            -
                      return false unless @options[:type].include? message.type
         | 
| 273 | 
            -
                    else
         | 
| 274 | 
            -
                      return false unless message.type == @options[:type]
         | 
| 275 | 
            -
                    end
         | 
| 276 | 
            -
                  end
         | 
| 277 | 
            -
                  if @options[:component]
         | 
| 278 | 
            -
                    return false if message.attributes['cId'] && message.attributes['cId'] != @options[:component]
         | 
| 279 | 
            -
                  end
         | 
| 280 | 
            -
                  true
         | 
| 282 | 
            +
                def acceptable? message
         | 
| 283 | 
            +
                  @filter == nil || @filter.accept?(message)
         | 
| 281 284 | 
             
                end
         | 
| 282 285 |  | 
| 283 286 | 
             
                # return a string describing the types of messages we're collecting
         | 
| 284 287 | 
             
                def describe_types
         | 
| 285 | 
            -
                  [@ | 
| 288 | 
            +
                  [@filter&.type].flatten.join('/')
         | 
| 286 289 | 
             
                end
         | 
| 287 290 |  | 
| 288 291 | 
             
                # return a string that describes whe number of messages, and type of message we're collecting
         | 
| 289 292 | 
             
                def describe_num_and_type
         | 
| 290 | 
            -
                  if @ | 
| 291 | 
            -
                    "#{@ | 
| 293 | 
            +
                  if @num && @num > 1
         | 
| 294 | 
            +
                    "#{@num} #{describe_types}s"
         | 
| 292 295 | 
             
                  else
         | 
| 293 296 | 
             
                    describe_types
         | 
| 294 297 | 
             
                  end
         | 
| 295 298 | 
             
                end
         | 
| 296 299 |  | 
| 297 300 | 
             
                # return a string that describes the attributes that we're looking for
         | 
| 298 | 
            -
                def  | 
| 299 | 
            -
                  h = {component: @ | 
| 301 | 
            +
                def describe_matcher
         | 
| 302 | 
            +
                  h = {component: @filter&.component}.compact
         | 
| 300 303 | 
             
                  if h.empty?
         | 
| 301 304 | 
             
                    describe_num_and_type
         | 
| 302 305 | 
             
                  else
         | 
| @@ -306,8 +309,8 @@ module RSMP | |
| 306 309 |  | 
| 307 310 | 
             
                # return a string that describe how many many messages have been collected
         | 
| 308 311 | 
             
                def describe_progress
         | 
| 309 | 
            -
                  if @ | 
| 310 | 
            -
                    "#{@messages.size} of #{@ | 
| 312 | 
            +
                  if @num
         | 
| 313 | 
            +
                    "#{@messages.size} of #{@num} message#{'s' if @messages.size!=1} collected"
         | 
| 311 314 | 
             
                  else
         | 
| 312 315 | 
             
                    "#{@messages.size} message#{'s' if @messages.size!=1} collected"
         | 
| 313 316 | 
             
                  end        
         | 
| @@ -315,17 +318,17 @@ module RSMP | |
| 315 318 |  | 
| 316 319 | 
             
                # log when we start collecting
         | 
| 317 320 | 
             
                def log_start
         | 
| 318 | 
            -
                  @ | 
| 321 | 
            +
                  @distributor.log "#{identifier}: Waiting for #{describe_matcher}".strip, level: :collect
         | 
| 319 322 | 
             
                end
         | 
| 320 323 |  | 
| 321 324 | 
             
                # log current progress
         | 
| 322 325 | 
             
                def log_incomplete
         | 
| 323 | 
            -
                  @ | 
| 326 | 
            +
                  @distributor.log "#{identifier}: #{describe_progress}", level: :collect
         | 
| 324 327 | 
             
                end
         | 
| 325 328 |  | 
| 326 329 | 
             
                # log when we end collecting
         | 
| 327 330 | 
             
                def log_complete
         | 
| 328 | 
            -
                  @ | 
| 331 | 
            +
                  @distributor.log "#{identifier}: Done", level: :collect
         | 
| 329 332 | 
             
                end
         | 
| 330 333 |  | 
| 331 334 | 
             
                # get a short id in hex format, identifying ourself
         | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            module RSMP
         | 
| 2 2 | 
             
              # Match a specific command responses
         | 
| 3 | 
            -
              class  | 
| 4 | 
            -
                # Match a return value item against a  | 
| 3 | 
            +
              class CommandMatcher < Matcher
         | 
| 4 | 
            +
                # Match a return value item against a matcher
         | 
| 5 5 | 
             
                def match? item
         | 
| 6 6 | 
             
                  return nil if @want['cCI'] && @want['cCI'] != item['cCI']
         | 
| 7 7 | 
             
                  return nil if @want['n'] && @want['n'] != item['n']
         | 
| @@ -3,13 +3,13 @@ module RSMP | |
| 3 3 | 
             
              class CommandResponseCollector < StateCollector
         | 
| 4 4 | 
             
                def initialize proxy, want, options={}
         | 
| 5 5 | 
             
                  super proxy, want, options.merge(
         | 
| 6 | 
            -
                    type: 'CommandResponse',
         | 
| 6 | 
            +
                    filter: RSMP::Filter.new(ingoing: true, outgoing: false, type: 'CommandResponse'),
         | 
| 7 7 | 
             
                    title:'command response'
         | 
| 8 8 | 
             
                  )
         | 
| 9 9 | 
             
                end
         | 
| 10 10 |  | 
| 11 | 
            -
                def  | 
| 12 | 
            -
                   | 
| 11 | 
            +
                def build_matcher want
         | 
| 12 | 
            +
                  CommandMatcher.new want
         | 
| 13 13 | 
             
                end
         | 
| 14 14 |  | 
| 15 15 | 
             
                # Get items, in our case the return values
         | 
| @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            # Distributes messages to receivers
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RSMP
         | 
| 4 | 
            +
              module Distributor
         | 
| 5 | 
            +
                attr_reader :receivers
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                include Inspect
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def inspect
         | 
| 10 | 
            +
                  "#<#{self.class.name}:#{self.object_id}, #{inspector(:@receivers)}>"
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def initialize_distributor
         | 
| 14 | 
            +
                  @receivers = []
         | 
| 15 | 
            +
                  @defer_distribution = false
         | 
| 16 | 
            +
                  @deferred_messages = []
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def clear_deferred_distribution &block
         | 
| 20 | 
            +
                  @deferred_messages = []
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def with_deferred_distribution &block
         | 
| 24 | 
            +
                  was, @defer_distribution = @defer_distribution, true
         | 
| 25 | 
            +
                  yield
         | 
| 26 | 
            +
                  distribute_queued
         | 
| 27 | 
            +
                ensure
         | 
| 28 | 
            +
                  @defer_distribution = was
         | 
| 29 | 
            +
                  @deferred_messages = []
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def distribute_queued
         | 
| 33 | 
            +
                  @deferred_messages.each { |message| distribute_immediately message }
         | 
| 34 | 
            +
                ensure
         | 
| 35 | 
            +
                  @deferred_messages = []
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def add_receiver receiver
         | 
| 39 | 
            +
                  raise ArgumentError unless receiver
         | 
| 40 | 
            +
                  @receivers << receiver unless @receivers.include? receiver
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def remove_receiver receiver
         | 
| 44 | 
            +
                  raise ArgumentError unless receiver
         | 
| 45 | 
            +
                  @receivers.delete receiver
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def distribute message
         | 
| 49 | 
            +
                  raise ArgumentError unless message
         | 
| 50 | 
            +
                  if @defer_distribution
         | 
| 51 | 
            +
                    @deferred_messages << message
         | 
| 52 | 
            +
                  else
         | 
| 53 | 
            +
                    distribute_immediately message
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def distribute_immediately message
         | 
| 58 | 
            +
                  @receivers.each { |receiver| receiver.receive message }
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def distribute_error error, options={}
         | 
| 62 | 
            +
                  @receivers.each { |receiver| receiver.receive_error error, options }
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
            end
         |