rsmp 0.1.13 → 0.1.29
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.gitmodules +0 -4
- data/Gemfile.lock +52 -52
- data/README.md +7 -1
- data/config/supervisor.yaml +9 -15
- data/config/tlc.yaml +26 -28
- data/documentation/classes_and_modules.md +65 -0
- data/documentation/message_distribution.md +23 -0
- data/lib/rsmp.rb +14 -5
- data/lib/rsmp/archive.rb +23 -22
- data/lib/rsmp/cli.rb +133 -102
- data/lib/rsmp/collector.rb +102 -0
- data/lib/rsmp/component.rb +13 -4
- data/lib/rsmp/{site_base.rb → components.rb} +8 -6
- data/lib/rsmp/convert/export/json_schema.rb +204 -0
- data/lib/rsmp/convert/import/yaml.rb +38 -0
- data/lib/rsmp/deep_merge.rb +11 -0
- data/lib/rsmp/error.rb +1 -1
- data/lib/rsmp/inspect.rb +46 -0
- data/lib/rsmp/listener.rb +23 -0
- data/lib/rsmp/logger.rb +6 -4
- data/lib/rsmp/{base.rb → logging.rb} +3 -3
- data/lib/rsmp/message.rb +46 -32
- data/lib/rsmp/node.rb +47 -12
- data/lib/rsmp/notifier.rb +29 -0
- data/lib/rsmp/proxy.rb +143 -71
- data/lib/rsmp/rsmp.rb +34 -23
- data/lib/rsmp/site.rb +35 -42
- data/lib/rsmp/site_proxy.rb +182 -78
- data/lib/rsmp/site_proxy_wait.rb +206 -0
- data/lib/rsmp/supervisor.rb +85 -49
- data/lib/rsmp/supervisor_proxy.rb +80 -34
- data/lib/rsmp/tlc.rb +761 -86
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp/wait.rb +7 -8
- data/rsmp.gemspec +9 -21
- metadata +37 -142
- data/config/site.yaml +0 -40
- data/lib/rsmp/probe.rb +0 -114
- data/lib/rsmp/probe_collection.rb +0 -28
- data/lib/rsmp/supervisor_base.rb +0 -10
@@ -0,0 +1,206 @@
|
|
1
|
+
# waiting for various types of messages and reponses from remote sites
|
2
|
+
module RSMP
|
3
|
+
module SiteProxyWait
|
4
|
+
|
5
|
+
def wait_for_status_updates parent_task, options={}, &send_block
|
6
|
+
send_while_collecting parent_task, send_block do |task, m_id|
|
7
|
+
collect_status_updates_or_responses task, 'StatusUpdate', options, m_id
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def wait_for_status_responses parent_task, options={}, &send_block
|
12
|
+
send_while_collecting parent_task, send_block do |task, m_id|
|
13
|
+
collect_status_updates_or_responses task, 'StatusResponse', options, m_id
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def wait_for_command_responses parent_task, options={}, &send_block
|
18
|
+
send_while_collecting parent_task, send_block do |task, m_id|
|
19
|
+
collect_command_responses task, options, m_id
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def wait_for_alarm parent_task, options={}
|
24
|
+
matching_alarm = nil
|
25
|
+
message = collect(parent_task,options.merge(type: "Alarm", with_message: true, num: 1)) do |message|
|
26
|
+
# TODO check components
|
27
|
+
matching_alarm = nil
|
28
|
+
alarm = message
|
29
|
+
next if options[:aCId] && options[:aCId] != alarm.attribute("aCId")
|
30
|
+
next if options[:aSp] && options[:aSp] != alarm.attribute("aSp")
|
31
|
+
next if options[:aS] && options[:aS] != alarm.attribute("aS")
|
32
|
+
matching_alarm = alarm
|
33
|
+
break
|
34
|
+
end
|
35
|
+
if item
|
36
|
+
{ message: message, status: matching_alarm }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def collect_status_updates task, options, m_id
|
41
|
+
collect_status_updates_or_responses task, 'StatusUpdate', options, m_id
|
42
|
+
end
|
43
|
+
|
44
|
+
def collect_status_responses task, options, m_id
|
45
|
+
collect_status_updates_or_responses task, 'StatusResponse', options, m_id
|
46
|
+
end
|
47
|
+
|
48
|
+
def collect_command_responses parent_task, options, m_id
|
49
|
+
task.annotate "wait for command response"
|
50
|
+
want = options[:command_list].clone
|
51
|
+
result = {}
|
52
|
+
messages = []
|
53
|
+
collect(parent_task,options.merge({
|
54
|
+
type: ['CommandResponse','MessageNotAck'],
|
55
|
+
num: 1
|
56
|
+
})) do |message|
|
57
|
+
if message.is_a?(MessageNotAck)
|
58
|
+
if message.attribute('oMId') == m_id
|
59
|
+
# set result to an exception, but don't raise it.
|
60
|
+
# this will be returned by the task and stored as the task result
|
61
|
+
# when the parent task call wait() on the task, the exception
|
62
|
+
# will be raised in the parent task, and caught by rspec.
|
63
|
+
# rspec will then show the error and record the test as failed
|
64
|
+
m_id_short = RSMP::Message.shorten_m_id m_id, 8
|
65
|
+
result = RSMP::MessageRejected.new "Command request #{m_id_short} was rejected: #{message.attribute('rea')}"
|
66
|
+
next true # done, no more messages wanted
|
67
|
+
else
|
68
|
+
false
|
69
|
+
end
|
70
|
+
else
|
71
|
+
add = false
|
72
|
+
# look through querues
|
73
|
+
want.each_with_index do |query,i|
|
74
|
+
# look through items in message
|
75
|
+
message.attributes['rvs'].each do |input|
|
76
|
+
matching = command_match? query, input
|
77
|
+
if matching == true
|
78
|
+
result[query] = input
|
79
|
+
add = true
|
80
|
+
elsif matching == false
|
81
|
+
result.delete query
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
messages << message if add
|
86
|
+
result.size == want.size # any queries left to match?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
return result, messages
|
90
|
+
rescue Async::TimeoutError
|
91
|
+
raise RSMP::TimeoutError.new "Did not receive correct command response to #{m_id} within #{options[:timeout]}s"
|
92
|
+
end
|
93
|
+
|
94
|
+
def collect_status_updates_or_responses task, type, options, m_id
|
95
|
+
want = options[:status_list].clone
|
96
|
+
result = {}
|
97
|
+
messages = []
|
98
|
+
# wait for a status update
|
99
|
+
collect(task,options.merge({
|
100
|
+
type: [type,'MessageNotAck'],
|
101
|
+
num: 1
|
102
|
+
})) do |message|
|
103
|
+
if message.is_a?(MessageNotAck)
|
104
|
+
if message.attribute('oMId') == m_id
|
105
|
+
# set result to an exception, but don't raise it.
|
106
|
+
# this will be returned by the task and stored as the task result
|
107
|
+
# when the parent task call wait() on the task, the exception
|
108
|
+
# will be raised in the parent task, and caught by rspec.
|
109
|
+
# rspec will then show the error and record the test as failed
|
110
|
+
m_id_short = RSMP::Message.shorten_m_id m_id, 8
|
111
|
+
result = RSMP::MessageRejected.new "Status request #{m_id_short} was rejected: #{message.attribute('rea')}"
|
112
|
+
next true # done, no more messages wanted
|
113
|
+
end
|
114
|
+
false
|
115
|
+
else
|
116
|
+
found = []
|
117
|
+
add = false
|
118
|
+
# look through querues
|
119
|
+
want.each_with_index do |query,i|
|
120
|
+
# look through status items in message
|
121
|
+
message.attributes['sS'].each do |input|
|
122
|
+
matching = status_match? query, input
|
123
|
+
if matching == true
|
124
|
+
result[query] = input
|
125
|
+
add = true
|
126
|
+
elsif matching == false
|
127
|
+
result.delete query
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
messages << message if add
|
132
|
+
result.size == want.size # any queries left to match?
|
133
|
+
end
|
134
|
+
end
|
135
|
+
return result, messages
|
136
|
+
rescue Async::TimeoutError
|
137
|
+
type_str = {'StatusUpdate'=>'update', 'StatusResponse'=>'response'}[type]
|
138
|
+
raise RSMP::TimeoutError.new "Did not received correct status #{type_str} in reply to #{m_id} within #{options[:timeout]}s"
|
139
|
+
end
|
140
|
+
|
141
|
+
def status_match? query, item
|
142
|
+
return nil if query['sCI'] && query['sCI'] != item['sCI']
|
143
|
+
return nil if query['n'] && query['n'] != item['n']
|
144
|
+
return false if query['q'] && query['q'] != item['q']
|
145
|
+
if query['s'].is_a? Regexp
|
146
|
+
return false if query['s'] && item['s'] !~ query['s']
|
147
|
+
else
|
148
|
+
return false if query['s'] && item['s'] != query['s']
|
149
|
+
end
|
150
|
+
true
|
151
|
+
end
|
152
|
+
|
153
|
+
def command_match? query, item
|
154
|
+
return nil if query['cCI'] && query['cCI'] != item['cCI']
|
155
|
+
return nil if query['n'] && query['n'] != item['n']
|
156
|
+
if query['v'].is_a? Regexp
|
157
|
+
return false if query['v'] && item['v'] !~ query['v']
|
158
|
+
else
|
159
|
+
return false if query['v'] && item['v'] != query['v']
|
160
|
+
end
|
161
|
+
true
|
162
|
+
end
|
163
|
+
|
164
|
+
def send_while_collecting parent_task, send_block, &collect_block
|
165
|
+
m_id = RSMP::Message.make_m_id # make message id so we can start waiting for it
|
166
|
+
|
167
|
+
# wait for command responses in an async task
|
168
|
+
task = parent_task.async do |task|
|
169
|
+
collect_block.call task, m_id
|
170
|
+
rescue StandardError => e
|
171
|
+
notify_error e, level: :internal
|
172
|
+
end
|
173
|
+
|
174
|
+
# call block, it should send command request using the given m_id
|
175
|
+
send_block.call m_id
|
176
|
+
|
177
|
+
# wait for the response and return it, raise exception if NotAck received, it it timed out
|
178
|
+
task.wait
|
179
|
+
end
|
180
|
+
|
181
|
+
def wait_for_aggregated_status parent_task, options={}
|
182
|
+
collect(parent_task,options.merge({
|
183
|
+
type: ['AggregatedStatus','MessageNotAck'],
|
184
|
+
num: 1
|
185
|
+
})) do |message|
|
186
|
+
if message.is_a?(MessageNotAck)
|
187
|
+
if message.attribute('oMId') == m_id
|
188
|
+
# set result to an exception, but don't raise it.
|
189
|
+
# this will be returned by the task and stored as the task result
|
190
|
+
# when the parent task call wait() on the task, the exception
|
191
|
+
# will be raised in the parent task, and caught by rspec.
|
192
|
+
# rspec will then show the error and record the test as failed
|
193
|
+
m_id_short = RSMP::Message.shorten_m_id m_id, 8
|
194
|
+
result = RSMP::MessageRejected.new "Aggregated status request #{m_id_short} was rejected: #{message.attribute('rea')}"
|
195
|
+
next true # done, no more messages wanted
|
196
|
+
else
|
197
|
+
false
|
198
|
+
end
|
199
|
+
else
|
200
|
+
true
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
end
|
data/lib/rsmp/supervisor.rb
CHANGED
@@ -17,47 +17,57 @@ module RSMP
|
|
17
17
|
@supervisor_settings['site_id']
|
18
18
|
end
|
19
19
|
|
20
|
-
def handle_supervisor_settings options
|
21
|
-
|
20
|
+
def handle_supervisor_settings options={}
|
21
|
+
defaults = {
|
22
22
|
'port' => 12111,
|
23
|
-
'
|
24
|
-
'
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
:any => {}
|
23
|
+
'ips' => 'all',
|
24
|
+
'guest' => {
|
25
|
+
'rsmp_versions' => 'all',
|
26
|
+
'sxl' => 'tlc',
|
27
|
+
'intervals' => {
|
28
|
+
'timer' => 1,
|
29
|
+
'watchdog' => 1
|
30
|
+
},
|
31
|
+
'timeouts' => {
|
32
|
+
'watchdog' => 2,
|
33
|
+
'acknowledgement' => 2
|
34
|
+
}
|
36
35
|
}
|
37
36
|
}
|
38
|
-
|
39
|
-
if options[:supervisor_settings]
|
40
|
-
converted = options[:supervisor_settings].map { |k,v| [k.to_s,v] }.to_h #convert symbol keys to string keys
|
41
|
-
converted.compact!
|
42
|
-
@supervisor_settings.merge! converted
|
43
|
-
end
|
44
|
-
|
45
|
-
required = [:port, :rsmp_versions, :watchdog_interval, :watchdog_timeout,
|
46
|
-
:acknowledgement_timeout, :command_response_timeout]
|
47
|
-
check_required_settings @supervisor_settings, required
|
48
37
|
|
38
|
+
# merge options into defaults
|
39
|
+
@supervisor_settings = defaults.deep_merge(options[:supervisor_settings] || {})
|
49
40
|
@rsmp_versions = @supervisor_settings["rsmp_versions"]
|
41
|
+
check_site_sxl_types
|
42
|
+
end
|
43
|
+
|
44
|
+
def check_site_sxl_types
|
45
|
+
sites = @supervisor_settings['sites'].clone || {}
|
46
|
+
sites['guest'] = @supervisor_settings['guest']
|
47
|
+
sites.each do |site_id,settings|
|
48
|
+
unless settings
|
49
|
+
raise RSMP::ConfigurationError.new("Configuration for site '#{site_id}' is empty")
|
50
|
+
end
|
51
|
+
sxl = settings['sxl']
|
52
|
+
sxl = 'tlc' unless sxl # temporary fix until configs are updated
|
53
|
+
unless sxl
|
54
|
+
raise RSMP::ConfigurationError.new("Configuration error for site '#{site_id}': No SXL specified")
|
55
|
+
end
|
56
|
+
RSMP::Schemer.find_schemas! sxl if sxl
|
57
|
+
rescue RSMP::Schemer::UnknownSchemaError => e
|
58
|
+
raise RSMP::ConfigurationError.new("Configuration error for site '#{site_id}': #{e}")
|
59
|
+
end
|
50
60
|
end
|
51
61
|
|
52
62
|
def start_action
|
53
63
|
@endpoint = Async::IO::Endpoint.tcp('0.0.0.0', @supervisor_settings["port"])
|
54
|
-
@endpoint.accept do |socket|
|
64
|
+
@endpoint.accept do |socket| # creates async tasks
|
55
65
|
handle_connection(socket)
|
66
|
+
rescue StandardError => e
|
67
|
+
notify_error e, level: :internal
|
56
68
|
end
|
57
|
-
rescue SystemCallError => e # all ERRNO errors
|
58
|
-
log "Exception: #{e.to_s}", level: :error
|
59
69
|
rescue StandardError => e
|
60
|
-
|
70
|
+
notify_error e, level: :internal
|
61
71
|
end
|
62
72
|
|
63
73
|
def stop
|
@@ -74,16 +84,18 @@ module RSMP
|
|
74
84
|
remote_hostname = socket.remote_address.ip_address
|
75
85
|
remote_ip = socket.remote_address.ip_address
|
76
86
|
|
77
|
-
info = {ip:remote_ip, port:remote_port, hostname:remote_hostname, now:
|
87
|
+
info = {ip:remote_ip, port:remote_port, hostname:remote_hostname, now:Clock.now}
|
78
88
|
if accept? socket, info
|
79
89
|
connect socket, info
|
80
90
|
else
|
81
91
|
reject socket, info
|
82
92
|
end
|
83
|
-
rescue
|
84
|
-
log "
|
93
|
+
rescue ConnectionError => e
|
94
|
+
log "Rejected connection from #{remote_ip}:#{remote_port}, #{e.to_s}", level: :warning
|
95
|
+
notify_error e
|
85
96
|
rescue StandardError => e
|
86
|
-
log "
|
97
|
+
log "Connection: #{e.to_s}", exception: e, level: :error
|
98
|
+
notify_error e, level: :internal
|
87
99
|
ensure
|
88
100
|
close socket, info
|
89
101
|
end
|
@@ -91,7 +103,7 @@ module RSMP
|
|
91
103
|
def starting
|
92
104
|
log "Starting supervisor on port #{@supervisor_settings["port"]}",
|
93
105
|
level: :info,
|
94
|
-
timestamp:
|
106
|
+
timestamp: @clock.now
|
95
107
|
end
|
96
108
|
|
97
109
|
def accept? socket, info
|
@@ -110,29 +122,49 @@ module RSMP
|
|
110
122
|
end
|
111
123
|
end
|
112
124
|
|
125
|
+
def authorize_ip ip
|
126
|
+
return if @supervisor_settings['ips'] == 'all'
|
127
|
+
return if @supervisor_settings['ips'].include? ip
|
128
|
+
raise ConnectionError.new('guest ip not allowed')
|
129
|
+
end
|
130
|
+
|
131
|
+
def check_max_sites
|
132
|
+
max = @supervisor_settings['max_sites']
|
133
|
+
if max
|
134
|
+
if @proxies.size >= max
|
135
|
+
raise ConnectionError.new("maximum of #{max} sites already connected")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
113
140
|
def connect socket, info
|
114
141
|
log "Site connected from #{format_ip_and_port(info)}",
|
115
142
|
ip: info[:ip],
|
116
143
|
port: info[:port],
|
117
144
|
level: :info,
|
118
|
-
timestamp:
|
145
|
+
timestamp: Clock.now
|
146
|
+
|
147
|
+
authorize_ip info[:ip]
|
148
|
+
check_max_sites
|
119
149
|
|
120
150
|
proxy = build_proxy({
|
121
151
|
supervisor: self,
|
152
|
+
ip: info[:ip],
|
153
|
+
port: info[:port],
|
122
154
|
task: @task,
|
123
|
-
settings:
|
155
|
+
settings: {'collect'=>@supervisor_settings['collect']},
|
124
156
|
socket: socket,
|
125
157
|
info: info,
|
126
158
|
logger: @logger,
|
127
159
|
archive: @archive
|
128
160
|
})
|
129
161
|
@proxies.push proxy
|
130
|
-
|
131
162
|
proxy.run # will run until the site disconnects
|
163
|
+
ensure
|
132
164
|
@proxies.delete proxy
|
133
165
|
site_ids_changed
|
134
166
|
|
135
|
-
stop if @supervisor_settings['
|
167
|
+
stop if @supervisor_settings['one_shot']
|
136
168
|
end
|
137
169
|
|
138
170
|
def site_ids_changed
|
@@ -145,9 +177,9 @@ module RSMP
|
|
145
177
|
|
146
178
|
def close socket, info
|
147
179
|
if info
|
148
|
-
log "Connection to #{format_ip_and_port(info)} closed", ip: info[:ip], level: :info, timestamp:
|
180
|
+
log "Connection to #{format_ip_and_port(info)} closed", ip: info[:ip], level: :info, timestamp: Clock.now
|
149
181
|
else
|
150
|
-
log "Connection closed", level: :info, timestamp:
|
182
|
+
log "Connection closed", level: :info, timestamp: Clock.now
|
151
183
|
end
|
152
184
|
|
153
185
|
socket.close
|
@@ -167,34 +199,38 @@ module RSMP
|
|
167
199
|
def wait_for_site site_id, timeout
|
168
200
|
site = find_site site_id
|
169
201
|
return site if site
|
170
|
-
|
202
|
+
wait_for(@site_id_condition,timeout) { find_site site_id }
|
171
203
|
rescue Async::TimeoutError
|
172
|
-
|
204
|
+
raise RSMP::TimeoutError.new "Site '#{site_id}' did not connect within #{timeout}s"
|
173
205
|
end
|
174
206
|
|
175
207
|
def wait_for_site_disconnect site_id, timeout
|
176
|
-
|
208
|
+
wait_for(@site_id_condition,timeout) { true unless find_site site_id }
|
177
209
|
rescue Async::TimeoutError
|
178
|
-
|
210
|
+
raise RSMP::TimeoutError.new "Site '#{site_id}' did not disconnect within #{timeout}s"
|
179
211
|
end
|
180
212
|
|
181
213
|
def check_site_id site_id
|
182
214
|
check_site_already_connected site_id
|
183
|
-
return
|
215
|
+
return site_id_to_site_setting site_id
|
184
216
|
end
|
185
217
|
|
186
218
|
def check_site_already_connected site_id
|
187
|
-
raise FatalError.new "Site #{site_id} already connected" if find_site(site_id)
|
219
|
+
raise FatalError.new "Site '#{site_id}' already connected" if find_site(site_id)
|
188
220
|
end
|
189
221
|
|
190
|
-
def
|
222
|
+
def site_id_to_site_setting site_id
|
191
223
|
return {} unless @supervisor_settings['sites']
|
192
224
|
@supervisor_settings['sites'].each_pair do |id,settings|
|
193
|
-
if id ==
|
225
|
+
if id == 'guest' || id == site_id
|
194
226
|
return settings
|
195
227
|
end
|
196
228
|
end
|
197
|
-
raise FatalError.new "site id #{site_id}
|
229
|
+
raise FatalError.new "site id #{site_id} unknown"
|
230
|
+
end
|
231
|
+
|
232
|
+
def ip_to_site_settings ip
|
233
|
+
@supervisor_settings['sites'][ip] || @supervisor_settings['sites']['guest']
|
198
234
|
end
|
199
235
|
|
200
236
|
def aggregated_status_changed site_proxy, component
|
@@ -14,7 +14,6 @@ module RSMP
|
|
14
14
|
@ip = options[:ip]
|
15
15
|
@port = options[:port]
|
16
16
|
@status_subscriptions = {}
|
17
|
-
@status_subscriptions_mutex = Mutex.new
|
18
17
|
@sxl = @site_settings['sxl']
|
19
18
|
@synthetic_id = Supervisor.build_id_from_ip_port @ip, @port
|
20
19
|
end
|
@@ -28,27 +27,34 @@ module RSMP
|
|
28
27
|
super
|
29
28
|
connect
|
30
29
|
@logger.unmute @ip, @port
|
30
|
+
log "Connected to superviser at #{@ip}:#{@port}", level: :info
|
31
31
|
start_reader
|
32
32
|
send_version @site_settings['site_id'], @site_settings["rsmp_versions"]
|
33
33
|
rescue Errno::ECONNREFUSED
|
34
34
|
log "No connection to supervisor at #{@ip}:#{@port}", level: :error
|
35
|
-
unless @site.site_settings[
|
36
|
-
log "Will try to reconnect again every #{@site.site_settings[
|
35
|
+
unless @site.site_settings['intervals']['reconnect'] == :no
|
36
|
+
log "Will try to reconnect again every #{@site.site_settings['intervals']['reconnect']} seconds..", level: :info
|
37
37
|
@logger.mute @ip, @port
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
+
def stop
|
42
|
+
log "Closing connection to supervisor", level: :info
|
43
|
+
super
|
44
|
+
@last_status_sent = nil
|
45
|
+
end
|
46
|
+
|
41
47
|
def connect
|
42
48
|
return if @socket
|
43
49
|
@endpoint = Async::IO::Endpoint.tcp(@ip, @port)
|
44
50
|
@socket = @endpoint.connect
|
45
51
|
@stream = Async::IO::Stream.new(@socket)
|
46
|
-
@protocol = Async::IO::Protocol::Line.new(@stream,
|
52
|
+
@protocol = Async::IO::Protocol::Line.new(@stream,WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
|
47
53
|
end
|
48
54
|
|
49
55
|
def connection_complete
|
50
56
|
super
|
51
|
-
log "Connection to supervisor established", level: :info
|
57
|
+
log "Connection to supervisor established, using core #{@rsmp_version}, #{sxl} #{sxl_version}", level: :info
|
52
58
|
start_watchdog
|
53
59
|
end
|
54
60
|
|
@@ -59,6 +65,8 @@ module RSMP
|
|
59
65
|
when StatusUpdate
|
60
66
|
when AggregatedStatus
|
61
67
|
will_not_handle message
|
68
|
+
when AggregatedStatusRequest
|
69
|
+
process_aggregated_status_request message
|
62
70
|
when CommandRequest
|
63
71
|
process_command_request message
|
64
72
|
when CommandResponse
|
@@ -72,14 +80,15 @@ module RSMP
|
|
72
80
|
else
|
73
81
|
super message
|
74
82
|
end
|
75
|
-
rescue UnknownComponent
|
76
|
-
|
77
|
-
rescue UnknownCommand => e
|
78
|
-
dont_acknowledge message, '', e.to_s
|
79
|
-
rescue UnknownStatus => e
|
83
|
+
rescue UnknownComponent, UnknownCommand, UnknownStatus,
|
84
|
+
MessageRejected, MissingAttribute => e
|
80
85
|
dont_acknowledge message, '', e.to_s
|
81
86
|
end
|
82
87
|
|
88
|
+
def process_deferred
|
89
|
+
site.process_deferred
|
90
|
+
end
|
91
|
+
|
83
92
|
def acknowledged_first_ingoing message
|
84
93
|
# TODO
|
85
94
|
# aggregateds status should only be send for later version of rsmp
|
@@ -101,7 +110,7 @@ module RSMP
|
|
101
110
|
end
|
102
111
|
|
103
112
|
def reconnect_delay
|
104
|
-
interval = @site_settings[
|
113
|
+
interval = @site_settings['intervals']['watchdog']
|
105
114
|
log "Waiting #{interval} seconds before trying to reconnect", level: :info
|
106
115
|
@task.sleep interval
|
107
116
|
end
|
@@ -116,9 +125,9 @@ module RSMP
|
|
116
125
|
|
117
126
|
def send_aggregated_status component
|
118
127
|
message = AggregatedStatus.new({
|
119
|
-
"aSTS" =>
|
128
|
+
"aSTS" => clock.to_s,
|
120
129
|
"cId" => component.c_id,
|
121
|
-
"fP" =>
|
130
|
+
"fP" => 'NormalControl',
|
122
131
|
"fS" => nil,
|
123
132
|
"se" => component.aggregated_status_bools
|
124
133
|
})
|
@@ -154,11 +163,21 @@ module RSMP
|
|
154
163
|
sorted
|
155
164
|
end
|
156
165
|
|
166
|
+
def process_aggregated_status_request message
|
167
|
+
log "Received #{message.type}", message: message, level: :log
|
168
|
+
component_id = message.attributes["cId"]
|
169
|
+
component = @site.find_component component_id
|
170
|
+
acknowledge message
|
171
|
+
send_aggregated_status component
|
172
|
+
end
|
173
|
+
|
157
174
|
def process_command_request message
|
158
|
-
log "Received #{message.type}", message: message, level: :log
|
175
|
+
log "Received #{message.type}", message: message, level: :log
|
176
|
+
component_id = message.attributes["cId"]
|
177
|
+
component = @site.find_component component_id
|
159
178
|
commands = simplify_command_requests message.attributes["arg"]
|
160
179
|
commands.each_pair do |command_code,arg|
|
161
|
-
|
180
|
+
component.handle_command command_code,arg
|
162
181
|
end
|
163
182
|
|
164
183
|
rvs = message.attributes["arg"].map do |item|
|
@@ -167,26 +186,27 @@ module RSMP
|
|
167
186
|
item
|
168
187
|
end
|
169
188
|
response = CommandResponse.new({
|
170
|
-
"cId"=>
|
171
|
-
"cTS"=>
|
189
|
+
"cId"=>component_id,
|
190
|
+
"cTS"=>clock.to_s,
|
172
191
|
"rvs"=>rvs
|
173
192
|
})
|
174
193
|
acknowledge message
|
175
194
|
send_message response
|
176
195
|
end
|
177
196
|
|
178
|
-
def process_status_request message
|
197
|
+
def process_status_request message, options={}
|
179
198
|
component_id = message.attributes["cId"]
|
180
199
|
component = @site.find_component component_id
|
181
200
|
log "Received #{message.type}", message: message, level: :log
|
182
201
|
sS = message.attributes["sS"].map do |arg|
|
183
|
-
value, quality =
|
184
|
-
{ "s" => value, "q" => quality }.merge arg
|
202
|
+
value, quality = component.get_status arg['sCI'], arg['n']
|
203
|
+
{ "s" => value.to_s, "q" => quality.to_s }.merge arg
|
185
204
|
end
|
186
205
|
response = StatusResponse.new({
|
187
206
|
"cId"=>component_id,
|
188
|
-
"sTs"=>
|
189
|
-
"sS"=>sS
|
207
|
+
"sTs"=>clock.to_s,
|
208
|
+
"sS"=>sS,
|
209
|
+
"mId" => options[:m_id]
|
190
210
|
})
|
191
211
|
acknowledge message
|
192
212
|
send_message response
|
@@ -211,10 +231,11 @@ module RSMP
|
|
211
231
|
update_list[component] ||= {}
|
212
232
|
|
213
233
|
subs = @status_subscriptions[component]
|
234
|
+
now = Time.now # internal timestamp
|
214
235
|
|
215
236
|
message.attributes["sS"].each do |arg|
|
216
237
|
sCI = arg["sCI"]
|
217
|
-
subcription = {interval: arg["uRt"].to_i, last_sent_at:
|
238
|
+
subcription = {interval: arg["uRt"].to_i, last_sent_at: now}
|
218
239
|
subs[sCI] ||= {}
|
219
240
|
subs[sCI][arg["n"]] = subcription
|
220
241
|
|
@@ -248,18 +269,40 @@ module RSMP
|
|
248
269
|
status_update_timer now if ready?
|
249
270
|
end
|
250
271
|
|
272
|
+
def fetch_last_sent_status component, code, name
|
273
|
+
if @last_status_sent && @last_status_sent[component] && @last_status_sent[component][code]
|
274
|
+
@last_status_sent[component][code][name]
|
275
|
+
else
|
276
|
+
nil
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def store_last_sent_status component, code, name, value
|
281
|
+
@last_status_sent ||= {}
|
282
|
+
@last_status_sent[component] ||= {}
|
283
|
+
@last_status_sent[component][code] ||= {}
|
284
|
+
@last_status_sent[component][code][name] = value
|
285
|
+
end
|
286
|
+
|
251
287
|
def status_update_timer now
|
252
288
|
update_list = {}
|
253
289
|
# go through subscriptons and build a similarly organized list,
|
254
290
|
# that only contains what should be send
|
255
291
|
|
256
292
|
@status_subscriptions.each_pair do |component,by_code|
|
293
|
+
component_object = @site.find_component component
|
257
294
|
by_code.each_pair do |code,by_name|
|
258
295
|
by_name.each_pair do |name,subscription|
|
296
|
+
current = nil
|
259
297
|
if subscription[:interval] == 0
|
260
298
|
# send as soon as the data changes
|
261
|
-
if
|
299
|
+
if component_object
|
300
|
+
current, age = *(component_object.get_status code, name)
|
301
|
+
end
|
302
|
+
last_sent = fetch_last_sent_status component, code, name
|
303
|
+
if current != last_sent
|
262
304
|
should_send = true
|
305
|
+
store_last_sent_status component, code, name, current
|
263
306
|
end
|
264
307
|
else
|
265
308
|
# send at regular intervals
|
@@ -270,24 +313,27 @@ module RSMP
|
|
270
313
|
if should_send
|
271
314
|
subscription[:last_sent_at] = now
|
272
315
|
update_list[component] ||= {}
|
273
|
-
update_list[component][code] ||=
|
274
|
-
update_list[component][code]
|
316
|
+
update_list[component][code] ||= {}
|
317
|
+
update_list[component][code][name] = current
|
275
318
|
end
|
276
319
|
end
|
277
320
|
end
|
278
321
|
end
|
279
322
|
send_status_updates update_list
|
280
|
-
rescue StandardError => e
|
281
|
-
log ["Status update exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
|
282
323
|
end
|
283
324
|
|
284
325
|
def send_status_updates update_list
|
285
|
-
now =
|
286
|
-
update_list.each_pair do |
|
326
|
+
now = clock.to_s
|
327
|
+
update_list.each_pair do |component_id,by_code|
|
328
|
+
component = @site.find_component component_id
|
287
329
|
sS = []
|
288
330
|
by_code.each_pair do |code,names|
|
289
|
-
names.map do |status_name|
|
290
|
-
value
|
331
|
+
names.map do |status_name,value|
|
332
|
+
if value
|
333
|
+
quality = 'recent'
|
334
|
+
else
|
335
|
+
value,quality = component.get_status code, status_name
|
336
|
+
end
|
291
337
|
sS << { "sCI" => code,
|
292
338
|
"n" => status_name,
|
293
339
|
"s" => value.to_s,
|
@@ -295,7 +341,7 @@ module RSMP
|
|
295
341
|
end
|
296
342
|
end
|
297
343
|
update = StatusUpdate.new({
|
298
|
-
"cId"=>
|
344
|
+
"cId"=>component_id,
|
299
345
|
"sTs"=>now,
|
300
346
|
"sS"=>sS
|
301
347
|
})
|
@@ -305,7 +351,7 @@ module RSMP
|
|
305
351
|
|
306
352
|
def send_alarm
|
307
353
|
message = Alarm.new({
|
308
|
-
"aSTS"=>
|
354
|
+
"aSTS"=>clock.to_s,
|
309
355
|
"fP"=>nil,
|
310
356
|
"fS"=>nil,
|
311
357
|
"se"=>@site.aggregated_status_bools
|