rsmp 0.2.2 → 0.3.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55265e42f3956b7af530055680da60f82c84d34d7febd19286bf7c78bd2048f8
4
- data.tar.gz: 445498a67e24c6b63cbfceca009ad736e962717eb89e92239bdd7ad4870cf72f
3
+ metadata.gz: 3e3cdaa079ff62d328eec49b03368afeabc463dd8e805a3ca6cd8d04237deefa
4
+ data.tar.gz: e4c49d13767b56e0d0efe552406aac32a714b0ff162e680e65716118b88eb280
5
5
  SHA512:
6
- metadata.gz: '0854ca1835fc15e8ccd10059c3f6f30638daf54677d215799eabae0f87fa4de11c9f825319f10ba92f9d25052fc90927fb54eb9f5df19cb714ba9ead236effbf'
7
- data.tar.gz: 5d60090a4de455ca03fcb0fc182977ff938d0c8df9ea0bfb69d1dc985546c6de6ece83296bb25a64cc5f9e3b67d94e7694ec9702e08877bd6d9e05853d700ebd
6
+ metadata.gz: fa218c2c4404e1a8fdb06c89a60707004adb83ad63b6f9b41a6f8128b8a251112000a6b4f5e6b0cef4109332171172b9c7171e2b7684741d846eafe4f1b573a9
7
+ data.tar.gz: c221e735fce70db77cd0772c336904d7ac707d2525270bed8c4559c103fec632c5f66717b2cec98f81facfeb8824f0acda1dd924a68d5d99e126c114e9e26536
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rsmp (0.2.2)
4
+ rsmp (0.3.2)
5
5
  async (~> 1.29.1)
6
6
  async-io (~> 1.32.1)
7
7
  colorize (~> 0.8.1)
@@ -1,21 +1,20 @@
1
- # Collects matching ingoing and/or outgoing messages and
2
- # wakes up the client once the desired amount has been collected.
3
- # Can listen for ingoing and/or outgoing messages.
4
-
5
1
  module RSMP
6
- class Collector < Listener
7
2
 
3
+ # Collects ingoing and/or outgoing messages.
4
+ # Can filter by message type and wakes up the client once the desired number of messages has been collected.
5
+ class Collector < Listener
8
6
  attr_reader :condition, :messages, :done
9
7
 
10
8
  def initialize proxy, options={}
11
9
  super proxy, options
10
+ @options = options.clone
12
11
  @ingoing = options[:ingoing] == nil ? true : options[:ingoing]
13
12
  @outgoing = options[:outgoing] == nil ? false : options[:outgoing]
14
- @messages = []
15
13
  @condition = Async::Notification.new
16
- @done = false
17
- @options = options
18
- @num = options[:num]
14
+ @title = options[:title] || [@options[:type]].flatten.join('/')
15
+ @options[:timeout] ||= 1
16
+ @options[:num] ||= 1
17
+ reset
19
18
  end
20
19
 
21
20
  def inspect
@@ -34,17 +33,9 @@ module RSMP
34
33
  @condition.wait
35
34
  end
36
35
 
37
- def collect_for task, duration
38
- siphon do
39
- task.sleep duration
40
- end
41
- end
42
-
43
36
  def collect task, options={}, &block
44
- @num = options[:num] if options[:num]
45
- @options[:timeout] = options[:timeout] if options[:timeout]
37
+ @options.merge! options
46
38
  @block = block
47
-
48
39
  unless @done
49
40
  listen do
50
41
  task.with_timeout(@options[:timeout]) do
@@ -52,51 +43,105 @@ module RSMP
52
43
  end
53
44
  end
54
45
  end
46
+ return @error if @error
47
+ self
48
+ rescue Async::TimeoutError
49
+ str = "Did not receive #{@title}"
50
+ str << " in response to #{options[:m_id]}" if options[:m_id]
51
+ str << " within #{@options[:timeout]}s"
52
+ raise RSMP::TimeoutError.new str
53
+ end
55
54
 
56
- if @num == 1
57
- @messages = @messages.first # if one message was requested, return it instead of array
58
- else
59
- @messages = @messages.first @num # return array, but ensure we never return more than requested
60
- end
61
- @messages
55
+ # Get the collected messages.
56
+ # If one message was requested, return it as a plain object instead of array
57
+ def result
58
+ return @messages.first if @options[:num] == 1
59
+ @messages.first @options[:num]
62
60
  end
63
61
 
62
+ # Clear all query results
64
63
  def reset
65
- @message.clear
64
+ @messages = []
65
+ @error = nil
66
66
  @done = false
67
67
  end
68
68
 
69
+ # Check if we receive a NotAck related to initiating request, identified by @m_id.
70
+ def check_not_ack message
71
+ return unless @options[:m_id]
72
+ if message.is_a?(MessageNotAck)
73
+ if message.attribute('oMId') == @options[:m_id]
74
+ m_id_short = RSMP::Message.shorten_m_id @options[:m_id], 8
75
+ @error = RSMP::MessageRejected.new("#{@title} #{m_id_short} was rejected: #{message.attribute('rea')}")
76
+ complete
77
+ end
78
+ false
79
+ end
80
+ end
81
+
82
+ # Handle message. and return true when we're done collecting
69
83
  def notify message
70
84
  raise ArgumentError unless message
85
+ raise RuntimeError.new("can't process message when already done") if @done
86
+ check_not_ack(message)
71
87
  return true if @done
72
- return if message.direction == :in && @ingoing == false
73
- return if message.direction == :out && @outgoing == false
74
- if matches? message
75
- @messages << message
76
- if @num && @messages.size >= @num
77
- @done = true
78
- @proxy.remove_listener self
79
- @condition.signal
80
- end
88
+ check_match message
89
+ complete if done?
90
+ @done
91
+ end
92
+
93
+ # Match message against our collection criteria
94
+ def check_match message
95
+ matched = match? message
96
+ if matched == true
97
+ keep message
98
+ elsif matched == false
99
+ forget message
81
100
  end
82
101
  end
83
102
 
84
- def matches? message
85
- raise ArgumentError unless message
103
+ # Have we collected the required number of messages?
104
+ def done?
105
+ @options[:num] && @messages.size >= @options[:num]
106
+ end
107
+
108
+ # Called when we're done collecting. Remove ourself as a listener,
109
+ # se we don't receive message notifications anymore
110
+ def complete
111
+ @done = true
112
+ @proxy.remove_listener self
113
+ @condition.signal
114
+ end
115
+
116
+ # Store a message in the result array
117
+ def keep message
118
+ @messages << message
119
+ end
120
+
121
+ # Remove a message from the result array
122
+ def forget message
123
+ @messages.delete message
124
+ end
86
125
 
126
+ # Check a message against our match criteria
127
+ # Return true if there's a match
128
+ def match? message
129
+ raise ArgumentError unless message
130
+ return if message.direction == :in && @ingoing == false
131
+ return if message.direction == :out && @outgoing == false
87
132
  if @options[:type]
88
- return false if message == nil
133
+ return if message == nil
89
134
  if @options[:type].is_a? Array
90
- return false unless @options[:type].include? message.type
135
+ return unless @options[:type].include? message.type
91
136
  else
92
- return false unless message.type == @options[:type]
137
+ return unless message.type == @options[:type]
93
138
  end
94
139
  end
95
140
  if @options[:component]
96
- return false if message.attributes['cId'] && message.attributes['cId'] != @options[:component]
141
+ return if message.attributes['cId'] && message.attributes['cId'] != @options[:component]
97
142
  end
98
143
  if @block
99
- return false if @block.call(message) == false
144
+ return if @block.call(message) == false
100
145
  end
101
146
  true
102
147
  end
@@ -13,6 +13,7 @@ module RSMP
13
13
 
14
14
  def setup_components settings
15
15
  return unless settings
16
+ check_main_component settings
16
17
  settings.each_pair do |type,components_by_type|
17
18
  components_by_type.each_pair do |id,settings|
18
19
  @components[id] = build_component(id:id, type:type, settings:settings)
@@ -20,6 +21,15 @@ module RSMP
20
21
  end
21
22
  end
22
23
 
24
+ def check_main_component settings
25
+ unless settings['main'] && settings['main'].size >= 1
26
+ raise ConfigurationError.new("main component must be defined")
27
+ end
28
+ if settings['main'].size > 1
29
+ raise ConfigurationError.new("only one main component can be defined, found #{settings['main'].keys.join(', ')}")
30
+ end
31
+ end
32
+
23
33
  def add_component component
24
34
  @components[component.c_id] = component
25
35
  end
@@ -0,0 +1,195 @@
1
+ module RSMP
2
+
3
+ # Base class for waiting for specific status or command responses, specified by
4
+ # a list of queries. Queries are defined as an array of hashes, e.g
5
+ # [
6
+ # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"securityCode", "v"=>"1111"},
7
+ # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"year", "v"=>"2020"},
8
+ # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/}
9
+ # ]
10
+ #
11
+ # Note that queries can contain regex patterns for values, like /\d+/ in the example above.
12
+ #
13
+ # When an input messages is received it typically contains several items, eg:
14
+ # [
15
+ # {"cCI"=>"M0104", "n"=>"month", "v"=>"9", "age"=>"recent"},
16
+ # {"cCI"=>"M0104", "n"=>"day", "v"=>"29", "age"=>"recent"},
17
+ # {"cCI"=>"M0104", "n"=>"hour", "v"=>"17", "age"=>"recent"}
18
+ # ]
19
+ #
20
+ # Each input item is matched against each of the queries.
21
+ # If a match is found, it's stored in the @results hash, with the query as the key,
22
+ # and a mesage and status as the key. In the example above, this query:
23
+ #
24
+ # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/}
25
+ #
26
+ # matches this input:
27
+ #
28
+ # {"cCI"=>"M0104", "n"=>"month", "v"=>"9", "age"=>"recent"}
29
+ #
30
+ # And the result is stored as:
31
+ # {
32
+ # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/} =>
33
+ # { <StatusResponse message>, {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>"9"} }
34
+ # }
35
+ #
36
+ #
37
+ class Matcher < Collector
38
+ attr_reader :queries
39
+
40
+ # Initialize with a list a wanted statuses
41
+ def initialize proxy, want, options={}
42
+ super proxy, options.merge( ingoing: true, outgoing: false)
43
+ @queries = {}
44
+ want.each do |query|
45
+ @queries[query] = nil
46
+ end
47
+ end
48
+
49
+ # Get the results, as a hash of queries => results
50
+ def result
51
+ @queries
52
+ end
53
+
54
+ # Get messages from results
55
+ def messages
56
+ @queries.map { |query,result| result[:message] }.uniq
57
+ end
58
+
59
+ # Get items from results
60
+ def items
61
+ @queries.map { |query,result| result[:item] }.uniq
62
+ end
63
+
64
+ # Are there queries left to match?
65
+ def done?
66
+ @queries.values.all? { |result| result != nil }
67
+ end
68
+
69
+ # Get a simplified hash of queries, with values set to either true or false,
70
+ # indicating which queries have been matched.
71
+ def status
72
+ @queries.transform_values{ |v| v != nil }
73
+ end
74
+
75
+ # Get a simply array of bools, showing which queries ahve been matched.
76
+ def summary
77
+ @queries.values.map { |v| v != nil }
78
+ end
79
+
80
+ # Mark a query as matched, by linking it to the matched item and message
81
+ def keep query, message, item
82
+ @queries[query] = { message:message, item:item }
83
+ end
84
+
85
+ # Mark a query as not matched
86
+ def forget query
87
+ @queries[query] = nil
88
+ end
89
+
90
+ # Check if a messages matches our criteria.
91
+ # We iterate through each of the status items or return values in the message
92
+ # Breaks as soon as where done matching all queries
93
+ def check_match message
94
+ return unless match?(message)
95
+ @queries.keys.each do |query| # look through queries
96
+ get_items(message).each do |item| # look through status items in message
97
+ break if check_item_match message, query, item
98
+ end
99
+ end
100
+ end
101
+
102
+ # Check if an item matches, and mark query as matched/unmatched accordingly.
103
+ def check_item_match message, query, item
104
+ matched = match_item? query, item
105
+ if matched == true
106
+ keep query, message, item
107
+ true
108
+ elsif matched == false
109
+ forget query
110
+ true
111
+ end
112
+ end
113
+ end
114
+
115
+ # Class for waiting for specific command responses
116
+ class CommandResponseMatcher < Matcher
117
+ def initialize proxy, want, options={}
118
+ super proxy, want, options.merge(
119
+ type: ['CommandResponse','MessageNotAck'],
120
+ title:'command response'
121
+ )
122
+ end
123
+
124
+ # Get items, in our case the return values
125
+ def get_items message
126
+ message.attributes['rvs']
127
+ end
128
+
129
+ # Match a return value item against a query
130
+ def match_item? query, item
131
+ return nil if query['cCI'] && query['cCI'] != item['cCI']
132
+ return nil if query['n'] && query['n'] != item['n']
133
+ if query['v'].is_a? Regexp
134
+ return false if query['v'] && item['v'] !~ query['v']
135
+ else
136
+ return false if query['v'] && item['v'] != query['v']
137
+ end
138
+ true
139
+ end
140
+ end
141
+
142
+ # Base class for waiting for status updates or responses
143
+ class StatusUpdateOrResponseMatcher < Matcher
144
+ def initialize proxy, want, options={}
145
+ super proxy, want, options.merge
146
+ end
147
+
148
+ # Get items, in our case status values
149
+ def get_items message
150
+ message.attributes['sS']
151
+ end
152
+
153
+ # Match a status value against a query
154
+ def match_item? query, item
155
+ return nil if query['sCI'] && query['sCI'] != item['sCI']
156
+ return nil if query['cO'] && query['cO'] != item['cO']
157
+ return nil if query['n'] && query['n'] != item['n']
158
+ return false if query['q'] && query['q'] != item['q']
159
+ if query['s'].is_a? Regexp
160
+ return false if query['s'] && item['s'] !~ query['s']
161
+ else
162
+ return false if query['s'] && item['s'] != query['s']
163
+ end
164
+ true
165
+ end
166
+ end
167
+
168
+ # Class for waiting for specific status responses
169
+ class StatusResponseMatcher < StatusUpdateOrResponseMatcher
170
+ def initialize proxy, want, options={}
171
+ super proxy, want, options.merge(
172
+ type: ['StatusResponse','MessageNotAck'],
173
+ title: 'status response'
174
+ )
175
+ end
176
+ end
177
+
178
+ # Class for waiting for specific status responses
179
+ class StatusUpdateMatcher < StatusUpdateOrResponseMatcher
180
+ def initialize proxy, want, options={}
181
+ super proxy, want, options.merge(
182
+ type: ['StatusUpdate','MessageNotAck'],
183
+ title:'status update'
184
+ )
185
+ end
186
+ end
187
+
188
+ # Class for waiting for an aggregated status response
189
+ class AggregatedStatusMatcher < Collector
190
+ def initialize proxy, options={}
191
+ required = { type: ['AggregatedStatus','MessageNotAck'], title: 'aggregated status' }
192
+ super proxy, options.merge(required)
193
+ end
194
+ end
195
+ end
data/lib/rsmp/proxy.rb CHANGED
@@ -59,6 +59,7 @@ module RSMP
59
59
  def collect task, options, &block
60
60
  collector = RSMP::Collector.new self, options
61
61
  collector.collect task, &block
62
+ collector
62
63
  end
63
64
 
64
65
  def run
@@ -123,7 +124,11 @@ module RSMP
123
124
  message = process_packet json
124
125
  duration = Time.now - beginning
125
126
  ms = (duration*1000).round(4)
126
- per_second = (1.0 / duration).round
127
+ if duration > 0
128
+ per_second = (1.0 / duration).round
129
+ else
130
+ per_second = Float::INFINITY
131
+ end
127
132
  if message
128
133
  type = message.type
129
134
  m_id = Logger.shorten_message_id(message.m_id)
@@ -202,7 +207,6 @@ module RSMP
202
207
  def watchdog_send_timer now
203
208
  return unless @watchdog_started
204
209
  return if @site_settings['intervals']['watchdog'] == :never
205
-
206
210
  if @latest_watchdog_send_at == nil
207
211
  send_watchdog now
208
212
  else
data/lib/rsmp/site.rb CHANGED
@@ -41,10 +41,16 @@ module RSMP
41
41
  },
42
42
  'send_after_connect' => true,
43
43
  'components' => {
44
- 'C1' => {}
44
+ 'main' => {
45
+ 'C1' => {}
46
+ }
45
47
  }
46
48
  }
47
-
49
+ # only one main component can be defined, so replace the default if options define one
50
+ if options.dig(:site_settings,'components','main')
51
+ defaults['components']['main'] = options[:site_settings]['components']['main']
52
+ end
53
+
48
54
  @site_settings = defaults.deep_merge options[:site_settings]
49
55
  check_sxl_version
50
56
  setup_components @site_settings['components']
@@ -3,7 +3,6 @@
3
3
  module RSMP
4
4
  class SiteProxy < Proxy
5
5
  include Components
6
- include SiteProxyWait
7
6
 
8
7
  attr_reader :supervisor, :site_id
9
8
 
@@ -12,7 +11,7 @@ module RSMP
12
11
  initialize_components
13
12
  @supervisor = options[:supervisor]
14
13
  @settings = @supervisor.supervisor_settings.clone
15
- @site_id = nil
14
+ @site_id = options[:site_id]
16
15
  end
17
16
 
18
17
  def revive options
@@ -43,7 +42,7 @@ module RSMP
43
42
 
44
43
  def connection_complete
45
44
  super
46
- sanitized_sxl_version = RSMP::Schemer.sanitize_version(@site_sxl_version)
45
+ sanitized_sxl_version = RSMP::Schemer.sanitize_version(@site_sxl_version)
47
46
  log "Connection to site #{@site_id} established, using core #{@rsmp_version}, #{@sxl} #{sanitized_sxl_version}", level: :info
48
47
  end
49
48
 
@@ -86,7 +85,6 @@ module RSMP
86
85
  acknowledge message
87
86
  send_version @site_id, rsmp_versions
88
87
  @version_determined = true
89
-
90
88
  end
91
89
 
92
90
  def validate_ready action
@@ -104,9 +102,8 @@ module RSMP
104
102
  "mId" => m_id
105
103
  })
106
104
  if options[:collect]
107
- result = nil
108
105
  task = @task.async do |task|
109
- wait_for_aggregated_status task, options[:collect], m_id
106
+ collect_aggregated_status task, options[:collect].merge(m_id: m_id)
110
107
  end
111
108
  send_message message, validate: options[:validate]
112
109
  return message, task.wait
@@ -178,7 +175,7 @@ module RSMP
178
175
  m_id = options[:m_id] || RSMP::Message.make_m_id
179
176
 
180
177
  # additional items can be used when verifying the response,
181
- # but must to remove from the request
178
+ # but must be removed from the request
182
179
  request_list = status_list.map { |item| item.slice('sCI','n') }
183
180
 
184
181
  message = RSMP::StatusRequest.new({
@@ -188,23 +185,8 @@ module RSMP
188
185
  "sS" => request_list,
189
186
  "mId" => m_id
190
187
  })
191
- if options[:collect]
192
- result = nil
193
- task = @task.async do |task|
194
- collect_options = options[:collect].merge status_list: status_list
195
- collect_status_responses task, collect_options, m_id
196
- end
197
- send_message message, validate: options[:validate]
198
-
199
- # task.wait return the result of the task. if the task raised an exception
200
- # it will be reraised. but that mechanish does not work if multiple values
201
- # are returned. so manually raise if first element is an exception
202
- result = task.wait
203
- raise result.first if result.first.is_a? Exception
204
- return message, *result
205
- else
206
- send_message message, validate: options[:validate]
207
- message
188
+ send_while_collecting message, options do |task|
189
+ collect_status_responses task, status_list, options[:collect].merge(m_id: m_id)
208
190
  end
209
191
  end
210
192
 
@@ -213,6 +195,13 @@ module RSMP
213
195
  acknowledge message
214
196
  end
215
197
 
198
+ def send_while_collecting message, options, &block
199
+ task = @task.async { |task| yield task } if options[:collect]
200
+ send_message message, validate: options[:validate]
201
+ return message, task.wait if task
202
+ message
203
+ end
204
+
216
205
  def subscribe_to_status component, status_list, options={}
217
206
  validate_ready 'subscribe to status'
218
207
  m_id = options[:m_id] || RSMP::Message.make_m_id
@@ -228,23 +217,8 @@ module RSMP
228
217
  "sS" => subscribe_list,
229
218
  'mId' => m_id
230
219
  })
231
- if options[:collect]
232
- result = nil
233
- task = @task.async do |task|
234
- collect_options = options[:collect].merge status_list: status_list
235
- collect_status_updates task, collect_options, m_id
236
- end
237
- send_message message, validate: options[:validate]
238
-
239
- # task.wait return the result of the task. if the task raised an exception
240
- # it will be reraised. but that mechanish does not work if multiple values
241
- # are returned. so manually raise if first element is an exception
242
- result = task.wait
243
- raise result.first if result.first.is_a? Exception
244
- return message, *result
245
- else
246
- send_message message, validate: options[:validate]
247
- message
220
+ send_while_collecting message, options do |task|
221
+ collect_status_updates task, status_list, options[:collect].merge(m_id: m_id)
248
222
  end
249
223
  end
250
224
 
@@ -289,23 +263,8 @@ module RSMP
289
263
  "arg" => command_list,
290
264
  "mId" => m_id
291
265
  })
292
- if options[:collect]
293
- result = nil
294
- task = @task.async do |task|
295
- collect_options = options[:collect].merge command_list: command_list
296
- collect_command_responses task, collect_options, m_id
297
- end
298
- send_message message, validate: options[:validate]
299
-
300
- # task.wait return the result of the task. if the task raised an exception
301
- # it will be reraised. but that mechanish does not work if multiple values
302
- # are returned. so manually raise if first element is an exception
303
- result = task.wait
304
- raise result.first if result.first.is_a? Exception
305
- return message, *result
306
- else
307
- send_message message, validate: options[:validate]
308
- message
266
+ send_while_collecting message, options do |task|
267
+ collect_command_responses task, command_list, options[:collect].merge(m_id: m_id)
309
268
  end
310
269
  end
311
270
 
@@ -381,5 +340,37 @@ module RSMP
381
340
  @supervisor.notify_error e, options if @supervisor
382
341
  end
383
342
 
343
+ def wait_for_alarm parent_task, options={}
344
+ matching_alarm = nil
345
+ message = collect(parent_task,options.merge(type: "Alarm", with_message: true, num: 1)) do |message|
346
+ # TODO check components
347
+ matching_alarm = nil
348
+ alarm = message
349
+ next if options[:aCId] && options[:aCId] != alarm.attribute("aCId")
350
+ next if options[:aSp] && options[:aSp] != alarm.attribute("aSp")
351
+ next if options[:aS] && options[:aS] != alarm.attribute("aS")
352
+ matching_alarm = alarm
353
+ break
354
+ end
355
+ if item
356
+ { message: message, status: matching_alarm }
357
+ end
358
+ end
359
+
360
+ def collect_status_updates task, status_list, options
361
+ StatusUpdateMatcher.new(self, status_list, options).collect task
362
+ end
363
+
364
+ def collect_status_responses task, status_list, options
365
+ StatusResponseMatcher.new(self, status_list, options).collect task
366
+ end
367
+
368
+ def collect_command_responses task, command_list, options
369
+ CommandResponseMatcher.new(self, command_list, options).collect task
370
+ end
371
+
372
+ def collect_aggregated_status task, options
373
+ AggregatedStatusMatcher.new(self, options).collect task
374
+ end
384
375
  end
385
376
  end
@@ -1,171 +0,0 @@
1
- # waiting for various types of messages and reponses from remote sites
2
- module RSMP
3
- module SiteProxyWait
4
-
5
- def wait_for_alarm parent_task, options={}
6
- matching_alarm = nil
7
- message = collect(parent_task,options.merge(type: "Alarm", with_message: true, num: 1)) do |message|
8
- # TODO check components
9
- matching_alarm = nil
10
- alarm = message
11
- next if options[:aCId] && options[:aCId] != alarm.attribute("aCId")
12
- next if options[:aSp] && options[:aSp] != alarm.attribute("aSp")
13
- next if options[:aS] && options[:aS] != alarm.attribute("aS")
14
- matching_alarm = alarm
15
- break
16
- end
17
- if item
18
- { message: message, status: matching_alarm }
19
- end
20
- end
21
-
22
- def collect_status_updates task, options, m_id
23
- collect_status_updates_or_responses task, 'StatusUpdate', options, m_id
24
- end
25
-
26
- def collect_status_responses task, options, m_id
27
- collect_status_updates_or_responses task, 'StatusResponse', options, m_id
28
- end
29
-
30
- def collect_command_responses parent_task, options, m_id
31
- task.annotate "wait for command response"
32
- want = options[:command_list].clone
33
- result = {}
34
- messages = []
35
- collect(parent_task,options.merge({
36
- type: ['CommandResponse','MessageNotAck'],
37
- num: 1
38
- })) do |message|
39
- if message.is_a?(MessageNotAck)
40
- if message.attribute('oMId') == m_id
41
- # set result to an exception, but don't raise it.
42
- # this will be returned by the task and stored as the task result
43
- # when the parent task call wait() on the task, the exception
44
- # will be raised in the parent task, and caught by rspec.
45
- # rspec will then show the error and record the test as failed
46
- m_id_short = RSMP::Message.shorten_m_id m_id, 8
47
- result = RSMP::MessageRejected.new "Command request #{m_id_short} was rejected: #{message.attribute('rea')}"
48
- next true # done, no more messages wanted
49
- else
50
- false
51
- end
52
- else
53
- add = false
54
- # look through querues
55
- want.each_with_index do |query,i|
56
- # look through items in message
57
- message.attributes['rvs'].each do |input|
58
- matching = command_match? query, input
59
- if matching == true
60
- result[query] = input
61
- add = true
62
- elsif matching == false
63
- result.delete query
64
- end
65
- end
66
- end
67
- messages << message if add
68
- result.size == want.size # any queries left to match?
69
- end
70
- end
71
- return result, messages
72
- rescue Async::TimeoutError
73
- raise RSMP::TimeoutError.new "Did not receive correct command response to #{m_id} within #{options[:timeout]}s"
74
- end
75
-
76
- def collect_status_updates_or_responses task, type, options, m_id
77
- want = options[:status_list].clone
78
- result = {}
79
- messages = []
80
- # wait for a status update
81
- collect(task,options.merge({
82
- type: [type,'MessageNotAck'],
83
- num: 1
84
- })) do |message|
85
- if message.is_a?(MessageNotAck)
86
- if message.attribute('oMId') == m_id
87
- # set result to an exception, but don't raise it.
88
- # this will be returned by the task and stored as the task result
89
- # when the parent task call wait() on the task, the exception
90
- # will be raised in the parent task, and caught by rspec.
91
- # rspec will then show the error and record the test as failed
92
- m_id_short = RSMP::Message.shorten_m_id m_id, 8
93
- result = RSMP::MessageRejected.new "Status request #{m_id_short} was rejected: #{message.attribute('rea')}"
94
- next true # done, no more messages wanted
95
- end
96
- false
97
- else
98
- found = []
99
- add = false
100
- # look through querues
101
- want.each_with_index do |query,i|
102
- # look through status items in message
103
- message.attributes['sS'].each do |input|
104
- matching = status_match? query, input
105
- if matching == true
106
- result[query] = input
107
- add = true
108
- elsif matching == false
109
- result.delete query
110
- end
111
- end
112
- end
113
- messages << message if add
114
- result.size == want.size # any queries left to match?
115
- end
116
- end
117
- return result, messages
118
- rescue Async::TimeoutError
119
- type_str = {'StatusUpdate'=>'update', 'StatusResponse'=>'response'}[type]
120
- raise RSMP::TimeoutError.new "Did not received correct status #{type_str} in reply to #{m_id} within #{options[:timeout]}s"
121
- end
122
-
123
- def status_match? query, item
124
- return nil if query['sCI'] && query['sCI'] != item['sCI']
125
- return nil if query['n'] && query['n'] != item['n']
126
- return false if query['q'] && query['q'] != item['q']
127
- if query['s'].is_a? Regexp
128
- return false if query['s'] && item['s'] !~ query['s']
129
- else
130
- return false if query['s'] && item['s'] != query['s']
131
- end
132
- true
133
- end
134
-
135
- def command_match? query, item
136
- return nil if query['cCI'] && query['cCI'] != item['cCI']
137
- return nil if query['n'] && query['n'] != item['n']
138
- if query['v'].is_a? Regexp
139
- return false if query['v'] && item['v'] !~ query['v']
140
- else
141
- return false if query['v'] && item['v'] != query['v']
142
- end
143
- true
144
- end
145
-
146
- def wait_for_aggregated_status parent_task, options, m_id
147
- collect(parent_task,options.merge({
148
- type: ['AggregatedStatus','MessageNotAck'],
149
- num: 1
150
- })) do |message|
151
- if message.is_a?(MessageNotAck)
152
- if message.attribute('oMId') == m_id
153
- # set result to an exception, but don't raise it.
154
- # this will be returned by the task and stored as the task result
155
- # when the parent task call wait() on the task, the exception
156
- # will be raised in the parent task, and caught by rspec.
157
- # rspec will then show the error and record the test as failed
158
- m_id_short = RSMP::Message.shorten_m_id m_id, 8
159
- result = RSMP::MessageRejected.new "Aggregated status request #{m_id_short} was rejected: #{message.attribute('rea')}"
160
- next true # done, no more messages wanted
161
- else
162
- false
163
- end
164
- else
165
- true
166
- end
167
- end
168
- end
169
-
170
- end
171
- end
@@ -175,7 +175,7 @@ module RSMP
175
175
  proxy.revive settings
176
176
  else
177
177
  check_max_sites
178
- proxy = build_proxy settings
178
+ proxy = build_proxy settings.merge(site_id:id) # keep the id learned by peeking above
179
179
  @proxies.push proxy
180
180
  end
181
181
  proxy.run # will run until the site disconnects
@@ -225,7 +225,12 @@ module RSMP
225
225
  return site if site
226
226
  wait_for(@site_id_condition,timeout) { find_site site_id }
227
227
  rescue Async::TimeoutError
228
- raise RSMP::TimeoutError.new "Site '#{site_id}' did not connect within #{timeout}s"
228
+ if site_id == :any
229
+ str = "No site connected"
230
+ else
231
+ str = "Site '#{site_id}' did not connect"
232
+ end
233
+ raise RSMP::TimeoutError.new "#{str} within #{timeout}s"
229
234
  end
230
235
 
231
236
  def wait_for_site_disconnect site_id, timeout
@@ -129,7 +129,7 @@ module RSMP
129
129
  message = AggregatedStatus.new({
130
130
  "aSTS" => clock.to_s,
131
131
  "cId" => component.c_id,
132
- "fP" => 'NormalControl',
132
+ "fP" => nil,
133
133
  "fS" => nil,
134
134
  "se" => component.aggregated_status_bools,
135
135
  "mId" => m_id,
data/lib/rsmp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module RSMP
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.2"
3
3
  end
data/lib/rsmp.rb CHANGED
@@ -21,11 +21,11 @@ require 'rsmp/notifier'
21
21
 
22
22
  require 'rsmp/listener'
23
23
  require 'rsmp/collector'
24
+ require 'rsmp/matcher'
24
25
  require 'rsmp/component'
25
26
  require 'rsmp/site'
26
27
  require 'rsmp/proxy'
27
28
  require 'rsmp/supervisor_proxy'
28
- require 'rsmp/site_proxy_wait'
29
29
  require 'rsmp/site_proxy'
30
30
  require 'rsmp/error'
31
31
  require 'rsmp/message'
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.2.2
4
+ version: 0.3.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-09-22 00:00:00.000000000 Z
11
+ date: 2021-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -217,6 +217,7 @@ files:
217
217
  - lib/rsmp/listener.rb
218
218
  - lib/rsmp/logger.rb
219
219
  - lib/rsmp/logging.rb
220
+ - lib/rsmp/matcher.rb
220
221
  - lib/rsmp/message.rb
221
222
  - lib/rsmp/node.rb
222
223
  - lib/rsmp/notifier.rb
@@ -254,7 +255,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
254
255
  - !ruby/object:Gem::Version
255
256
  version: '0'
256
257
  requirements: []
257
- rubygems_version: 3.2.26
258
+ rubygems_version: 3.2.15
258
259
  signing_key:
259
260
  specification_version: 4
260
261
  summary: RoadSide Message Protocol (RSMP) library.