rsmp 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9614e6db6381449fa893901766de8239b7bc025c4454724709dc32b75551b5f3
4
- data.tar.gz: fce1a7fb067481f5ca199301bbd91953d0dd8d13f07e66ad365a5ffa24e58966
3
+ metadata.gz: 5316c50ad6cd09d30e7d1baab78e717fac4656c06178272ef4adc1c6647fb544
4
+ data.tar.gz: 6ef4d635b42f3eac19b94b68bbcb05c59ce94de96e0e27f1f5c7a6deefd4e470
5
5
  SHA512:
6
- metadata.gz: b942ee93f8185051dadac06576087ca8b27b11f8e3c653cb9638a72464941dd72c82c566ede50a46d2bffeb16c809345ef78b3edef7174a74d8ea75e963fa8f1
7
- data.tar.gz: 0052cb6afefed493e4ac4e2d48f4c587afe3f377ddabd2492fbeb3c223ff78e39b23ac7b01cd5e564abc3474a5f8f0b35b6edc93c24c453876597c478365ac2a
6
+ metadata.gz: 1947906505e532d0d47d93f1d9fe2c8c3199ff58d378b9be3df18865339e966a6ad5f74512aa168ee4a5d641624cf7b0f170436d2c11e4f495bd7d6b88be502f
7
+ data.tar.gz: ee0ecbb229637276d6cec21ccacc6414199ad7f0392324633231cb4a8632a8b64b1aa2ba5abd979a7699ed6721a5a40b9d9c531f45dc0f59345149662e608b12
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rsmp (0.8.0)
4
+ rsmp (0.8.1)
5
5
  async (~> 1.29.1)
6
6
  async-io (~> 1.32.1)
7
7
  colorize (~> 0.8.1)
@@ -19,9 +19,6 @@ Handle logging.
19
19
  ### Wait
20
20
  Handles waiting for an async condition and block.
21
21
 
22
- ### SiteProxyWait
23
- Handles waiting for different types of messages and responses from a remote site.
24
-
25
22
  ### Components
26
23
  Component handling.
27
24
 
@@ -38,7 +35,7 @@ A Site has one or more SupervisorProxies (connections to supervisor).
38
35
 
39
36
  A site has one of more components.
40
37
 
41
- ### Supervisor
38
+ ### Supervisor
42
39
  A Supervisor represents an RSMP supervisor, typically a central supervisor system. An RSMP supervisor can handle connections one or more sites.
43
40
 
44
41
  A Supervisor has one or more SiteProxies (connections to sites).
@@ -0,0 +1,196 @@
1
+ # Collection
2
+ You often need to collect messages or responses. The collector classes are used to collect message asyncronously. Other tasks continue until the collection completes, time outs or is cancelled.
3
+
4
+ A collector can collect ingoing and/or outgoing messages.
5
+
6
+ An object that includes the Notifier module (or implements the same functionality) must be provided when you construct a Collected. The collector will attach itself to this notifier when it starts collecting, to receive messages. The SiteProxy and SupervisorProxy classes both include the Notifier module, and can therefore be used as message sources.
7
+
8
+ Messages that match the relevant criteria are stored by the collector.
9
+
10
+ When the collection is done, the collector detaches from the notifier, and returns the status.
11
+
12
+
13
+ ## Collector
14
+ Class uses for collecting messages filtered by message type, direction and/or component id. A block can be used for custom filtering.
15
+
16
+ You can choose to collect a specific number of message and/or for a specific duration.
17
+
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
+
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.
23
+
24
+ ```ruby
25
+ collector = MessageCollector.new notifier, num: 10, ingoing: true, outgoing: true
26
+ ```
27
+
28
+ num: The number of messages to collect. If not provided, a timeout must be set instead.
29
+ timeout: The number of seconds to collect
30
+ ingoing: Whether to collect ingoing messages. Defaults to true
31
+ outgoing: Whether to collect outgoing messages. Defaults to true
32
+ component: An RSMP component id.
33
+
34
+ ### Collecting
35
+ Use collect() to start collecting and wait for completion or timeout. The status will be returned.
36
+
37
+ ```ruby
38
+ result = collector.collect # => :ok, :timeout or :cancelled
39
+ collector.messages # => collected messages
40
+ ```
41
+
42
+ If you want start collection, but not wait for the result, use `start()`. You can then later use `wait()` if you want:
43
+
44
+ ```ruby
45
+ result = collector.start # => nil
46
+ # do other stuff
47
+ result = collector.wait
48
+ ```
49
+
50
+ ### Custom filtering
51
+ You can use a block to do extra filtering. The block will be callled for each messages that fulfils the correct message type, direction and component id.
52
+
53
+ 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
+
55
+ ```ruby
56
+ result = collector.collect do |message|
57
+ :keep, :cancel # example of how to keep the message and cancel collection
58
+ end
59
+ ```
60
+
61
+ `:keep` keeps (collect) this message
62
+ `:cancel` cancel collection
63
+
64
+ Note that you cannot use `return` in a block. You can either simply provide the values as the last expresssion in the block, or use next().
65
+
66
+ Exceptions in the block will cause the collector to abort. If the collect! or wait! variants are used, the exception is propagated to the caller.
67
+
68
+ ### Bang version
69
+ The method collect!() will raise exceptions in case of errors, and will return the collect message directly.
70
+
71
+ ```ruby
72
+ message = collector.collect # => collected message.
73
+ ```
74
+
75
+ Similar, `wait!()` will raise an exception in case of timeouts or errors:
76
+
77
+ ```ruby
78
+ message = collector.wait! # => collected message.
79
+ ```
80
+
81
+
82
+ ### Schema Errors and Disconnects
83
+ The collector can optionally cancel collection in special cases, controlled by the `:cancel` option provided when contructing the collector.
84
+
85
+ ```ruby
86
+ options = {
87
+ cancel: {
88
+ disconnect: true,
89
+ schema_error: true
90
+ }
91
+ }
92
+ result = collector.collect options
93
+ ```
94
+
95
+ disconnect: If the proxy which provides messages experience a disconnect, the collector will cancel collection.
96
+
97
+ schema_error: If the proxy receives a message with a schema error, the collector will cancel collection, if the the invalid message has the correct message type.
98
+
99
+ ### NotAck
100
+ A typical scenaria is that you send a command or status request, and want to collect the response. But if the original message is rejected by the site, you will received a NotAck instead of a reply. The collector classes can handle this, as long as you provide the message id of the original request in the `m_id` key of teh options when you construct the collector.
101
+
102
+ If a NotAck is received with a matching `oMId` (original message id), the collection is cancelled.
103
+
104
+ ## StatusCollector
105
+ Waits for a set of status criteria to be met.
106
+
107
+ Note that a single RSMP status message can contain multiple status items. Unlike MessageCollector, a StatusCollector therefore operates on items, rather than messages, and you can't specify a number of messages to collect.
108
+
109
+
110
+ ### Criteria
111
+ You construct a StatusCollector with set of criteria, specifying the status codes, names, and optionally values that must be met.
112
+
113
+ ### Collecting
114
+ When you start collection, it will complete once all criteria are all fulfilled, the timeout is reached or a custom filtering block aborts the collection.
115
+
116
+ ```ruby
117
+ collector = StatusCollector.new(options)
118
+ result = matcher.collect(timeout: 5)
119
+ ```
120
+
121
+ ### Custom filtering
122
+ You can use a block to do extra filtering. The block will be called for each individual status item that fulfils all criteria, like status code and name, component, etc.
123
+
124
+ Like with MessageCollector, the block must return a hash specifing whether to keep the message and whether to continue collection.
125
+
126
+ ```ruby
127
+ matcher = StatusCollector.new(options)
128
+ result = matcher.collect(options) do |message,item|
129
+ next(:keep) if good_item?(item) # keep item
130
+ end
131
+ ```
132
+
133
+ ## Subscribing to status updates
134
+ The method `subscribe_to_status` can be used to subscribe to one of more status messages.
135
+
136
+ ### Without collection
137
+ The simple form sends an RSMP status subscription message to the site and then returns immediatly. To collect incoming status messages, you need to manually use e.g. a Collector.
138
+
139
+ A hash is returned, with `:sent` containing the send subscription messages.
140
+
141
+ ```ruby
142
+ options = {
143
+ list: [{'sCI'=>'S0001','n'=>'signalgroupstatus'}],
144
+ }
145
+ result = subscribe_to_status(options)
146
+ result.keys => # [:sent]
147
+ ```
148
+
149
+ Note: If you want to use this simple form and manually collect responses, it's best to start collection in an asyncronous task _before_ you subscribe, to make sure you don't miss early responses:
150
+
151
+ ```ruby
152
+ task = async do
153
+ MessageCollector.new(options).collect(num: 5, timeout:10) # start listening for status messages
154
+ end
155
+ result = subscribe_to_status(options) # subscribe
156
+ task.wait # wait for collection task to complete (or time out)
157
+ ```
158
+
159
+ ### With collection
160
+ If you provide `:collect` options, it will be used to construct a StatusUpdateCollector for collecting the relevant status messages. When collection completes the collector is returned in the `:collector` key:
161
+
162
+ ```ruby
163
+ options = {
164
+ list: [{'sCI'=>'S0001','n'=>'signalgroupstatus'}],
165
+ collect: {timeout: 5}
166
+ }
167
+ result = subscribe_to_status(options)
168
+ result.keys => # [:sent, :collector]
169
+ result[:collector].messages # => list of collected messages
170
+ ```
171
+
172
+ You can pass you own collector which will give you more control of how to collect the incoming status messages:
173
+
174
+ ```ruby
175
+ collector = Collector.new(options)
176
+ options = {collect: collector}
177
+ result = subscribe_to_status(options)
178
+ result.keys => # [:sent, :collector]
179
+ result[:collector].messages # => list of collected messages
180
+ ```
181
+
182
+ ### Processing responses
183
+ 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.
185
+
186
+ ```ruby
187
+ options = {
188
+ list: [{'sCI'=>'S0001','n'=>'signalgroupstatus'}]
189
+ }
190
+ result = subscribe_to_status(options) do |message|
191
+ # do something with message
192
+ :keep # or not
193
+ end
194
+ result.keys => # [:sent, :collector]
195
+ ```
196
+
@@ -0,0 +1,9 @@
1
+ module RSMP
2
+ # Class for waiting for an aggregated status response
3
+ class AggregatedStatusCollector < Collector
4
+ def initialize proxy, options={}
5
+ required = { type: ['AggregatedStatus','MessageNotAck'], title: 'aggregated status' }
6
+ super proxy, options.merge(required)
7
+ end
8
+ end
9
+ end
@@ -1,9 +1,10 @@
1
1
  module RSMP
2
2
 
3
- # Collects ingoing and/or outgoing messages from a notifier.
4
- # Can filter by message type and wakes up the client once the desired number of messages has been collected.
3
+ # Collects messages from a notifier.
4
+ # Can filter by message type, componet and direction.
5
+ # Wakes up the once the desired number of messages has been collected.
5
6
  class Collector < Listener
6
- attr_reader :condition, :messages, :status, :error
7
+ attr_reader :condition, :messages, :status, :error, :task
7
8
 
8
9
  def initialize proxy, options={}
9
10
  super proxy, options
@@ -17,15 +18,19 @@ module RSMP
17
18
  @outgoing = options[:outgoing] == nil ? false : options[:outgoing]
18
19
  @condition = Async::Notification.new
19
20
  @title = options[:title] || [@options[:type]].flatten.join('/')
21
+ @task = options[:task]
20
22
  reset
21
23
  end
22
24
 
25
+ def use_task task
26
+ @task = task
27
+ end
28
+
23
29
  # Clear all query results
24
30
  def reset
25
31
  @messages = []
26
32
  @error = nil
27
33
  @status = :ready
28
- @why = nil
29
34
  end
30
35
 
31
36
  # Inspect formatter that shows the message we have collected
@@ -33,6 +38,31 @@ module RSMP
33
38
  "#<#{self.class.name}:#{self.object_id}, #{inspector(:@messages)}>"
34
39
  end
35
40
 
41
+ # Is collection active?
42
+ def collecting?
43
+ @status == :collecting
44
+ end
45
+
46
+ # Is collection active?
47
+ def ok?
48
+ @status == :ok
49
+ end
50
+
51
+ # Has collection time out?
52
+ def timeout?
53
+ @status == :timeout
54
+ end
55
+
56
+ # Is collection ready to start?
57
+ def ready?
58
+ @status == :ready
59
+ end
60
+
61
+ # Has collection been cancelled?
62
+ def cancelled?
63
+ @status == :cancelled
64
+ end
65
+
36
66
  # Want ingoing messages?
37
67
  def ingoing?
38
68
  @ingoing == true
@@ -43,35 +73,53 @@ module RSMP
43
73
  @outgoing == true
44
74
  end
45
75
 
46
- # If collection is complete, return immeditatly. Otherwise wait until
47
- # the desired messages have been collected, or timeout is reached.
48
- def wait task
49
- wait! task
50
- rescue RSMP::TimeoutError
76
+ # Collect message
77
+ # Will return once all messages have been collected, or timeout is reached
78
+ def collect &block
79
+ start &block
80
+ wait
51
81
  @status
82
+ ensure
83
+ @notifier.remove_listener self
52
84
  end
53
85
 
54
- # If collection is complete, return immeditatly. Otherwise wait until
55
- # the desired messages have been collected.
56
- # If timeout is reached, an exceptioin is raised.
57
- def wait! task
58
- return @status unless @status == :collecting
59
- if @options[:timeout]
60
- task.with_timeout(@options[:timeout]) { @condition.wait }
61
- else
62
- @condition.wait
86
+ # Collect message
87
+ # Returns the collected messages, or raise an exception in case of a time out.
88
+ def collect! &block
89
+ if collect(&block) == :timeout
90
+ raise RSMP::TimeoutError.new describe_progress
91
+ end
92
+ @messages
93
+ end
94
+
95
+ # If collection is not active, return status immeditatly. Otherwise wait until
96
+ # the desired messages have been collected, or timeout is reached.
97
+ def wait
98
+ if collecting?
99
+ if @options[:timeout]
100
+ @task.with_timeout(@options[:timeout]) { @condition.wait }
101
+ else
102
+ @condition.wait
103
+ end
63
104
  end
64
105
  @status
65
106
  rescue Async::TimeoutError
66
107
  @status = :timeout
67
- raise RSMP::TimeoutError.new(describe_progress)
108
+ end
109
+
110
+ # If collection is not active, raise an error. Otherwise wait until
111
+ # the desired messages have been collected.
112
+ # If timeout is reached, an exceptioin is raised.
113
+ def wait!
114
+ wait
115
+ raise RSMP::TimeoutError.new(describe_progress) if timeout?
116
+ @messages
68
117
  end
69
118
 
70
119
  # Start collection and return immediately
71
120
  # You can later use wait() to wait for completion
72
- def start options={}, &block
73
- raise RuntimeError.new("Can't begin unless ready (currenty #{@status})") unless @status == :ready
74
- @options.merge! options
121
+ def start &block
122
+ raise RuntimeError.new("Can't begin unless ready (currenty #{@status})") unless ready?
75
123
  @block = block
76
124
  raise ArgumentError.new("Num, timeout or block must be provided") unless @options[:num] || @options[:timeout] || @block
77
125
  reset
@@ -79,16 +127,6 @@ module RSMP
79
127
  @notifier.add_listener self if @notifier
80
128
  end
81
129
 
82
- # Collect message
83
- # Will return once all messages have been collected, or timeout is reached
84
- def collect task, options={}, &block
85
- start options, &block
86
- wait task
87
- @status
88
- ensure
89
- @notifier.remove_listener self
90
- end
91
-
92
130
  # Build a string describing how how progress reached before timeout
93
131
  def describe_progress
94
132
  str = "#{@title.capitalize} collection "
@@ -98,17 +136,6 @@ module RSMP
98
136
  str
99
137
  end
100
138
 
101
- # Collect message
102
- # Returns the collected messages, or raise an exception in case of a time out.
103
- def collect! task, options={}, &block
104
- case collect(task, options, &block)
105
- when :timeout
106
- raise RSMP::TimeoutError.new @why
107
- else
108
- @messages
109
- end
110
- end
111
-
112
139
  # Check if we receive a NotAck related to initiating request, identified by @m_id.
113
140
  def reject_not_ack message
114
141
  return unless @options[:m_id]
@@ -124,7 +151,7 @@ module RSMP
124
151
  # Handle message. and return true when we're done collecting
125
152
  def notify message
126
153
  raise ArgumentError unless message
127
- raise RuntimeError.new("can't process message when done") unless @status == :ready || @status == :collecting
154
+ raise RuntimeError.new("can't process message when done") unless ready? || collecting?
128
155
  unless reject_not_ack(message)
129
156
  perform_match message
130
157
  end
@@ -0,0 +1,16 @@
1
+ module RSMP
2
+ # Match a specific command responses
3
+ class CommandQuery < Query
4
+ # Match a return value item against a query
5
+ def match? item
6
+ return nil if @want['cCI'] && @want['cCI'] != item['cCI']
7
+ return nil if @want['n'] && @want['n'] != item['n']
8
+ if @want['v'].is_a? Regexp
9
+ return false if @want['v'] && item['v'] !~ @want['v']
10
+ else
11
+ return false if @want['v'] && item['v'] != @want['v']
12
+ end
13
+ true
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ module RSMP
2
+ # Class for waiting for specific command responses
3
+ class CommandResponseCollector < StateCollector
4
+ def initialize proxy, want, options={}
5
+ super proxy, want, options.merge(
6
+ type: ['CommandResponse','MessageNotAck'],
7
+ title:'command response'
8
+ )
9
+ end
10
+
11
+ def build_query want
12
+ CommandQuery.new want
13
+ end
14
+
15
+ # Get items, in our case the return values
16
+ def get_items message
17
+ message.attributes['rvs']
18
+ end
19
+ end
20
+ end
@@ -1,64 +0,0 @@
1
- module RSMP
2
- # Class for waiting for specific command responses
3
- class CommandResponseMatcher < Matcher
4
- def initialize proxy, want, options={}
5
- super proxy, want, options.merge(
6
- type: ['CommandResponse','MessageNotAck'],
7
- title:'command response'
8
- )
9
- end
10
-
11
- def build_query want
12
- CommandQuery.new want
13
- end
14
-
15
- # Get items, in our case the return values
16
- def get_items message
17
- message.attributes['rvs']
18
- end
19
- end
20
-
21
- # Base class for waiting for status updates or responses
22
- class StatusUpdateOrResponseMatcher < Matcher
23
- def initialize proxy, want, options={}
24
- super proxy, want, options.merge
25
- end
26
-
27
- def build_query want
28
- StatusQuery.new want
29
- end
30
-
31
- # Get items, in our case status values
32
- def get_items message
33
- message.attributes['sS']
34
- end
35
- end
36
-
37
- # Class for waiting for specific status responses
38
- class StatusResponseMatcher < StatusUpdateOrResponseMatcher
39
- def initialize proxy, want, options={}
40
- super proxy, want, options.merge(
41
- type: ['StatusResponse','MessageNotAck'],
42
- title: 'status response'
43
- )
44
- end
45
- end
46
-
47
- # Class for waiting for specific status responses
48
- class StatusUpdateMatcher < StatusUpdateOrResponseMatcher
49
- def initialize proxy, want, options={}
50
- super proxy, want, options.merge(
51
- type: ['StatusUpdate','MessageNotAck'],
52
- title:'status update'
53
- )
54
- end
55
- end
56
-
57
- # Class for waiting for an aggregated status response
58
- class AggregatedStatusMatcher < Collector
59
- def initialize proxy, options={}
60
- required = { type: ['AggregatedStatus','MessageNotAck'], title: 'aggregated status' }
61
- super proxy, options.merge(required)
62
- end
63
- end
64
- end
@@ -31,8 +31,7 @@ module RSMP
31
31
  # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/} =>
32
32
  # { <StatusResponse message>, {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>"9"} }
33
33
  # }
34
-
35
- class Matcher < Collector
34
+ class StateCollector < Collector
36
35
  attr_reader :queries
37
36
 
38
37
  # Initialize with a list of wanted statuses
@@ -1,19 +1,4 @@
1
1
  module RSMP
2
- # Match a specific command responses
3
- class CommandQuery < Query
4
- # Match a return value item against a query
5
- def match? item
6
- return nil if @want['cCI'] && @want['cCI'] != item['cCI']
7
- return nil if @want['n'] && @want['n'] != item['n']
8
- if @want['v'].is_a? Regexp
9
- return false if @want['v'] && item['v'] !~ @want['v']
10
- else
11
- return false if @want['v'] && item['v'] != @want['v']
12
- end
13
- true
14
- end
15
- end
16
-
17
2
  # Match a specific status response or update
18
3
  class StatusQuery < Query
19
4
  # Match a status value against a query
@@ -0,0 +1,11 @@
1
+ module RSMP
2
+ # Class for waiting for specific status responses
3
+ class StatusResponseCollector < StatusUpdateOrResponseCollector
4
+ def initialize proxy, want, options={}
5
+ super proxy, want, options.merge(
6
+ type: ['StatusResponse','MessageNotAck'],
7
+ title: 'status response'
8
+ )
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module RSMP
2
+ # Class for waiting for specific status responses
3
+ class StatusUpdateCollector < StatusUpdateOrResponseCollector
4
+ def initialize proxy, want, options={}
5
+ super proxy, want, options.merge(
6
+ type: ['StatusUpdate','MessageNotAck'],
7
+ title:'status update'
8
+ )
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module RSMP
2
+ # Base class for waiting for status updates or responses
3
+ class StatusUpdateOrResponseCollector < StateCollector
4
+ def initialize proxy, want, options={}
5
+ super proxy, want, options.merge
6
+ end
7
+
8
+ def build_query want
9
+ RSMP::StatusQuery.new want
10
+ end
11
+
12
+ # Get items, in our case status values
13
+ def get_items message
14
+ message.attributes['sS']
15
+ end
16
+ end
17
+ end
data/lib/rsmp/proxy.rb CHANGED
@@ -52,9 +52,9 @@ module RSMP
52
52
  node.clock
53
53
  end
54
54
 
55
- def collect task, options, &block
56
- collector = RSMP::Collector.new self, options
57
- collector.collect task, &block
55
+ def collect options, &block
56
+ collector = RSMP::Collector.new self, options.merge(task: @task)
57
+ collector.collect &block
58
58
  collector
59
59
  end
60
60
 
@@ -349,26 +349,26 @@ module RSMP
349
349
  end
350
350
 
351
351
  def collect_status_updates task, status_list, options
352
- collector = StatusUpdateMatcher.new(self, status_list, options)
353
- collector.collect task
352
+ collector = StatusUpdateCollector.new(self, status_list, options.merge(task:task))
353
+ collector.collect
354
354
  collector
355
355
  end
356
356
 
357
357
  def collect_status_responses task, status_list, options
358
- collector = StatusResponseMatcher.new(self, status_list, options)
359
- collector.collect task
358
+ collector = StatusResponseCollector.new(self, status_list, options.merge(task:task))
359
+ collector.collect
360
360
  collector
361
361
  end
362
362
 
363
363
  def collect_command_responses task, command_list, options
364
- collector = CommandResponseMatcher.new(self, command_list, options)
365
- collector.collect task
364
+ collector = CommandResponseCollector.new(self, command_list, options.merge(task:task))
365
+ collector.collect
366
366
  collector
367
367
  end
368
368
 
369
369
  def collect_aggregated_status task, options
370
- collector = AggregatedStatusMatcher.new(self, options)
371
- collector.collect task
370
+ collector = AggregatedStatusCollector.new(self, options.merge(task:task))
371
+ collector.collect
372
372
  collector
373
373
  end
374
374
  end
@@ -16,7 +16,7 @@ module RSMP
16
16
  return default unless plan.states
17
17
  states = plan.states[c_id]
18
18
  return default unless states
19
- state =states[pos]
19
+ state = states[pos]
20
20
  return default unless state =~ /[a-hA-G0-9N-P]/ # valid signal group states
21
21
  state
22
22
  end
data/lib/rsmp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module RSMP
2
- VERSION = "0.8.0"
2
+ VERSION = "0.8.1"
3
3
  end
data/lib/rsmp.rb CHANGED
@@ -20,10 +20,15 @@ require 'rsmp/components'
20
20
  require 'rsmp/collect/notifier'
21
21
  require 'rsmp/collect/listener'
22
22
  require 'rsmp/collect/collector'
23
+ require 'rsmp/collect/state_collector'
23
24
  require 'rsmp/collect/query'
24
- require 'rsmp/collect/matcher'
25
- require 'rsmp/collect/message_queries'
26
- require 'rsmp/collect/message_matchers'
25
+ require 'rsmp/collect/status_query'
26
+ require 'rsmp/collect/command_query'
27
+ require 'rsmp/collect/status_update_or_response_collector'
28
+ require 'rsmp/collect/status_response_collector'
29
+ require 'rsmp/collect/status_update_collector'
30
+ require 'rsmp/collect/command_response_collector'
31
+ require 'rsmp/collect/aggregated_status_collector'
27
32
  require 'rsmp/component'
28
33
  require 'rsmp/site'
29
34
  require 'rsmp/proxy'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rsmp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emil Tin
@@ -201,19 +201,25 @@ files:
201
201
  - config/supervisor.yaml
202
202
  - config/tlc.yaml
203
203
  - documentation/classes_and_modules.md
204
+ - documentation/collecting_message.md
204
205
  - documentation/message_distribution.md
205
206
  - exe/rsmp
206
207
  - lib/rsmp.rb
207
208
  - lib/rsmp/archive.rb
208
209
  - lib/rsmp/cli.rb
210
+ - lib/rsmp/collect/aggregated_status_collector.rb
209
211
  - lib/rsmp/collect/collector.rb
212
+ - lib/rsmp/collect/command_query.rb
213
+ - lib/rsmp/collect/command_response_collector.rb
210
214
  - lib/rsmp/collect/listener.rb
211
- - lib/rsmp/collect/matcher.rb
212
- - lib/rsmp/collect/message_collector.rb
213
215
  - lib/rsmp/collect/message_matchers.rb
214
- - lib/rsmp/collect/message_queries.rb
215
216
  - lib/rsmp/collect/notifier.rb
216
217
  - lib/rsmp/collect/query.rb
218
+ - lib/rsmp/collect/state_collector.rb
219
+ - lib/rsmp/collect/status_query.rb
220
+ - lib/rsmp/collect/status_response_collector.rb
221
+ - lib/rsmp/collect/status_update_collector.rb
222
+ - lib/rsmp/collect/status_update_or_response_collector.rb
217
223
  - lib/rsmp/component.rb
218
224
  - lib/rsmp/components.rb
219
225
  - lib/rsmp/convert/export/json_schema.rb
@@ -1,209 +0,0 @@
1
- module RSMP
2
-
3
- # Collects ingoing and/or outgoing messages from a notifier.
4
- # Can filter by message type and wakes up the client once the desired number of messages has been collected.
5
- class MessageCollector < Collector
6
- attr_reader :condition, :messages, :done
7
-
8
- def initialize proxy, options={}
9
- super proxy, options
10
- @options = {
11
- cancel: {
12
- schema_error: true,
13
- disconnect: false,
14
- }
15
- }.deep_merge options
16
- @ingoing = options[:ingoing] == nil ? true : options[:ingoing]
17
- @outgoing = options[:outgoing] == nil ? false : options[:outgoing]
18
- @condition = Async::Notification.new
19
- @title = options[:title] || [@options[:type]].flatten.join('/')
20
- @options[:timeout] ||= 1
21
- @options[:num] ||= 1
22
- reset
23
- end
24
-
25
- # Inspect formatter that shows the message we have collected
26
- def inspect
27
- "#<#{self.class.name}:#{self.object_id}, #{inspector(:@messages)}>"
28
- end
29
-
30
- # Want ingoing messages?
31
- def ingoing?
32
- @ingoing == true
33
- end
34
-
35
- # Want outgoing messages?
36
- def outgoing?
37
- @outgoing == true
38
- end
39
-
40
- # Block until all messages have been collected
41
- def wait
42
- @condition.wait
43
- end
44
-
45
- # Collect message
46
- # Will block until all messages have been collected,
47
- # or we time out
48
- def collect task, options={}, &block
49
- @options.merge! options
50
- @block = block
51
- unless @done
52
- listen do
53
- task.with_timeout(@options[:timeout]) do
54
- @condition.wait
55
- end
56
- end
57
- end
58
- return @error if @error
59
- self
60
- rescue Async::TimeoutError
61
- str = "#{@title.capitalize} collection"
62
- str << " in response to #{options[:m_id]}" if options[:m_id]
63
- str << " didn't complete within #{@options[:timeout]}s"
64
- reached = progress
65
- str << ", reached #{progress[:reached]}/#{progress[:need]}"
66
- raise RSMP::TimeoutError.new str
67
- end
68
-
69
- # Return progress as collected vs. number requested
70
- def progress
71
- need = @options[:num]
72
- reached = @messages.size
73
- { need: need, got: reached }
74
- end
75
-
76
- # Get the collected message.
77
- def message
78
- @messages.first
79
- end
80
-
81
- # Get the collected messages.
82
- def messages
83
- @messages
84
- end
85
-
86
- # Clear all query results
87
- def reset
88
- @messages = []
89
- @error = nil
90
- @done = false
91
- end
92
-
93
- # Check if we receive a NotAck related to initiating request, identified by @m_id.
94
- def check_not_ack message
95
- return unless @options[:m_id]
96
- if message.is_a?(MessageNotAck)
97
- if message.attribute('oMId') == @options[:m_id]
98
- m_id_short = RSMP::Message.shorten_m_id @options[:m_id], 8
99
- @error = RSMP::MessageRejected.new("#{@title} #{m_id_short} was rejected with '#{message.attribute('rea')}'")
100
- complete
101
- end
102
- false
103
- end
104
- end
105
-
106
- # Handle message. and return true when we're done collecting
107
- def notify message
108
- raise ArgumentError unless message
109
- raise RuntimeError.new("can't process message when already done") if @done
110
- check_not_ack(message)
111
- return true if @done
112
- perform_match message
113
- complete if done?
114
- @done
115
- end
116
-
117
- # Match message against our collection criteria
118
- def perform_match message
119
- matched = type_match?(message) && block_match?(message)
120
- if matched == true
121
- keep message
122
- elsif matched == false
123
- forget message
124
- end
125
- end
126
-
127
- # Have we collected the required number of messages?
128
- def done?
129
- @options[:num] && @messages.size >= @options[:num]
130
- end
131
-
132
- # Called when we're done collecting. Remove ourself as a listener,
133
- # se we don't receive message notifications anymore
134
- def complete
135
- @done = true
136
- @proxy.remove_listener self
137
- @condition.signal
138
- end
139
-
140
- # The proxy experienced some error.
141
- # Check if this should cause us to cancel.
142
- def notify_error error, options={}
143
- case error
144
- when RSMP::SchemaError
145
- notify_schema_error error, options
146
- when RSMP::ConnectionError
147
- notify_disconnect error, options
148
- end
149
- end
150
-
151
- # Cancel if we received e schema error for a message type we're collecting
152
- def notify_schema_error error, options
153
- return unless @options.dig(:cancel,:schema_error)
154
- message = options[:message]
155
- return unless message
156
- klass = message.class.name.split('::').last
157
- return unless [@options[:type]].flatten.include? klass
158
- @proxy.log "Collect cancelled due to schema error in #{klass} #{message.m_id_short}", level: :debug
159
- cancel error
160
- end
161
-
162
- # Cancel if we received e notificaiton about a disconnect
163
- def notify_disconnect error, options
164
- return unless @options.dig(:cancel,:disconnect)
165
- @proxy.log "Collect cancelled due to a connection error: #{error.to_s}", level: :debug
166
- cancel error
167
- end
168
-
169
- # Abort collection
170
- def cancel error
171
- @error = error if error
172
- @done = false
173
- @proxy.remove_listener self
174
- @condition.signal
175
- end
176
-
177
- # Store a message in the result array
178
- def keep message
179
- @messages << message
180
- end
181
-
182
- # Remove a message from the result array
183
- def forget message
184
- @messages.delete message
185
- end
186
-
187
- # Check a message against our match criteria
188
- # Return true if there's a match, false if not
189
- def type_match? message
190
- return false if message.direction == :in && @ingoing == false
191
- return false if message.direction == :out && @outgoing == false
192
- if @options[:type]
193
- if @options[:type].is_a? Array
194
- return false unless @options[:type].include? message.type
195
- else
196
- return false unless message.type == @options[:type]
197
- end
198
- end
199
- if @options[:component]
200
- return false if message.attributes['cId'] && message.attributes['cId'] != @options[:component]
201
- end
202
- true
203
- end
204
- end
205
-
206
- def block_match? message
207
- @block.call(message) == true
208
- end
209
- end