rsmp 0.7.4 → 0.8.2

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: 8e33d1daa5e26bfea0abc96970804ebc66f294200c5e952c4febf8105b4a9041
4
- data.tar.gz: 6bb4d576fb0f9738f10a885265d3f58d56df78cac77ea520671d3d269de981f4
3
+ metadata.gz: 49e4aee9252ad888e831e5ce0738eff5bba57fdd6f2ec2220d7e74936e0e8177
4
+ data.tar.gz: 5866b23cb324d0732de17f995abec6304f50673e9cfcaa7b440dea6858102cd8
5
5
  SHA512:
6
- metadata.gz: ec2bda9f884954830d81b65bdee1bdfeee7183e3e7655e4677defad2d0a47e76bc4004c966ef6119f7623570d9febc03a84358897803733c07255b471503097f
7
- data.tar.gz: b660975c624bb275c7853cc8060d0c95c0abac5a60f79ab0c2327324d9e7757b618c6dc4e2e91de4924fe1b1de0c5eb480e5cf35fd620e6c4c19aa923e33051e
6
+ metadata.gz: 0ff9a48cde72b3fb3963c421a321c4bd08c0edfb9afcd7b4b919e2e92aa37624259a5124c81f4bd0189cc6a3596414edc75a305fb19b0c348fadc4e836bdf7cf
7
+ data.tar.gz: 490aa51f0a4a5b9a6a197c0f6bbe11b8ae16934e5eecd76a486b6e4e34786ebbb334d4ddabf7340c9f13d4946cad4e162db49205a3fbf04fa064938bf6029e0f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rsmp (0.7.4)
4
+ rsmp (0.8.2)
5
5
  async (~> 1.29.1)
6
6
  async-io (~> 1.32.1)
7
7
  colorize (~> 0.8.1)
@@ -95,7 +95,7 @@ GEM
95
95
  thor
96
96
  thread_safe
97
97
  rake (13.0.3)
98
- regexp_parser (2.1.1)
98
+ regexp_parser (2.2.0)
99
99
  rsmp_schemer (0.3.2)
100
100
  json_schemer (~> 0.2.18)
101
101
  rspec (3.10.0)
data/config/tlc.yaml CHANGED
@@ -21,10 +21,10 @@ signal_plans:
21
21
  1: 0
22
22
  2: 5
23
23
  states:
24
- A1: '11NBBB'
25
- A2: '1NBBBB'
26
- B1: 'BBB11N'
27
- B2: 'BBB1NB'
24
+ A1: '123efg'
25
+ A2: '123efg'
26
+ B1: '123efg'
27
+ B2: '123efg'
28
28
  2:
29
29
  states:
30
30
  A1: '111NBB'
@@ -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, :done
7
+ attr_reader :condition, :messages, :status, :error, :task
7
8
 
8
9
  def initialize proxy, options={}
9
10
  super proxy, options
@@ -17,16 +18,51 @@ 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('/')
20
- @options[:timeout] ||= 1
21
- @options[:num] ||= 1
21
+ @task = options[:task]
22
22
  reset
23
23
  end
24
24
 
25
+ def use_task task
26
+ @task = task
27
+ end
28
+
29
+ # Clear all query results
30
+ def reset
31
+ @messages = []
32
+ @error = nil
33
+ @status = :ready
34
+ end
35
+
25
36
  # Inspect formatter that shows the message we have collected
26
37
  def inspect
27
38
  "#<#{self.class.name}:#{self.object_id}, #{inspector(:@messages)}>"
28
39
  end
29
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
+
30
66
  # Want ingoing messages?
31
67
  def ingoing?
32
68
  @ingoing == true
@@ -37,91 +73,104 @@ module RSMP
37
73
  @outgoing == true
38
74
  end
39
75
 
40
- # Block until all messages have been collected
41
- def wait
42
- @condition.wait
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
81
+ @status
82
+ ensure
83
+ @notifier.remove_listener self if @notifier
43
84
  end
44
85
 
45
86
  # 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
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
57
91
  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
92
+ @messages
67
93
  end
68
94
 
69
- # Return progress as collected vs. number requested
70
- def progress
71
- need = @options[:num]
72
- reached = @messages.size
73
- { need: need, got: reached }
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
104
+ end
105
+ @status
106
+ rescue Async::TimeoutError
107
+ @status = :timeout
74
108
  end
75
109
 
76
- # Get the collected message.
77
- def message
78
- @messages.first
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
79
117
  end
80
118
 
81
- # Get the collected messages.
82
- def messages
83
- @messages
119
+ # Start collection and return immediately
120
+ # You can later use wait() to wait for completion
121
+ def start &block
122
+ raise RuntimeError.new("Can't begin unless ready (currenty #{@status})") unless ready?
123
+ @block = block
124
+ raise ArgumentError.new("Num, timeout or block must be provided") unless @options[:num] || @options[:timeout] || @block
125
+ reset
126
+ @status = :collecting
127
+ @notifier.add_listener self if @notifier
84
128
  end
85
129
 
86
- # Clear all query results
87
- def reset
88
- @messages = []
89
- @error = nil
90
- @done = false
130
+ # Build a string describing how how progress reached before timeout
131
+ def describe_progress
132
+ str = "#{@title.capitalize} collection "
133
+ str << "in response to #{@options[:m_id]} " if @options[:m_id]
134
+ str << "didn't complete within #{@options[:timeout]}s, "
135
+ str << "reached #{@messages.size}/#{@options[:num]}"
136
+ str
91
137
  end
92
138
 
93
139
  # Check if we receive a NotAck related to initiating request, identified by @m_id.
94
- def check_not_ack message
140
+ def reject_not_ack message
95
141
  return unless @options[:m_id]
96
142
  if message.is_a?(MessageNotAck)
97
143
  if message.attribute('oMId') == @options[:m_id]
98
144
  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
145
+ cancel RSMP::MessageRejected.new("#{@title} #{m_id_short} was rejected with '#{message.attribute('rea')}'")
146
+ true
101
147
  end
102
- false
103
148
  end
104
149
  end
105
150
 
106
151
  # Handle message. and return true when we're done collecting
107
152
  def notify message
108
153
  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
- check_match message
113
- complete if done?
114
- @done
154
+ raise RuntimeError.new("can't process message when status is :#{@status}, title: #{@title}, desc: #{describe}") unless ready? || collecting?
155
+ perform_match message
156
+ @status
157
+ end
158
+
159
+ def describe
115
160
  end
116
161
 
117
162
  # Match message against our collection criteria
118
- def check_match message
119
- matched = match? message
120
- if matched == true
163
+ def perform_match message
164
+ return false if reject_not_ack(message)
165
+ return false unless type_match?(message)
166
+ if @block
167
+ status = [@block.call(message)].flatten
168
+ return unless collecting?
169
+ keep message if status.include?(:keep)
170
+ else
121
171
  keep message
122
- elsif matched == false
123
- forget message
124
172
  end
173
+ complete if done?
125
174
  end
126
175
 
127
176
  # Have we collected the required number of messages?
@@ -132,8 +181,14 @@ module RSMP
132
181
  # Called when we're done collecting. Remove ourself as a listener,
133
182
  # se we don't receive message notifications anymore
134
183
  def complete
135
- @done = true
136
- @proxy.remove_listener self
184
+ @status = :ok
185
+ do_stop
186
+ end
187
+
188
+ # Remove ourself as a listener, so we don't receive message notifications anymore,
189
+ # and wake up the async condition
190
+ def do_stop
191
+ @notifier.remove_listener self
137
192
  @condition.signal
138
193
  end
139
194
 
@@ -143,7 +198,7 @@ module RSMP
143
198
  case error
144
199
  when RSMP::SchemaError
145
200
  notify_schema_error error, options
146
- when RSMP::ConnectionError
201
+ when RSMP::DisconnectError
147
202
  notify_disconnect error, options
148
203
  end
149
204
  end
@@ -154,24 +209,23 @@ module RSMP
154
209
  message = options[:message]
155
210
  return unless message
156
211
  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
212
+ return unless @options[:type] == nil || [@options[:type]].flatten.include?(klass)
213
+ @notifier.log "Collection cancelled due to schema error in #{klass} #{message.m_id_short}", level: :debug
159
214
  cancel error
160
215
  end
161
216
 
162
217
  # Cancel if we received e notificaiton about a disconnect
163
218
  def notify_disconnect error, options
164
219
  return unless @options.dig(:cancel,:disconnect)
165
- @proxy.log "Collect cancelled due to a connection error: #{error.to_s}", level: :debug
220
+ @notifier.log "Collection cancelled due to a connection error: #{error.to_s}", level: :debug
166
221
  cancel error
167
222
  end
168
223
 
169
224
  # Abort collection
170
- def cancel error
171
- @error = error if error
172
- @done = false
173
- @proxy.remove_listener self
174
- @condition.signal
225
+ def cancel error=nil
226
+ @error = error
227
+ @status = :cancelled
228
+ do_stop
175
229
  end
176
230
 
177
231
  # Store a message in the result array
@@ -179,30 +233,20 @@ module RSMP
179
233
  @messages << message
180
234
  end
181
235
 
182
- # Remove a message from the result array
183
- def forget message
184
- @messages.delete message
185
- end
186
-
187
236
  # Check a message against our match criteria
188
- # Return true if there's a match
189
- def match? message
190
- raise ArgumentError unless message
191
- return if message.direction == :in && @ingoing == false
192
- return if message.direction == :out && @outgoing == false
237
+ # Return true if there's a match, false if not
238
+ def type_match? message
239
+ return false if message.direction == :in && @ingoing == false
240
+ return false if message.direction == :out && @outgoing == false
193
241
  if @options[:type]
194
- return if message == nil
195
242
  if @options[:type].is_a? Array
196
- return unless @options[:type].include? message.type
243
+ return false unless @options[:type].include? message.type
197
244
  else
198
- return unless message.type == @options[:type]
245
+ return false unless message.type == @options[:type]
199
246
  end
200
247
  end
201
248
  if @options[:component]
202
- return if message.attributes['cId'] && message.attributes['cId'] != @options[:component]
203
- end
204
- if @block
205
- return if @block.call(message) == false
249
+ return false if message.attributes['cId'] && message.attributes['cId'] != @options[:component]
206
250
  end
207
251
  true
208
252
  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
@@ -0,0 +1,31 @@
1
+ module RSMP
2
+
3
+ # Filter messages based on type, direction and component id.
4
+ # Used by Collectors.
5
+ class Filter
6
+ def initialize ingoing:true, outgoing:true, type:, component:nil
7
+ @ingoing = ingoing
8
+ @outgoing = outgoing
9
+ @type = type
10
+ @component = component
11
+ end
12
+
13
+ # Check a message against our match criteria
14
+ # Return true if there's a match, false if not
15
+ def accept? message
16
+ return false if message.direction == :in && @ingoing == false
17
+ return false if message.direction == :out && @outgoing == false
18
+ if @type
19
+ if @type.is_a? Array
20
+ return false unless @type.include? message.type
21
+ else
22
+ return false unless message.type == @type
23
+ end
24
+ end
25
+ if @component
26
+ return false if message.attributes['cId'] && message.attributes['cId'] != @component
27
+ end
28
+ true
29
+ end
30
+ end
31
+ end
@@ -5,22 +5,19 @@ module RSMP
5
5
  class Listener
6
6
  include Inspect
7
7
 
8
- def initialize proxy, options={}
9
- @proxy = proxy
8
+ def initialize notifier, options={}
9
+ @notifier = notifier
10
10
  end
11
11
 
12
- def notify message
12
+ def change_notifier notifier
13
+ @notifier.remove_listener self if @notifier
14
+ @notifier = notifier
13
15
  end
14
16
 
15
- def notify_error error, options={}
17
+ def notify message
16
18
  end
17
19
 
18
- def listen &block
19
- @proxy.add_listener self
20
- yield
21
- ensure
22
- @proxy.remove_listener self
20
+ def notify_error error, options={}
23
21
  end
24
-
25
22
  end
26
23
  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
@@ -1,5 +1,5 @@
1
1
  module RSMP
2
-
2
+
3
3
  # Class that matches a single status or command item
4
4
  class Query
5
5
  attr_reader :want, :got, :message
@@ -8,26 +8,36 @@ module RSMP
8
8
  @want = want
9
9
  @got = nil
10
10
  @message = nil
11
- @done = false
12
11
  end
13
12
 
14
13
  # Are we done, i.e. did the last checked item match?
15
14
  def done?
16
- @done
15
+ @got != nil
17
16
  end
18
17
 
19
18
  # Check an item and set @done to true if it matches
20
19
  # Store the item and corresponding message if there's a positive or negative match
21
- def check_match item, message
20
+ def perform_match item, message, block
22
21
  matched = match? item
23
22
  if matched != nil
24
- @message = message
25
- @got = item
26
- @done = matched
23
+ if block
24
+ status = block.call(nil,item)
25
+ matched = status if status == true || status == false
26
+ end
27
27
  end
28
28
  matched
29
29
  end
30
30
 
31
+ def keep message, item
32
+ @message = message
33
+ @got = item
34
+ end
35
+
36
+ def forget
37
+ @message = nil
38
+ @got = nil
39
+ end
40
+
31
41
  def match? item
32
42
  end
33
43
  end
@@ -31,15 +31,14 @@ 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
39
38
  def initialize proxy, want, options={}
39
+ raise ArgumentError.new("num option cannot be used") if options[:num]
40
40
  super proxy, options.merge( ingoing: true, outgoing: false)
41
41
  @queries = want.map { |item| build_query item }
42
- @want = want
43
42
  end
44
43
 
45
44
  # Build a query object.
@@ -60,14 +59,9 @@ module RSMP
60
59
  @queries.map { |query| query.got }.compact
61
60
  end
62
61
 
63
- # get the first message. Useful when you only collect one mesage
64
- def message
65
- @queries.first.message
66
- end
67
-
68
62
  # Get messages from results
69
63
  def messages
70
- @queries.map { |query| query.message }.uniq
64
+ @queries.map { |query| query.message }.uniq.compact
71
65
  end
72
66
 
73
67
  # Return progress as completes queries vs. total number of queries
@@ -77,14 +71,14 @@ module RSMP
77
71
  { need: need, reached: reached }
78
72
  end
79
73
 
80
- # Are there queries left to match?
74
+ # Are there queries left to type_match?
81
75
  def done?
82
76
  @queries.all? { |query| query.done? }
83
77
  end
84
78
 
85
79
  # Get a simplified hash of queries, with values set to either true or false,
86
80
  # indicating which queries have been matched.
87
- def status
81
+ def query_status
88
82
  @queries.map { |query| [query.want, query.done?] }.to_h
89
83
  end
90
84
 
@@ -95,19 +89,34 @@ module RSMP
95
89
 
96
90
  # Check if a messages matches our criteria.
97
91
  # Match each query against each item in the message
98
- def check_match message
99
- return unless match?(message)
92
+ def perform_match message
93
+ return false if super(message) == false
94
+ return unless collecting?
100
95
  @queries.each do |query| # look through queries
101
96
  get_items(message).each do |item| # look through items in message
102
- matched = query.check_match(item,message)
97
+ matched = query.perform_match(item,message,@block)
98
+ return unless collecting?
103
99
  if matched != nil
104
100
  type = {true=>'match',false=>'mismatch'}[matched]
105
- @proxy.log "#{@title.capitalize} #{message.m_id_short} collect #{type} #{query.want}, item #{item}", level: :debug
106
- break
101
+ @notifier.log "#{@title.capitalize} #{message.m_id_short} collect #{type} #{query.want}, item #{item}", level: :debug
102
+ if matched == true
103
+ query.keep message, item
104
+ elsif matched == false
105
+ query.forget
106
+ end
107
107
  end
108
108
  end
109
109
  end
110
- @proxy.log "#{@title.capitalize} collect reached #{summary}", level: :debug
110
+ complete if done?
111
+ @notifier.log "#{@title.capitalize} collect reached #{summary}", level: :debug
112
+ end
113
+
114
+ # don't collect anything. Query will collect them instead
115
+ def keep message
116
+ end
117
+
118
+ def describe
119
+ @queries.map {|q| q.want.to_s }
111
120
  end
112
121
  end
113
122
  end
@@ -1,20 +1,5 @@
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
- # Match a specific status response or update
2
+ # Match a specific status response or update
18
3
  class StatusQuery < Query
19
4
  # Match a status value against a query
20
5
  def match? item
@@ -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(outgoing: false)
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/error.rb CHANGED
@@ -41,7 +41,7 @@ module RSMP
41
41
  class TimeoutError < Error
42
42
  end
43
43
 
44
- class DisonnectError < Error
44
+ class DisconnectError < Error
45
45
  end
46
46
 
47
47
  class ConnectionError < Error
data/lib/rsmp/node.rb CHANGED
@@ -6,7 +6,7 @@ module RSMP
6
6
  include Wait
7
7
  include Inspect
8
8
 
9
- attr_reader :archive, :logger, :task, :deferred, :error_queue, :clock
9
+ attr_reader :archive, :logger, :task, :deferred, :error_queue, :clock, :collector
10
10
 
11
11
  def initialize options
12
12
  initialize_logging options
@@ -15,6 +15,8 @@ module RSMP
15
15
  @clock = Clock.new
16
16
  @error_queue = Async::Queue.new
17
17
  @ignore_errors = []
18
+ options[:collector]
19
+ @collect = options[:collect]
18
20
  end
19
21
 
20
22
  def ignore_errors classes, &block
data/lib/rsmp/proxy.rb CHANGED
@@ -15,9 +15,8 @@ module RSMP
15
15
 
16
16
  def initialize options
17
17
  initialize_logging options
18
- setup options
19
18
  initialize_distributor
20
- prepare_collection @settings['collect']
19
+ setup options
21
20
  clear
22
21
  end
23
22
 
@@ -37,6 +36,10 @@ module RSMP
37
36
  @sxl = nil
38
37
  @site_settings = nil # can't pick until we know the site id
39
38
  @state = :stopped
39
+ if options[:collect]
40
+ @collector = RSMP::Collector.new self, options[:collect]
41
+ @collector.start
42
+ end
40
43
  end
41
44
 
42
45
  def inspect
@@ -49,16 +52,9 @@ module RSMP
49
52
  node.clock
50
53
  end
51
54
 
52
- def prepare_collection num
53
- if num
54
- @collector = RSMP::Collector.new self, num: num, ingoing: true, outgoing: true
55
- add_listener @collector
56
- end
57
- end
58
-
59
- def collect task, options, &block
60
- collector = RSMP::Collector.new self, options
61
- collector.collect task, &block
55
+ def collect options, &block
56
+ collector = RSMP::Collector.new self, options.merge(task: @task)
57
+ collector.collect &block
62
58
  collector
63
59
  end
64
60
 
@@ -86,7 +82,7 @@ module RSMP
86
82
  return if @state == :stopped
87
83
  set_state :stopping
88
84
  stop_tasks
89
- notify_error DisonnectError.new("Connection was closed")
85
+ notify_error DisconnectError.new("Connection was closed")
90
86
  ensure
91
87
  close_socket
92
88
  clear
@@ -101,15 +101,8 @@ module RSMP
101
101
  "cId" => component,
102
102
  "mId" => m_id
103
103
  })
104
- if options[:collect]
105
- task = @task.async do |task|
106
- collect_aggregated_status task, options[:collect].merge(m_id: m_id)
107
- end
108
- send_message message, validate: options[:validate]
109
- return message, task.wait
110
- else
111
- send_message message, validate: options[:validate]
112
- message
104
+ send_and_collect_if_needed message, options do |task|
105
+ collect_aggregated_status task, options[:collect].merge(m_id: m_id, num:1)
113
106
  end
114
107
  end
115
108
 
@@ -181,7 +174,7 @@ module RSMP
181
174
  "sS" => request_list,
182
175
  "mId" => m_id
183
176
  })
184
- send_while_collecting message, options do |task|
177
+ send_and_collect_if_needed message, options do |task|
185
178
  collect_status_responses task, status_list, options[:collect].merge(m_id: m_id)
186
179
  end
187
180
  end
@@ -193,11 +186,15 @@ module RSMP
193
186
  acknowledge message
194
187
  end
195
188
 
196
- def send_while_collecting message, options, &block
197
- task = @task.async { |task| yield task } if options[:collect]
198
- send_message message, validate: options[:validate]
199
- return message, task.wait if task
200
- message
189
+ def send_and_collect_if_needed message, options, &block
190
+ if options[:collect]
191
+ task = @task.async { |task| yield task }
192
+ send_message message, validate: options[:validate]
193
+ { sent: message, collector: task.wait }
194
+ else
195
+ send_message message, validate: options[:validate]
196
+ return { sent: message }
197
+ end
201
198
  end
202
199
 
203
200
  def subscribe_to_status component_id, status_list, options={}
@@ -218,7 +215,7 @@ module RSMP
218
215
  "sS" => subscribe_list,
219
216
  'mId' => m_id
220
217
  })
221
- send_while_collecting message, options do |task|
218
+ send_and_collect_if_needed message, options do |task|
222
219
  collect_status_updates task, status_list, options[:collect].merge(m_id: m_id)
223
220
  end
224
221
  end
@@ -264,7 +261,7 @@ module RSMP
264
261
  "arg" => command_list,
265
262
  "mId" => m_id
266
263
  })
267
- send_while_collecting message, options do |task|
264
+ send_and_collect_if_needed message, options do |task|
268
265
  collect_command_responses task, command_list, options[:collect].merge(m_id: m_id)
269
266
  end
270
267
  end
@@ -352,19 +349,27 @@ module RSMP
352
349
  end
353
350
 
354
351
  def collect_status_updates task, status_list, options
355
- StatusUpdateMatcher.new(self, status_list, options).collect task
352
+ collector = StatusUpdateCollector.new(self, status_list, options.merge(task:task))
353
+ collector.collect
354
+ collector
356
355
  end
357
356
 
358
357
  def collect_status_responses task, status_list, options
359
- StatusResponseMatcher.new(self, status_list, options).collect task
358
+ collector = StatusResponseCollector.new(self, status_list, options.merge(task:task))
359
+ collector.collect
360
+ collector
360
361
  end
361
362
 
362
363
  def collect_command_responses task, command_list, options
363
- CommandResponseMatcher.new(self, command_list, options).collect task
364
+ collector = CommandResponseCollector.new(self, command_list, options.merge(task:task))
365
+ collector.collect
366
+ collector
364
367
  end
365
368
 
366
369
  def collect_aggregated_status task, options
367
- AggregatedStatusMatcher.new(self, options).collect task
370
+ collector = AggregatedStatusCollector.new(self, options.merge(task:task))
371
+ collector.collect
372
+ collector
368
373
  end
369
374
  end
370
375
  end
@@ -7,7 +7,6 @@ module RSMP
7
7
  attr_reader :rsmp_versions, :site_id, :supervisor_settings, :proxies, :logger
8
8
 
9
9
  def initialize options={}
10
-
11
10
  handle_supervisor_settings( options[:supervisor_settings] || {} )
12
11
  super options
13
12
  @proxies = []
@@ -161,7 +160,7 @@ module RSMP
161
160
  ip: info[:ip],
162
161
  port: info[:port],
163
162
  task: @task,
164
- settings: {'collect'=>@supervisor_settings['collect']},
163
+ collect: @collect,
165
164
  socket: socket,
166
165
  stream: stream,
167
166
  protocol: protocol,
@@ -282,11 +282,7 @@ module RSMP
282
282
  end
283
283
 
284
284
  def fetch_last_sent_status component, code, name
285
- if @last_status_sent && @last_status_sent[component] && @last_status_sent[component][code]
286
- @last_status_sent[component][code][name]
287
- else
288
- nil
289
- end
285
+ @last_status_sent.dig component, code, name if @last_status_sent
290
286
  end
291
287
 
292
288
  def store_last_sent_status message
@@ -310,10 +306,12 @@ module RSMP
310
306
  by_code.each_pair do |code,by_name|
311
307
  by_name.each_pair do |name,subscription|
312
308
  current = nil
309
+ should_send = false
313
310
  if subscription[:interval] == 0
314
311
  # send as soon as the data changes
315
312
  if component_object
316
313
  current, age = *(component_object.get_status code, name)
314
+ current = current.to_s
317
315
  end
318
316
  last_sent = fetch_last_sent_status component, code, name
319
317
  if current != last_sent
@@ -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.7.4"
2
+ VERSION = "0.8.2"
3
3
  end
data/lib/rsmp.rb CHANGED
@@ -20,10 +20,16 @@ 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'
24
+ require 'rsmp/collect/filter'
23
25
  require 'rsmp/collect/query'
24
- require 'rsmp/collect/matcher'
25
- require 'rsmp/collect/message_queries'
26
- require 'rsmp/collect/message_matchers'
26
+ require 'rsmp/collect/status_query'
27
+ require 'rsmp/collect/command_query'
28
+ require 'rsmp/collect/status_update_or_response_collector'
29
+ require 'rsmp/collect/status_response_collector'
30
+ require 'rsmp/collect/status_update_collector'
31
+ require 'rsmp/collect/command_response_collector'
32
+ require 'rsmp/collect/aggregated_status_collector'
27
33
  require 'rsmp/component'
28
34
  require 'rsmp/site'
29
35
  require 'rsmp/proxy'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rsmp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.4
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emil Tin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-02 00:00:00.000000000 Z
11
+ date: 2022-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -201,18 +201,26 @@ 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
214
+ - lib/rsmp/collect/filter.rb
210
215
  - lib/rsmp/collect/listener.rb
211
- - lib/rsmp/collect/matcher.rb
212
216
  - lib/rsmp/collect/message_matchers.rb
213
- - lib/rsmp/collect/message_queries.rb
214
217
  - lib/rsmp/collect/notifier.rb
215
218
  - lib/rsmp/collect/query.rb
219
+ - lib/rsmp/collect/state_collector.rb
220
+ - lib/rsmp/collect/status_query.rb
221
+ - lib/rsmp/collect/status_response_collector.rb
222
+ - lib/rsmp/collect/status_update_collector.rb
223
+ - lib/rsmp/collect/status_update_or_response_collector.rb
216
224
  - lib/rsmp/component.rb
217
225
  - lib/rsmp/components.rb
218
226
  - lib/rsmp/convert/export/json_schema.rb