rsmp 0.3.6 → 0.4.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/rsmp/{collector.rb → collect/collector.rb} +7 -4
- data/lib/rsmp/{listener.rb → collect/listener.rb} +0 -0
- data/lib/rsmp/collect/matcher.rb +100 -0
- data/lib/rsmp/collect/message_matchers.rb +64 -0
- data/lib/rsmp/collect/message_queries.rb +33 -0
- data/lib/rsmp/{notifier.rb → collect/notifier.rb} +0 -0
- data/lib/rsmp/collect/query.rb +44 -0
- data/lib/rsmp/component.rb +1 -1
- data/lib/rsmp/components.rb +3 -3
- data/lib/rsmp/site_proxy.rb +3 -23
- data/lib/rsmp/tlc.rb +16 -6
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +7 -4
- data/test.rb +27 -0
- metadata +10 -6
- data/lib/rsmp/matcher.rb +0 -195
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 05d67026eef7404afe7979c198bcfe97bd63e231400e174988ab14454f23ea09
|
4
|
+
data.tar.gz: e82717dc287f0eb2e91577cf5e02d752cdf1d1fef6bdac6f4cf3569494d8da3e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d971e86ebd1ef25ff52cc4babd23567b2b5e870cc70f7edfc18ce7925a850fd94dfa851e70c16a43d4df1ef174045999ad41c0fd5ad2ed7c074ae022282e441
|
7
|
+
data.tar.gz: 5f991d2aee4af7ac5a30b133ecfa8e1505b005fbc8e7092551f0d28bd4d26cee36fb645aa188e8a9664c0a7d5c991c78a2059445c82e1ec697ff14a8df3d2cdf
|
data/Gemfile.lock
CHANGED
@@ -52,11 +52,14 @@ module RSMP
|
|
52
52
|
raise RSMP::TimeoutError.new str
|
53
53
|
end
|
54
54
|
|
55
|
+
# Get the collected message.
|
56
|
+
def message
|
57
|
+
@messages.first
|
58
|
+
end
|
59
|
+
|
55
60
|
# Get the collected messages.
|
56
|
-
|
57
|
-
|
58
|
-
return @messages.first if @options[:num] == 1
|
59
|
-
@messages.first @options[:num]
|
61
|
+
def messages
|
62
|
+
@messages
|
60
63
|
end
|
61
64
|
|
62
65
|
# Clear all query results
|
File without changes
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module RSMP
|
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
|
4
|
+
# [
|
5
|
+
# {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"securityCode", "v"=>"1111"},
|
6
|
+
# {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"year", "v"=>"2020"},
|
7
|
+
# {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/}
|
8
|
+
# ]
|
9
|
+
#
|
10
|
+
# Note that queries can contain regex patterns for values, like /\d+/ in the example above.
|
11
|
+
#
|
12
|
+
# When an input messages is received it typically contains several items, eg:
|
13
|
+
# [
|
14
|
+
# {"cCI"=>"M0104", "n"=>"month", "v"=>"9", "age"=>"recent"},
|
15
|
+
# {"cCI"=>"M0104", "n"=>"day", "v"=>"29", "age"=>"recent"},
|
16
|
+
# {"cCI"=>"M0104", "n"=>"hour", "v"=>"17", "age"=>"recent"}
|
17
|
+
# ]
|
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:
|
22
|
+
#
|
23
|
+
# {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/}
|
24
|
+
#
|
25
|
+
# matches this input:
|
26
|
+
#
|
27
|
+
# {"cCI"=>"M0104", "n"=>"month", "v"=>"9", "age"=>"recent"}
|
28
|
+
#
|
29
|
+
# And the result is stored as:
|
30
|
+
# {
|
31
|
+
# {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/} =>
|
32
|
+
# { <StatusResponse message>, {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>"9"} }
|
33
|
+
# }
|
34
|
+
|
35
|
+
class Matcher < Collector
|
36
|
+
attr_reader :queries
|
37
|
+
|
38
|
+
# Initialize with a list a wanted statuses
|
39
|
+
def initialize proxy, want, options={}
|
40
|
+
super proxy, options.merge( ingoing: true, outgoing: false)
|
41
|
+
@queries = want.map { |wanted_item| build_query wanted_item }
|
42
|
+
end
|
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
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get a results
|
51
|
+
def query_result want
|
52
|
+
query = @queries.find { |q| q.want == want}
|
53
|
+
raise unless query
|
54
|
+
query.item
|
55
|
+
end
|
56
|
+
|
57
|
+
# get the first message. Useful when you only collected one mesage
|
58
|
+
def message
|
59
|
+
@queries.first.message
|
60
|
+
end
|
61
|
+
|
62
|
+
# Get messages from results
|
63
|
+
def messages
|
64
|
+
@queries.map { |query| query.message }.uniq
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get items from results
|
68
|
+
def items
|
69
|
+
@queries.map { |query| query.item }.uniq
|
70
|
+
end
|
71
|
+
|
72
|
+
# Are there queries left to match?
|
73
|
+
def done?
|
74
|
+
@queries.all? { |query| query.done? }
|
75
|
+
end
|
76
|
+
|
77
|
+
# Get a simplified hash of queries, with values set to either true or false,
|
78
|
+
# indicating which queries have been matched.
|
79
|
+
def status
|
80
|
+
@queries.map { |query| [query.query,query.done?] }.to_h
|
81
|
+
end
|
82
|
+
|
83
|
+
# Get a simply array of bools, showing which queries ahve been matched.
|
84
|
+
def summary
|
85
|
+
@queries.map { |query| query.done? }
|
86
|
+
end
|
87
|
+
|
88
|
+
# Check if a messages matches our criteria.
|
89
|
+
# We iterate through each of the status items or return values in the message
|
90
|
+
# Breaks as soon as where done matching all queries
|
91
|
+
def check_match message
|
92
|
+
return unless match?(message)
|
93
|
+
@queries.each do |query| # look through queries
|
94
|
+
get_items(message).each do |item| # look through status items in message
|
95
|
+
break if query.check_match(item,message) != nil #check_item_match message, query, item
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,64 @@
|
|
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
|
@@ -0,0 +1,33 @@
|
|
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
|
18
|
+
class StatusQuery < Query
|
19
|
+
# Match a status value against a query
|
20
|
+
def match? item
|
21
|
+
return nil if @want['sCI'] && @want['sCI'] != item['sCI']
|
22
|
+
return nil if @want['cO'] && @want['cO'] != item['cO']
|
23
|
+
return nil if @want['n'] && @want['n'] != item['n']
|
24
|
+
return false if @want['q'] && @want['q'] != item['q']
|
25
|
+
if @want['s'].is_a? Regexp
|
26
|
+
return false if @want['s'] && item['s'] !~ @want['s']
|
27
|
+
else
|
28
|
+
return false if @want['s'] && item['s'] != @want['s']
|
29
|
+
end
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
File without changes
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RSMP
|
2
|
+
|
3
|
+
# Class that matches a single status or command item
|
4
|
+
class Query
|
5
|
+
attr_reader :want, :item, :message
|
6
|
+
|
7
|
+
def initialize want
|
8
|
+
@want = want
|
9
|
+
@item = nil
|
10
|
+
@message = nil
|
11
|
+
@done = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def done?
|
15
|
+
@done
|
16
|
+
end
|
17
|
+
|
18
|
+
def check_match item, message
|
19
|
+
matched = match? item
|
20
|
+
if matched == true
|
21
|
+
keep message, item
|
22
|
+
true
|
23
|
+
elsif matched == false
|
24
|
+
forget
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def match? item
|
30
|
+
end
|
31
|
+
|
32
|
+
# Mark a query as matched and store item and message
|
33
|
+
def keep message, item
|
34
|
+
@message = message
|
35
|
+
@item = item
|
36
|
+
@done = true
|
37
|
+
end
|
38
|
+
|
39
|
+
# Mark a query as not matched
|
40
|
+
def forget
|
41
|
+
@done = false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/rsmp/component.rb
CHANGED
data/lib/rsmp/components.rb
CHANGED
@@ -39,7 +39,7 @@ module RSMP
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def infer_component_type component_id
|
42
|
-
|
42
|
+
Component
|
43
43
|
end
|
44
44
|
|
45
45
|
def find_component component_id, build: true
|
@@ -47,10 +47,10 @@ module RSMP
|
|
47
47
|
return component if component
|
48
48
|
if build
|
49
49
|
inferred = infer_component_type component_id
|
50
|
-
component = inferred
|
50
|
+
component = inferred.new node: self, id: component_id
|
51
51
|
@components[ component_id] = component
|
52
52
|
class_name = component.class.name.split('::').last
|
53
|
-
log "Inferred
|
53
|
+
log "Inferred #{class_name} component #{component_id}", level: :info
|
54
54
|
component
|
55
55
|
else
|
56
56
|
raise UnknownComponent.new("Component #{component_id} not found") unless component
|
data/lib/rsmp/site_proxy.rb
CHANGED
@@ -121,18 +121,6 @@ module RSMP
|
|
121
121
|
end
|
122
122
|
end
|
123
123
|
|
124
|
-
def infer_component_type component_id
|
125
|
-
if component_id =~ /TC/
|
126
|
-
{ klass: TrafficController, grouped: true }
|
127
|
-
elsif component_id =~ /DL/
|
128
|
-
{ klass: DetectorLogic, goruped: false }
|
129
|
-
elsif component_id =~ /SG/
|
130
|
-
{ klass: SignalGroup, grouped: false }
|
131
|
-
else
|
132
|
-
super
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
124
|
def process_aggregated_status message
|
137
125
|
se = message.attribute("se")
|
138
126
|
validate_aggregated_status(message,se) == false
|
@@ -347,20 +335,12 @@ module RSMP
|
|
347
335
|
@supervisor.notify_error e, options if @supervisor
|
348
336
|
end
|
349
337
|
|
350
|
-
def
|
351
|
-
|
352
|
-
message = collect(parent_task,options.merge(type: "Alarm", with_message: true, num: 1)) do |message|
|
353
|
-
# TODO check components
|
354
|
-
matching_alarm = nil
|
355
|
-
alarm = message
|
338
|
+
def collect_alarms parent_task, options={}
|
339
|
+
collect(parent_task,options.merge(type: "Alarm")) do |alarm|
|
356
340
|
next if options[:aCId] && options[:aCId] != alarm.attribute("aCId")
|
357
341
|
next if options[:aSp] && options[:aSp] != alarm.attribute("aSp")
|
358
342
|
next if options[:aS] && options[:aS] != alarm.attribute("aS")
|
359
|
-
|
360
|
-
break
|
361
|
-
end
|
362
|
-
if item
|
363
|
-
{ message: message, status: matching_alarm }
|
343
|
+
true
|
364
344
|
end
|
365
345
|
end
|
366
346
|
|
data/lib/rsmp/tlc.rb
CHANGED
@@ -293,7 +293,8 @@ module RSMP
|
|
293
293
|
def handle_s0002 status_code, status_name=nil
|
294
294
|
case status_name
|
295
295
|
when 'detectorlogicstatus'
|
296
|
-
RSMP::Tlc.make_status @detector_logics.
|
296
|
+
RSMP::Tlc.make_status @detector_logics.each { |dl| p dl.value }
|
297
|
+
RSMP::Tlc.make_status @detector_logics.map { |dl| dl.value ? '1' : '0' }.join
|
297
298
|
end
|
298
299
|
end
|
299
300
|
|
@@ -625,13 +626,15 @@ module RSMP
|
|
625
626
|
class SignalGroup < Component
|
626
627
|
attr_reader :plan, :state
|
627
628
|
|
628
|
-
|
629
|
+
# plan is a string, with each character representing a signal phase at a particular second in the cycle
|
630
|
+
def initialize node:, id:, plan: nil
|
629
631
|
super node: node, id: id, grouped: false
|
630
632
|
@plan = plan
|
631
633
|
move 0
|
632
634
|
end
|
633
635
|
|
634
636
|
def get_state pos
|
637
|
+
return 'a' unless @plan # if no plan, use phase a, which means disabled/dark
|
635
638
|
if pos > @plan.length
|
636
639
|
'.'
|
637
640
|
else
|
@@ -701,7 +704,7 @@ module RSMP
|
|
701
704
|
end
|
702
705
|
|
703
706
|
class DetectorLogic < Component
|
704
|
-
attr_reader :
|
707
|
+
attr_reader :forced, :value
|
705
708
|
|
706
709
|
def initialize node:, id:
|
707
710
|
super node: node, id: id, grouped: false
|
@@ -781,13 +784,20 @@ module RSMP
|
|
781
784
|
|
782
785
|
def handle_m0008 arg
|
783
786
|
@node.verify_security_code 2, arg['securityCode']
|
784
|
-
|
787
|
+
status = arg['status']=='True'
|
788
|
+
mode = arg['mode']=='True'
|
789
|
+
force_detector_logic status, mode
|
785
790
|
arg
|
786
791
|
end
|
787
792
|
|
788
|
-
def force_detector_logic
|
789
|
-
@forced =
|
793
|
+
def force_detector_logic forced, value
|
794
|
+
@forced = forced
|
790
795
|
@value = value
|
796
|
+
if @forced
|
797
|
+
log "Forcing to #{value}", level: :info
|
798
|
+
else
|
799
|
+
log "Releasing", level: :info
|
800
|
+
end
|
791
801
|
end
|
792
802
|
|
793
803
|
end
|
data/lib/rsmp/version.rb
CHANGED
data/lib/rsmp.rb
CHANGED
@@ -17,10 +17,13 @@ require 'rsmp/wait'
|
|
17
17
|
require 'rsmp/node'
|
18
18
|
require 'rsmp/supervisor'
|
19
19
|
require 'rsmp/components'
|
20
|
-
require 'rsmp/notifier'
|
21
|
-
require 'rsmp/listener'
|
22
|
-
require 'rsmp/collector'
|
23
|
-
require 'rsmp/
|
20
|
+
require 'rsmp/collect/notifier'
|
21
|
+
require 'rsmp/collect/listener'
|
22
|
+
require 'rsmp/collect/collector'
|
23
|
+
require 'rsmp/collect/query'
|
24
|
+
require 'rsmp/collect/matcher'
|
25
|
+
require 'rsmp/collect/message_queries'
|
26
|
+
require 'rsmp/collect/message_matchers'
|
24
27
|
require 'rsmp/component'
|
25
28
|
require 'rsmp/site'
|
26
29
|
require 'rsmp/proxy'
|
data/test.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
class A
|
2
|
+
def go &block
|
3
|
+
@block = block # block will be converted automatically to a Proc
|
4
|
+
indirect
|
5
|
+
end
|
6
|
+
|
7
|
+
def call
|
8
|
+
@block.call
|
9
|
+
end
|
10
|
+
|
11
|
+
def indirect
|
12
|
+
call
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
a = A.new
|
18
|
+
|
19
|
+
a.go do
|
20
|
+
break # this is ok. break causes the block to exit, and the encasing method to return - go() will exit
|
21
|
+
end
|
22
|
+
|
23
|
+
# this raises an error. the block we passed to go() will be called again, and it tries to break
|
24
|
+
# but we're not inside a method we can exit from
|
25
|
+
|
26
|
+
|
27
|
+
a.indirect
|
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.
|
4
|
+
version: 0.4.0
|
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-10-
|
11
|
+
date: 2021-10-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async
|
@@ -205,7 +205,13 @@ files:
|
|
205
205
|
- lib/rsmp.rb
|
206
206
|
- lib/rsmp/archive.rb
|
207
207
|
- lib/rsmp/cli.rb
|
208
|
-
- lib/rsmp/collector.rb
|
208
|
+
- lib/rsmp/collect/collector.rb
|
209
|
+
- lib/rsmp/collect/listener.rb
|
210
|
+
- lib/rsmp/collect/matcher.rb
|
211
|
+
- lib/rsmp/collect/message_matchers.rb
|
212
|
+
- lib/rsmp/collect/message_queries.rb
|
213
|
+
- lib/rsmp/collect/notifier.rb
|
214
|
+
- lib/rsmp/collect/query.rb
|
209
215
|
- lib/rsmp/component.rb
|
210
216
|
- lib/rsmp/components.rb
|
211
217
|
- lib/rsmp/convert/export/json_schema.rb
|
@@ -213,13 +219,10 @@ files:
|
|
213
219
|
- lib/rsmp/deep_merge.rb
|
214
220
|
- lib/rsmp/error.rb
|
215
221
|
- lib/rsmp/inspect.rb
|
216
|
-
- lib/rsmp/listener.rb
|
217
222
|
- lib/rsmp/logger.rb
|
218
223
|
- lib/rsmp/logging.rb
|
219
|
-
- lib/rsmp/matcher.rb
|
220
224
|
- lib/rsmp/message.rb
|
221
225
|
- lib/rsmp/node.rb
|
222
|
-
- lib/rsmp/notifier.rb
|
223
226
|
- lib/rsmp/proxy.rb
|
224
227
|
- lib/rsmp/rsmp.rb
|
225
228
|
- lib/rsmp/site.rb
|
@@ -231,6 +234,7 @@ files:
|
|
231
234
|
- lib/rsmp/version.rb
|
232
235
|
- lib/rsmp/wait.rb
|
233
236
|
- rsmp.gemspec
|
237
|
+
- test.rb
|
234
238
|
homepage: https://github.com/rsmp-nordic/rsmp
|
235
239
|
licenses:
|
236
240
|
- MIT
|
data/lib/rsmp/matcher.rb
DELETED
@@ -1,195 +0,0 @@
|
|
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
|