rsmp 0.1.13 → 0.1.29
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 +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
|