rsmp 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,10 +3,13 @@ module RSMP
3
3
  # Filter messages based on type, direction and component id.
4
4
  # Used by Collectors.
5
5
  class Filter
6
- def initialize ingoing:true, outgoing:true, type:, component:nil
6
+
7
+ attr_reader :ingoing, :outgoing, :type, :component
8
+
9
+ def initialize ingoing:true, outgoing:true, type:nil, component:nil
7
10
  @ingoing = ingoing
8
11
  @outgoing = outgoing
9
- @type = type
12
+ @type = type ? [type].flatten : nil
10
13
  @component = component
11
14
  end
12
15
 
@@ -16,10 +19,8 @@ module RSMP
16
19
  return false if message.direction == :in && @ingoing == false
17
20
  return false if message.direction == :out && @outgoing == false
18
21
  if @type
19
- if @type.is_a? Array
22
+ unless message.is_a?(MessageNotAck)
20
23
  return false unless @type.include? message.type
21
- else
22
- return false unless message.type == @type
23
24
  end
24
25
  end
25
26
  if @component
@@ -27,5 +28,9 @@ module RSMP
27
28
  end
28
29
  true
29
30
  end
31
+
32
+ def reject? message
33
+ !accept?(message)
34
+ end
30
35
  end
31
36
  end
@@ -1,7 +1,7 @@
1
1
  module RSMP
2
2
 
3
3
  # Class that matches a single status or command item
4
- class Query
4
+ class Matcher
5
5
  attr_reader :want, :got, :message
6
6
 
7
7
  def initialize want
@@ -0,0 +1,39 @@
1
+ # Receives items from a Distributor and keeps them in a queue.
2
+ # The client can wait for mesages and will get them one by one.
3
+
4
+ module RSMP
5
+ class Queue
6
+ include Receiver
7
+
8
+ attr_reader :messages
9
+
10
+ def initialize distributor, filter: nil, task:
11
+ initialize_receiver distributor, filter: filter
12
+ @condition = Async::Notification.new
13
+ @task = task
14
+ clear
15
+ end
16
+
17
+ def clear
18
+ @messages = []
19
+ end
20
+
21
+ def wait_for_message timeout: nil
22
+ if @messages.empty?
23
+ if timeout
24
+ @task.with_timeout(timeout) { @condition.wait }
25
+ else
26
+ @condition.wait
27
+ end
28
+ end
29
+ @messages.shift
30
+ rescue Async::TimeoutError
31
+ raise RSMP::TimeoutError
32
+ end
33
+
34
+ def handle_message message
35
+ @messages << message
36
+ @condition.signal
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,40 @@
1
+ # Receives items from a Distributor, as long as it's
2
+ # installed as a receiver.
3
+ # Optionally can filter mesage using a Filter.
4
+
5
+ module RSMP
6
+ module Receiver
7
+ include Inspect
8
+
9
+ def initialize_receiver distributor, filter: nil
10
+ @distributor = distributor
11
+ @filter = filter
12
+ end
13
+
14
+ def start_receiving
15
+ @distributor.add_receiver(self)
16
+ end
17
+
18
+ def stop_receiving
19
+ @distributor.remove_receiver(self)
20
+ end
21
+
22
+ def receive message
23
+ handle_message(message) if accept_message?(message)
24
+ end
25
+
26
+ def receive_error error, options={}
27
+ end
28
+
29
+ def accept_message? message
30
+ @filter == nil || @filter.accept?(message)
31
+ end
32
+
33
+ def reject_message? message
34
+ !accept_message?(message)
35
+ end
36
+
37
+ def handle_message message
38
+ end
39
+ end
40
+ end
@@ -1,13 +1,13 @@
1
1
  module RSMP
2
2
  # Base class for waiting for specific status or command responses, specified by
3
- # a list of queries. Queries are defined as an array of hashes, e.g
3
+ # a list of matchers. Matchers are defined as an array of hashes, e.g
4
4
  # [
5
5
  # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"securityCode", "v"=>"1111"},
6
6
  # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"year", "v"=>"2020"},
7
7
  # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/}
8
8
  # ]
9
9
  #
10
- # Note that queries can contain regex patterns for values, like /\d+/ in the example above.
10
+ # Note that matchers can contain regex patterns for values, like /\d+/ in the example above.
11
11
  #
12
12
  # When an input messages is received it typically contains several items, eg:
13
13
  # [
@@ -16,9 +16,9 @@ module RSMP
16
16
  # {"cCI"=>"M0104", "n"=>"hour", "v"=>"17", "age"=>"recent"}
17
17
  # ]
18
18
  #
19
- # Each input item is matched against each of the queries.
20
- # If a match is found, it's stored in the @results hash, with the query as the key,
21
- # and a mesage and status as the key. In the example above, this query:
19
+ # Each input item is matched against each of the matchers.
20
+ # If a match is found, it's stored in the @results hash, with the matcher as the key,
21
+ # and a mesage and status as the key. In the example above, this matcher:
22
22
  #
23
23
  # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/}
24
24
  #
@@ -32,101 +32,101 @@ module RSMP
32
32
  # { <StatusResponse message>, {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>"9"} }
33
33
  # }
34
34
  class StateCollector < Collector
35
- attr_reader :queries
35
+ attr_reader :matchers
36
36
 
37
37
  # Initialize with a list of wanted statuses
38
38
  def initialize proxy, want, options={}
39
39
  raise ArgumentError.new("num option cannot be used") if options[:num]
40
- super proxy, options.merge( ingoing: true, outgoing: false)
41
- @queries = want.map { |item| build_query item }
40
+ super proxy, options
41
+ @matchers = want.map { |item| build_matcher item }
42
42
  end
43
43
 
44
- # Build a query object.
45
- # Sub-classes should override to use their own query classes.
46
- def build_query want
47
- Query.new want
44
+ # Build a matcher object.
45
+ # Sub-classes should override to use their own matcher classes.
46
+ def build_matcher want
47
+ Matcher.new want
48
48
  end
49
49
 
50
50
  # Get a results
51
- def query_result want
52
- query = @queries.find { |q| q.want == want}
53
- raise unless query
54
- query.got
51
+ def matcher_result want
52
+ matcher = @matchers.find { |q| q.want == want}
53
+ raise unless matcher
54
+ matcher.got
55
55
  end
56
56
 
57
- # Get an array of the last item received for each query
57
+ # Get an array of the last item received for each matcher
58
58
  def reached
59
- @queries.map { |query| query.got }.compact
59
+ @matchers.map { |matcher| matcher.got }.compact
60
60
  end
61
61
 
62
62
  # Get messages from results
63
63
  def messages
64
- @queries.map { |query| query.message }.uniq.compact
64
+ @matchers.map { |matcher| matcher.message }.uniq.compact
65
65
  end
66
66
 
67
- # Return progress as completes queries vs. total number of queries
67
+ # Return progress as completes matchers vs. total number of matchers
68
68
  def progress
69
- need = @queries.size
70
- reached = @queries.count { |query| query.done? }
69
+ need = @matchers.size
70
+ reached = @matchers.count { |matcher| matcher.done? }
71
71
  { need: need, reached: reached }
72
72
  end
73
73
 
74
- # Are there queries left to type_match?
74
+ # Are there matchers left to type_match?
75
75
  def done?
76
- @queries.all? { |query| query.done? }
76
+ @matchers.all? { |matcher| matcher.done? }
77
77
  end
78
78
 
79
- # Get a simplified hash of queries, with values set to either true or false,
80
- # indicating which queries have been matched.
81
- def query_status
82
- @queries.map { |query| [query.want, query.done?] }.to_h
79
+ # Get a simplified hash of matchers, with values set to either true or false,
80
+ # indicating which matchers have been matched.
81
+ def matcher_status
82
+ @matchers.map { |matcher| [matcher.want, matcher.done?] }.to_h
83
83
  end
84
84
 
85
- # Get a simply array of bools, showing which queries have been matched.
85
+ # Get a simply array of bools, showing which matchers have been matched.
86
86
  def summary
87
- @queries.map { |query| query.done? }
87
+ @matchers.map { |matcher| matcher.done? }
88
88
  end
89
89
 
90
90
  # Check if a messages matches our criteria.
91
- # Match each query against each item in the message
91
+ # Match each matcher against each item in the message
92
92
  def perform_match message
93
93
  return false if super(message) == false
94
94
  return unless collecting?
95
- @queries.each do |query| # look through queries
95
+ @matchers.each do |matcher| # look through matchers
96
96
  get_items(message).each do |item| # look through items in message
97
- matched = query.perform_match(item,message,@block)
97
+ matched = matcher.perform_match(item,message,@block)
98
98
  return unless collecting?
99
99
  if matched != nil
100
- #type = {true=>'match',false=>'mismatch'}[matched]
101
- #@notifier.log "#{@title.capitalize} #{message.m_id_short} collect #{type} #{query.want}, item #{item}", level: :debug
100
+ type = {true=>'match',false=>'mismatch'}[matched]
101
+ @distributor.log "#{@title.capitalize} #{message.m_id_short} collect #{type} #{matcher.want}, item #{item}", level: :debug
102
102
  if matched == true
103
- query.keep message, item
103
+ matcher.keep message, item
104
104
  elsif matched == false
105
- query.forget
105
+ matcher.forget
106
106
  end
107
107
  end
108
108
  end
109
109
  end
110
110
  end
111
111
 
112
- # don't collect anything. Query will collect them instead
112
+ # don't collect anything. Matcher will collect them instead
113
113
  def keep message
114
114
  end
115
115
 
116
116
  def describe
117
- @queries.map {|q| q.want.to_s }
117
+ @matchers.map {|q| q.want.to_s }
118
118
  end
119
119
 
120
120
  # return a string that describes the attributes that we're looking for
121
- def describe_query
122
- "#{super} matching #{query_want_hash.to_s}"
121
+ def describe_matcher
122
+ "#{super} matching #{matcher_want_hash.to_s}"
123
123
  end
124
124
 
125
- # return a hash that describe the status of all queries
125
+ # return a hash that describe the status of all matchers
126
126
  def progress_hash
127
127
  h = {}
128
- @queries.each do |query|
129
- want = query.want
128
+ @matchers.each do |matcher|
129
+ want = matcher.want
130
130
  if want['cCI']
131
131
  cCI = want['cCI']
132
132
  h[cCI] ||= {}
@@ -140,8 +140,8 @@ module RSMP
140
140
  h[sCI] ||= {}
141
141
  n = want['n']
142
142
  s = want['s']
143
- if query.got && query.got['s']
144
- h[sCI][n] = { {s=>query.got['s']} => query.done? }
143
+ if matcher.got && matcher.got['s']
144
+ h[sCI][n] = { {s=>matcher.got['s']} => matcher.done? }
145
145
  else
146
146
  h[sCI][n] = { s=>nil }
147
147
  end
@@ -152,15 +152,15 @@ module RSMP
152
152
 
153
153
  # return a string that describe how many many messages have been collected
154
154
  def describe_progress
155
- num_queries = @queries.size
156
- num_matched = @queries.count { |query| query.done? }
157
- ".. Matched #{num_matched}/#{num_queries} with #{progress_hash.to_s}"
155
+ num_matchers = @matchers.size
156
+ num_matched = @matchers.count { |matcher| matcher.done? }
157
+ ".. Matched #{num_matched}/#{num_matchers} with #{progress_hash.to_s}"
158
158
  end
159
159
 
160
- def query_want_hash
160
+ def matcher_want_hash
161
161
  h = {}
162
- @queries.each do |query|
163
- item = query.want
162
+ @matchers.each do |matcher|
163
+ item = matcher.want
164
164
  if item['cCI']
165
165
  cCI = item['cCI']
166
166
  h[cCI] ||= {}
@@ -181,11 +181,11 @@ module RSMP
181
181
  end
182
182
 
183
183
  # return a hash that describe the end result
184
- def query_got_hash
184
+ def matcher_got_hash
185
185
  h = {}
186
- @queries.each do |query|
187
- want = query.want
188
- got = query.got
186
+ @matchers.each do |matcher|
187
+ want = matcher.want
188
+ got = matcher.got
189
189
  if want['cCI']
190
190
  cCI = want['cCI']
191
191
  h[cCI] ||= {}
@@ -207,7 +207,7 @@ module RSMP
207
207
 
208
208
  # log when we end collecting
209
209
  def log_complete
210
- @notifier.log "#{identifier}: Completed with #{query_got_hash.to_s}", level: :collect
210
+ @distributor.log "#{identifier}: Completed with #{matcher_got_hash.to_s}", level: :collect
211
211
  end
212
212
  end
213
213
  end
@@ -2,15 +2,18 @@ module RSMP
2
2
  # Base class for waiting for status updates or responses
3
3
  class StatusCollector < StateCollector
4
4
  def initialize proxy, want, options={}
5
- super proxy, want, options.merge(title: 'status response')
5
+ type = []
6
+ type << 'StatusUpdate' unless options[:updates] == false
7
+ type << 'StatusResponse' unless options[:reponses] == false
6
8
 
7
- @options[:type] ||= []
8
- @options[:type] << 'StatusUpdate' unless options[:updates] == false
9
- @options[:type] << 'StatusResponse' unless options[:reponses] == false
9
+ super proxy, want, options.merge(
10
+ title: 'status response',
11
+ filter: RSMP::Filter.new(ingoing: true, outgoing: false, type: type)
12
+ )
10
13
  end
11
14
 
12
- def build_query want
13
- RSMP::StatusQuery.new want
15
+ def build_matcher want
16
+ RSMP::StatusMatcher.new want
14
17
  end
15
18
 
16
19
  # Get items, in our case status values
@@ -1,7 +1,7 @@
1
1
  module RSMP
2
2
  # Match a specific status response or update
3
- class StatusQuery < Query
4
- # Match a status value against a query
3
+ class StatusMatcher < Matcher
4
+ # Match a status value against a matcher
5
5
  def match? item
6
6
  return nil if @want['sCI'] && @want['sCI'] != item['sCI']
7
7
  return nil if @want['cO'] && @want['cO'] != item['cO']
@@ -56,5 +56,9 @@ module RSMP
56
56
  log "Deactivating alarm #{alarm_code}", level: :info
57
57
  @node.alarm_activated_or_deactivated alarm
58
58
  end
59
+
60
+ def status_updates_sent
61
+ end
62
+
59
63
  end
60
64
  end
data/lib/rsmp/node.rb CHANGED
@@ -36,7 +36,7 @@ module RSMP
36
36
  @ignore_errors = was
37
37
  end
38
38
 
39
- def notify_error e, options={}
39
+ def distribute_error e, options={}
40
40
  return if @ignore_errors.find { |klass| e.is_a? klass }
41
41
  if options[:level] == :internal
42
42
  log ["#{e.to_s} in task: #{Async::Task.current.to_s}",e.backtrace].flatten.join("\n"), level: :error
data/lib/rsmp/proxy.rb CHANGED
@@ -9,7 +9,7 @@ module RSMP
9
9
  WRAPPING_DELIMITER = "\f"
10
10
 
11
11
  include Logging
12
- include Notifier
12
+ include Distributor
13
13
  include Inspect
14
14
  include Task
15
15
 
@@ -47,7 +47,7 @@ module RSMP
47
47
  close_socket
48
48
  stop_reader
49
49
  set_state :disconnected
50
- notify_error DisconnectError.new("Connection was closed")
50
+ distribute_error DisconnectError.new("Connection was closed")
51
51
 
52
52
  # stop timer
53
53
  # as we're running inside the timer, code after stop_timer() will not be called,
@@ -188,7 +188,7 @@ module RSMP
188
188
  rescue Errno::EPIPE
189
189
  log "Broken pipe", level: :warning
190
190
  rescue StandardError => e
191
- notify_error e, level: :internal
191
+ distribute_error e, level: :internal
192
192
  end
193
193
 
194
194
  def read_line
@@ -213,8 +213,8 @@ module RSMP
213
213
  log str, level: :statistics
214
214
  end
215
215
 
216
- def notify_error e, options={}
217
- @node.notify_error e, options
216
+ def receive_error e, options={}
217
+ @node.receive_error e, options
218
218
  end
219
219
 
220
220
  def start_watchdog
@@ -265,7 +265,7 @@ module RSMP
265
265
  rescue Errno::EPIPE => e
266
266
  log "Timer: Broken pipe", level: :warning
267
267
  rescue StandardError => e
268
- notify_error e, level: :internal
268
+ distribute_error e, level: :internal
269
269
  end
270
270
  ensure
271
271
  next_time += interval
@@ -312,7 +312,7 @@ module RSMP
312
312
  begin
313
313
  close
314
314
  ensure
315
- notify_error MissingAcknowledgment.new(str)
315
+ distribute_error MissingAcknowledgment.new(str)
316
316
  end
317
317
  end
318
318
  end
@@ -325,7 +325,7 @@ module RSMP
325
325
  if left < 0
326
326
  str = "No Watchdog received within #{timeout} seconds"
327
327
  log str, level: :warning
328
- notify MissingWatchdog.new(str)
328
+ distribute MissingWatchdog.new(str)
329
329
  end
330
330
  end
331
331
 
@@ -348,14 +348,14 @@ module RSMP
348
348
  message.validate get_schemas unless validate==false
349
349
  @protocol.write_lines message.json
350
350
  expect_acknowledgement message
351
- notify message
351
+ distribute message
352
352
  log_send message, reason
353
353
  rescue EOFError, IOError
354
354
  buffer_message message
355
355
  rescue SchemaError, RSMP::Schema::Error => e
356
356
  str = "Could not send #{message.type} because schema validation failed: #{e.message}"
357
357
  log str, message: message, level: :error
358
- notify_error e.exception("#{str} #{message.json}")
358
+ distribute_error e.exception("#{str} #{message.json}")
359
359
  end
360
360
 
361
361
  def buffer_message message
@@ -398,39 +398,39 @@ module RSMP
398
398
  message = Message.build attributes, json
399
399
  message.validate(get_schemas) if should_validate_ingoing_message?(message)
400
400
  verify_sequence message
401
- deferred_notify do
402
- notify message
401
+ with_deferred_distribution do
402
+ distribute message
403
403
  process_message message
404
404
  end
405
405
  process_deferred
406
406
  message
407
407
  rescue InvalidPacket => e
408
408
  str = "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}"
409
- notify_error e.exception(str)
409
+ distribute_error e.exception(str)
410
410
  log str, level: :warning
411
411
  nil
412
412
  rescue MalformedMessage => e
413
413
  str = "Received malformed message, #{e.message}"
414
- notify_error e.exception(str)
414
+ distribute_error e.exception(str)
415
415
  log str, message: Malformed.new(attributes), level: :warning
416
416
  # cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
417
417
  nil
418
418
  rescue SchemaError, RSMP::Schema::Error => e
419
419
  reason = "schema errors: #{e.message}"
420
420
  str = "Received invalid #{message.type}"
421
- notify_error e.exception(str), message: message
421
+ distribute_error e.exception(str), message: message
422
422
  dont_acknowledge message, str, reason
423
423
  message
424
424
  rescue InvalidMessage => e
425
425
  reason = "#{e.message}"
426
426
  str = "Received invalid #{message.type},"
427
- notify_error e.exception("#{str} #{message.json}"), message: message
427
+ distribute_error e.exception("#{str} #{message.json}"), message: message
428
428
  dont_acknowledge message, str, reason
429
429
  message
430
430
  rescue FatalError => e
431
431
  reason = e.message
432
432
  str = "Rejected #{message.type},"
433
- notify_error e.exception(str), message: message
433
+ distribute_error e.exception(str), message: message
434
434
  dont_acknowledge message, str, reason
435
435
  close
436
436
  message
@@ -24,7 +24,7 @@ module RSMP
24
24
  rescue RSMP::ConnectionError => e
25
25
  log e, level: :error
26
26
  rescue StandardError => e
27
- notify_error e, level: :internal
27
+ distribute_error e, level: :internal
28
28
  ensure
29
29
  close
30
30
  end
@@ -77,7 +77,7 @@ module RSMP
77
77
  rescue RSMP::RepeatedAlarmError, RSMP::RepeatedStatusError, RSMP::TimestampError => e
78
78
  str = "Rejected #{message.type} message,"
79
79
  dont_acknowledge message, str, "#{e}"
80
- notify_error e.exception("#{str}#{e.message} #{message.json}")
80
+ distribute_error e.exception("#{str}#{e.message} #{message.json}")
81
81
  end
82
82
 
83
83
  def process_command_response message
@@ -367,8 +367,8 @@ module RSMP
367
367
  end
368
368
  end
369
369
 
370
- def notify_error e, options={}
371
- @supervisor.notify_error e, options if @supervisor
370
+ def receive_error e, options={}
371
+ @supervisor.receive_error e, options if @supervisor
372
372
  distribute_error e, options
373
373
  end
374
374
 
@@ -69,11 +69,11 @@ module RSMP
69
69
  tasks = @endpoint.accept do |socket| # creates async tasks
70
70
  handle_connection(socket)
71
71
  rescue StandardError => e
72
- notify_error e, level: :internal
72
+ distribute_error e, level: :internal
73
73
  end
74
74
  tasks.each { |task| task.wait }
75
75
  rescue StandardError => e
76
- notify_error e, level: :internal
76
+ distribute_error e, level: :internal
77
77
  end
78
78
 
79
79
  # stop
@@ -96,10 +96,10 @@ module RSMP
96
96
  end
97
97
  rescue ConnectionError => e
98
98
  log "Rejected connection from #{remote_ip}:#{remote_port}, #{e.to_s}", level: :warning
99
- notify_error e
99
+ distribute_error e
100
100
  rescue StandardError => e
101
101
  log "Connection: #{e.to_s}", exception: e, level: :error
102
- notify_error e, level: :internal
102
+ distribute_error e, level: :internal
103
103
  ensure
104
104
  close socket, info
105
105
  end
@@ -34,7 +34,7 @@ module RSMP
34
34
  log e, level: :error
35
35
  break if reconnect_delay == false
36
36
  rescue StandardError => e
37
- notify_error e, level: :internal
37
+ distribute_error e, level: :internal
38
38
  break if reconnect_delay == false
39
39
  ensure
40
40
  close
@@ -474,6 +474,7 @@ module RSMP
474
474
  set_nts_message_attributes update
475
475
  send_message update
476
476
  store_last_sent_status update
477
+ component.status_updates_sent
477
478
  end
478
479
  end
479
480
 
@@ -10,19 +10,35 @@ class RSMP::TLC::SignalPriority
10
10
  set_state 'received'
11
11
  end
12
12
 
13
+ def prune?
14
+ @state == 'stale' || @state == 'completed'
15
+ end
16
+
17
+ def cancel
18
+ if @state == 'activated'
19
+ set_state 'completed'
20
+ end
21
+ end
22
+
13
23
  def set_state state
14
24
  @state = state
15
25
  @updated = node.clock.now
16
- node.signal_priority_changed self, @state
26
+ @node.signal_priority_changed self, @state
17
27
  end
18
28
 
19
29
  def timer
20
30
  @age = @node.clock.now - @updated
21
31
  case @state
22
32
  when 'received'
23
- set_state 'activated' if @age >= 0.5
33
+ if @age >= 0.5
34
+ @node.log "Priority request #{@id} activated.", level: :info
35
+ set_state 'activated'
36
+ end
24
37
  when 'activated'
25
- set_state 'completed' if @age >= 0.5
38
+ if @age >= 1
39
+ @node.log "Priority request #{@id} became stale.", level: :info
40
+ set_state 'stale'
41
+ end
26
42
  end
27
43
  end
28
44
  end