rsmp 0.1.17 → 0.1.30
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/.gitignore +1 -0
- data/.gitmodules +0 -4
- data/Gemfile.lock +23 -19
- data/README.md +1 -1
- data/config/supervisor.yaml +9 -15
- data/config/tlc.yaml +12 -10
- 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 +3 -0
- data/lib/rsmp/{site_base.rb → components.rb} +3 -3
- 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 -34
- data/lib/rsmp/site_proxy.rb +180 -98
- data/lib/rsmp/site_proxy_wait.rb +206 -0
- data/lib/rsmp/supervisor.rb +85 -49
- data/lib/rsmp/supervisor_proxy.rb +70 -27
- data/lib/rsmp/tlc.rb +252 -90
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp/wait.rb +7 -8
- data/rsmp.gemspec +4 -15
- metadata +27 -118
- 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,6 +163,14 @@ 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
175
|
log "Received #{message.type}", message: message, level: :log
|
159
176
|
component_id = message.attributes["cId"]
|
@@ -170,14 +187,14 @@ module RSMP
|
|
170
187
|
end
|
171
188
|
response = CommandResponse.new({
|
172
189
|
"cId"=>component_id,
|
173
|
-
"cTS"=>
|
190
|
+
"cTS"=>clock.to_s,
|
174
191
|
"rvs"=>rvs
|
175
192
|
})
|
176
193
|
acknowledge message
|
177
194
|
send_message response
|
178
195
|
end
|
179
196
|
|
180
|
-
def process_status_request message
|
197
|
+
def process_status_request message, options={}
|
181
198
|
component_id = message.attributes["cId"]
|
182
199
|
component = @site.find_component component_id
|
183
200
|
log "Received #{message.type}", message: message, level: :log
|
@@ -187,8 +204,9 @@ module RSMP
|
|
187
204
|
end
|
188
205
|
response = StatusResponse.new({
|
189
206
|
"cId"=>component_id,
|
190
|
-
"sTs"=>
|
191
|
-
"sS"=>sS
|
207
|
+
"sTs"=>clock.to_s,
|
208
|
+
"sS"=>sS,
|
209
|
+
"mId" => options[:m_id]
|
192
210
|
})
|
193
211
|
acknowledge message
|
194
212
|
send_message response
|
@@ -213,10 +231,11 @@ module RSMP
|
|
213
231
|
update_list[component] ||= {}
|
214
232
|
|
215
233
|
subs = @status_subscriptions[component]
|
234
|
+
now = Time.now # internal timestamp
|
216
235
|
|
217
236
|
message.attributes["sS"].each do |arg|
|
218
237
|
sCI = arg["sCI"]
|
219
|
-
subcription = {interval: arg["uRt"].to_i, last_sent_at:
|
238
|
+
subcription = {interval: arg["uRt"].to_i, last_sent_at: now}
|
220
239
|
subs[sCI] ||= {}
|
221
240
|
subs[sCI][arg["n"]] = subcription
|
222
241
|
|
@@ -250,18 +269,40 @@ module RSMP
|
|
250
269
|
status_update_timer now if ready?
|
251
270
|
end
|
252
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
|
+
|
253
287
|
def status_update_timer now
|
254
288
|
update_list = {}
|
255
289
|
# go through subscriptons and build a similarly organized list,
|
256
290
|
# that only contains what should be send
|
257
291
|
|
258
292
|
@status_subscriptions.each_pair do |component,by_code|
|
293
|
+
component_object = @site.find_component component
|
259
294
|
by_code.each_pair do |code,by_name|
|
260
295
|
by_name.each_pair do |name,subscription|
|
296
|
+
current = nil
|
261
297
|
if subscription[:interval] == 0
|
262
298
|
# send as soon as the data changes
|
263
|
-
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
|
264
304
|
should_send = true
|
305
|
+
store_last_sent_status component, code, name, current
|
265
306
|
end
|
266
307
|
else
|
267
308
|
# send at regular intervals
|
@@ -272,25 +313,27 @@ module RSMP
|
|
272
313
|
if should_send
|
273
314
|
subscription[:last_sent_at] = now
|
274
315
|
update_list[component] ||= {}
|
275
|
-
update_list[component][code] ||=
|
276
|
-
update_list[component][code]
|
316
|
+
update_list[component][code] ||= {}
|
317
|
+
update_list[component][code][name] = current
|
277
318
|
end
|
278
319
|
end
|
279
320
|
end
|
280
321
|
end
|
281
322
|
send_status_updates update_list
|
282
|
-
rescue StandardError => e
|
283
|
-
log ["Status update exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
|
284
323
|
end
|
285
324
|
|
286
325
|
def send_status_updates update_list
|
287
|
-
now =
|
326
|
+
now = clock.to_s
|
288
327
|
update_list.each_pair do |component_id,by_code|
|
289
328
|
component = @site.find_component component_id
|
290
329
|
sS = []
|
291
330
|
by_code.each_pair do |code,names|
|
292
|
-
names.map do |status_name|
|
293
|
-
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
|
294
337
|
sS << { "sCI" => code,
|
295
338
|
"n" => status_name,
|
296
339
|
"s" => value.to_s,
|
@@ -308,7 +351,7 @@ module RSMP
|
|
308
351
|
|
309
352
|
def send_alarm
|
310
353
|
message = Alarm.new({
|
311
|
-
"aSTS"=>
|
354
|
+
"aSTS"=>clock.to_s,
|
312
355
|
"fP"=>nil,
|
313
356
|
"fS"=>nil,
|
314
357
|
"se"=>@site.aggregated_status_bools
|