rsmp 0.29.0 → 0.32.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.
@@ -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
 
@@ -340,22 +340,22 @@ module RSMP
340
340
  schemas
341
341
  end
342
342
 
343
- def send_message message, reason=nil, validate: true
344
- raise NotReady unless connected?
343
+ def send_message message, reason=nil, validate: true, force: false
344
+ raise NotReady unless connected? unless force
345
345
  raise IOError unless @protocol
346
346
  message.direction = :out
347
347
  message.generate_json
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
@@ -486,7 +486,9 @@ module RSMP
486
486
  if candidates.any?
487
487
  @core_version = candidates.sort_by { |v| Gem::Version.new(v) }.last # pick latest version
488
488
  else
489
- raise HandshakeError.new "RSMP versions [#{message.versions.join(',')}] requested, but only [#{versions.join(',')}] supported."
489
+ reason = "RSMP versions [#{message.versions.join(',')}] requested, but only [#{versions.join(',')}] supported."
490
+ dont_acknowledge message, "Version message rejected", reason, force: true
491
+ raise HandshakeError.new reason
490
492
  end
491
493
  end
492
494
 
@@ -501,7 +503,7 @@ module RSMP
501
503
  check_ingoing_acknowledged original
502
504
  end
503
505
 
504
- def dont_acknowledge original, prefix=nil, reason=nil
506
+ def dont_acknowledge original, prefix=nil, reason=nil, force: true
505
507
  raise InvalidArgument unless original
506
508
  str = [prefix,reason].join(' ')
507
509
  log str, message: original, level: :warning if reason
@@ -510,7 +512,7 @@ module RSMP
510
512
  "rea" => reason || "Unknown reason"
511
513
  })
512
514
  message.original = original.clone
513
- send_message message, "for #{original.type} #{original.m_id_short}"
515
+ send_message message, "for #{original.type} #{original.m_id_short}", force: force
514
516
  end
515
517
 
516
518
  def wait_for_state state, timeout:
data/lib/rsmp/site.rb CHANGED
@@ -58,6 +58,7 @@ module RSMP
58
58
 
59
59
  @site_settings = defaults.deep_merge options[:site_settings]
60
60
  check_sxl_version
61
+ check_core_versions
61
62
  setup_components @site_settings['components']
62
63
  end
63
64
 
@@ -67,10 +68,49 @@ module RSMP
67
68
  RSMP::Schema::find_schema! sxl, version, lenient: true
68
69
  end
69
70
 
71
+ def check_core_versions
72
+ return if @site_settings['core_versions'] == 'all'
73
+ requested = [@site_settings['core_versions']].flatten
74
+ invalid = requested - RSMP::Schema::core_versions
75
+ if invalid.any?
76
+ if invalid.size == 1
77
+ error_str = "Unknown core version: #{invalid.first}"
78
+ else
79
+ error_str = "Unknown core versions: [#{invalid.join(' ')}]"
80
+ end
81
+
82
+ raise RSMP::ConfigurationError.new(error_str)
83
+ end
84
+ end
85
+
86
+ def site_type_name
87
+ "site"
88
+ end
89
+
90
+ def log_site_starting
91
+ log "Starting #{site_type_name} #{@site_settings["site_id"]}", level: :info, timestamp: @clock.now
92
+
93
+ sxl = "Using #{@site_settings["sxl"]} sxl #{@site_settings["sxl_version"]}"
94
+
95
+ versions = @site_settings["core_versions"]
96
+ if versions.is_a?(Array) && versions.size == 1
97
+ versions = versions.first
98
+ end
99
+ if versions == 'all'
100
+ core = "accepting all core versions [#{RSMP::Schema.core_versions.join(', ')}]"
101
+ else
102
+ if versions.is_a?(String)
103
+ core = "accepting only core version #{versions}"
104
+ else
105
+ core = "accepting core versions [#{versions.join(', ')}]"
106
+ end
107
+ end
108
+
109
+ log "#{sxl}, #{core}", level: :info, timestamp: @clock.now
110
+ end
111
+
70
112
  def run
71
- log "Starting site #{@site_settings["site_id"]}",
72
- level: :info,
73
- timestamp: @clock.now
113
+ log_site_starting
74
114
  @proxies.each { |proxy| proxy.start }
75
115
  @proxies.each { |proxy| proxy.wait }
76
116
  end
@@ -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
@@ -94,12 +94,12 @@ module RSMP
94
94
  else
95
95
  reject_connection socket, info
96
96
  end
97
- rescue ConnectionError => e
97
+ rescue ConnectionError, HandshakeError => 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