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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/rsmp/collector.rb +86 -41
- data/lib/rsmp/components.rb +10 -0
- data/lib/rsmp/matcher.rb +195 -0
- data/lib/rsmp/proxy.rb +6 -2
- data/lib/rsmp/site.rb +8 -2
- data/lib/rsmp/site_proxy.rb +49 -58
- data/lib/rsmp/site_proxy_wait.rb +0 -171
- data/lib/rsmp/supervisor.rb +7 -2
- data/lib/rsmp/supervisor_proxy.rb +1 -1
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e3cdaa079ff62d328eec49b03368afeabc463dd8e805a3ca6cd8d04237deefa
|
4
|
+
data.tar.gz: e4c49d13767b56e0d0efe552406aac32a714b0ff162e680e65716118b88eb280
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa218c2c4404e1a8fdb06c89a60707004adb83ad63b6f9b41a6f8128b8a251112000a6b4f5e6b0cef4109332171172b9c7171e2b7684741d846eafe4f1b573a9
|
7
|
+
data.tar.gz: c221e735fce70db77cd0772c336904d7ac707d2525270bed8c4559c103fec632c5f66717b2cec98f81facfeb8824f0acda1dd924a68d5d99e126c114e9e26536
|
data/Gemfile.lock
CHANGED
data/lib/rsmp/collector.rb
CHANGED
@@ -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
|
-
@
|
17
|
-
@options
|
18
|
-
@
|
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
|
-
@
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
@
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
85
|
-
|
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
|
133
|
+
return if message == nil
|
89
134
|
if @options[:type].is_a? Array
|
90
|
-
return
|
135
|
+
return unless @options[:type].include? message.type
|
91
136
|
else
|
92
|
-
return
|
137
|
+
return unless message.type == @options[:type]
|
93
138
|
end
|
94
139
|
end
|
95
140
|
if @options[:component]
|
96
|
-
return
|
141
|
+
return if message.attributes['cId'] && message.attributes['cId'] != @options[:component]
|
97
142
|
end
|
98
143
|
if @block
|
99
|
-
return
|
144
|
+
return if @block.call(message) == false
|
100
145
|
end
|
101
146
|
true
|
102
147
|
end
|
data/lib/rsmp/components.rb
CHANGED
@@ -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
|
data/lib/rsmp/matcher.rb
ADDED
@@ -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
|
-
|
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
|
-
'
|
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']
|
data/lib/rsmp/site_proxy.rb
CHANGED
@@ -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 =
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
192
|
-
|
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
|
-
|
232
|
-
|
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
|
-
|
293
|
-
|
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
|
data/lib/rsmp/site_proxy_wait.rb
CHANGED
@@ -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
|
data/lib/rsmp/supervisor.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/rsmp/version.rb
CHANGED
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.
|
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-
|
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.
|
258
|
+
rubygems_version: 3.2.15
|
258
259
|
signing_key:
|
259
260
|
specification_version: 4
|
260
261
|
summary: RoadSide Message Protocol (RSMP) library.
|