rsmp 0.1.12 → 0.1.27
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/site.yaml +2 -2
- data/config/supervisor.yaml +12 -6
- data/config/tlc.yaml +52 -0
- 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 +131 -92
- data/lib/rsmp/collector.rb +102 -0
- data/lib/rsmp/component.rb +14 -2
- 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/error.rb +10 -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 +124 -63
- data/lib/rsmp/rsmp.rb +34 -23
- data/lib/rsmp/site.rb +27 -12
- data/lib/rsmp/site_proxy.rb +165 -73
- data/lib/rsmp/site_proxy_wait.rb +206 -0
- data/lib/rsmp/supervisor.rb +53 -23
- data/lib/rsmp/supervisor_proxy.rb +101 -41
- data/lib/rsmp/tlc.rb +907 -0
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp/wait.rb +7 -8
- data/rsmp.gemspec +9 -21
- metadata +38 -141
- 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
@@ -20,7 +20,7 @@ module RSMP
|
|
20
20
|
def handle_supervisor_settings options
|
21
21
|
@supervisor_settings = {
|
22
22
|
'port' => 12111,
|
23
|
-
'rsmp_versions' => ['3.1.1','3.1.2','3.1.3','3.1.4'],
|
23
|
+
'rsmp_versions' => ['3.1.1','3.1.2','3.1.3','3.1.4','3.1.5'],
|
24
24
|
'timer_interval' => 0.1,
|
25
25
|
'watchdog_interval' => 1,
|
26
26
|
'watchdog_timeout' => 2,
|
@@ -32,7 +32,9 @@ module RSMP
|
|
32
32
|
'site_ready_timeout' => 1,
|
33
33
|
'stop_after_first_session' => false,
|
34
34
|
'sites' => {
|
35
|
-
:any => {
|
35
|
+
:any => {
|
36
|
+
'sxl' => 'tlc'
|
37
|
+
}
|
36
38
|
}
|
37
39
|
}
|
38
40
|
|
@@ -47,17 +49,35 @@ module RSMP
|
|
47
49
|
check_required_settings @supervisor_settings, required
|
48
50
|
|
49
51
|
@rsmp_versions = @supervisor_settings["rsmp_versions"]
|
52
|
+
|
53
|
+
check_site_sxl_types
|
54
|
+
end
|
55
|
+
|
56
|
+
def check_site_sxl_types
|
57
|
+
@supervisor_settings['sites'].each do |ip,settings|
|
58
|
+
unless settings
|
59
|
+
raise RSMP::ConfigurationError.new("Configuration for site '#{ip}' is empty")
|
60
|
+
end
|
61
|
+
sxl = settings['sxl']
|
62
|
+
sxl = 'tlc' unless sxl # temporary fix until configs are updated
|
63
|
+
unless sxl
|
64
|
+
raise RSMP::ConfigurationError.new("Configuration error for site '#{ip}': No SXL specified")
|
65
|
+
end
|
66
|
+
RSMP::Schemer.find_schemas! sxl if sxl
|
67
|
+
rescue RSMP::Schemer::UnknownSchemaError => e
|
68
|
+
raise RSMP::ConfigurationError.new("Configuration error for site '#{ip}': #{e}")
|
69
|
+
end
|
50
70
|
end
|
51
71
|
|
52
72
|
def start_action
|
53
73
|
@endpoint = Async::IO::Endpoint.tcp('0.0.0.0', @supervisor_settings["port"])
|
54
|
-
@endpoint.accept do |socket|
|
74
|
+
@endpoint.accept do |socket| # creates async tasks
|
55
75
|
handle_connection(socket)
|
76
|
+
rescue StandardError => e
|
77
|
+
notify_error e, level: :internal
|
56
78
|
end
|
57
|
-
rescue SystemCallError => e # all ERRNO errors
|
58
|
-
log "Exception: #{e.to_s}", level: :error
|
59
79
|
rescue StandardError => e
|
60
|
-
|
80
|
+
notify_error e, level: :internal
|
61
81
|
end
|
62
82
|
|
63
83
|
def stop
|
@@ -74,16 +94,17 @@ module RSMP
|
|
74
94
|
remote_hostname = socket.remote_address.ip_address
|
75
95
|
remote_ip = socket.remote_address.ip_address
|
76
96
|
|
77
|
-
info = {ip:remote_ip, port:remote_port, hostname:remote_hostname, now:
|
97
|
+
info = {ip:remote_ip, port:remote_port, hostname:remote_hostname, now:Clock.now}
|
78
98
|
if accept? socket, info
|
79
99
|
connect socket, info
|
80
100
|
else
|
81
101
|
reject socket, info
|
82
102
|
end
|
83
|
-
rescue
|
84
|
-
log "
|
103
|
+
rescue ConnectionError => e
|
104
|
+
log "Rejected connection from #{remote_ip}, #{e.to_s}", level: :info
|
85
105
|
rescue StandardError => e
|
86
|
-
log "
|
106
|
+
log "Connection: #{e.to_s}", exception: e, level: :error
|
107
|
+
notify_error e, level: :internal
|
87
108
|
ensure
|
88
109
|
close socket, info
|
89
110
|
end
|
@@ -91,14 +112,14 @@ module RSMP
|
|
91
112
|
def starting
|
92
113
|
log "Starting supervisor on port #{@supervisor_settings["port"]}",
|
93
114
|
level: :info,
|
94
|
-
timestamp:
|
115
|
+
timestamp: @clock.now
|
95
116
|
end
|
96
117
|
|
97
118
|
def accept? socket, info
|
98
119
|
true
|
99
120
|
end
|
100
121
|
|
101
|
-
def
|
122
|
+
def build_proxy settings
|
102
123
|
SiteProxy.new settings
|
103
124
|
end
|
104
125
|
|
@@ -115,20 +136,25 @@ module RSMP
|
|
115
136
|
ip: info[:ip],
|
116
137
|
port: info[:port],
|
117
138
|
level: :info,
|
118
|
-
timestamp:
|
139
|
+
timestamp: Clock.now
|
119
140
|
|
120
|
-
|
141
|
+
settings = ip_to_site_settings info[:ip]
|
142
|
+
raise ConnectionError.new('unknown ip not allowed') unless settings
|
143
|
+
|
144
|
+
proxy = build_proxy({
|
121
145
|
supervisor: self,
|
146
|
+
ip: info[:ip],
|
147
|
+
port: info[:port],
|
122
148
|
task: @task,
|
123
|
-
settings:
|
149
|
+
settings: settings,
|
124
150
|
socket: socket,
|
125
151
|
info: info,
|
126
152
|
logger: @logger,
|
127
153
|
archive: @archive
|
128
154
|
})
|
129
155
|
@proxies.push proxy
|
130
|
-
|
131
156
|
proxy.run # will run until the site disconnects
|
157
|
+
ensure
|
132
158
|
@proxies.delete proxy
|
133
159
|
site_ids_changed
|
134
160
|
|
@@ -145,9 +171,9 @@ module RSMP
|
|
145
171
|
|
146
172
|
def close socket, info
|
147
173
|
if info
|
148
|
-
log "Connection to #{format_ip_and_port(info)} closed", ip: info[:ip], level: :info, timestamp:
|
174
|
+
log "Connection to #{format_ip_and_port(info)} closed", ip: info[:ip], level: :info, timestamp: Clock.now
|
149
175
|
else
|
150
|
-
log "Connection closed", level: :info, timestamp:
|
176
|
+
log "Connection closed", level: :info, timestamp: Clock.now
|
151
177
|
end
|
152
178
|
|
153
179
|
socket.close
|
@@ -167,15 +193,15 @@ module RSMP
|
|
167
193
|
def wait_for_site site_id, timeout
|
168
194
|
site = find_site site_id
|
169
195
|
return site if site
|
170
|
-
|
196
|
+
wait_for(@site_id_condition,timeout) { find_site site_id }
|
171
197
|
rescue Async::TimeoutError
|
172
|
-
|
198
|
+
raise RSMP::TimeoutError.new "Site '#{site_id}'' did not connect within #{timeout}s"
|
173
199
|
end
|
174
200
|
|
175
201
|
def wait_for_site_disconnect site_id, timeout
|
176
|
-
|
202
|
+
wait_for(@site_id_condition,timeout) { true unless find_site site_id }
|
177
203
|
rescue Async::TimeoutError
|
178
|
-
|
204
|
+
raise RSMP::TimeoutError.new "Site '#{site_id}'' did not disconnect within #{timeout}s"
|
179
205
|
end
|
180
206
|
|
181
207
|
def check_site_id site_id
|
@@ -184,7 +210,7 @@ module RSMP
|
|
184
210
|
end
|
185
211
|
|
186
212
|
def check_site_already_connected site_id
|
187
|
-
raise FatalError.new "Site #{site_id} already connected" if find_site(site_id)
|
213
|
+
raise FatalError.new "Site '#{site_id}'' already connected" if find_site(site_id)
|
188
214
|
end
|
189
215
|
|
190
216
|
def find_allowed_site_setting site_id
|
@@ -197,6 +223,10 @@ module RSMP
|
|
197
223
|
raise FatalError.new "site id #{site_id} rejected"
|
198
224
|
end
|
199
225
|
|
226
|
+
def ip_to_site_settings ip
|
227
|
+
@supervisor_settings['sites'][ip] || @supervisor_settings['sites'][:any]
|
228
|
+
end
|
229
|
+
|
200
230
|
def aggregated_status_changed site_proxy, component
|
201
231
|
end
|
202
232
|
|
@@ -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,6 +27,7 @@ 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
|
@@ -38,17 +38,23 @@ module RSMP
|
|
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,6 +80,13 @@ module RSMP
|
|
72
80
|
else
|
73
81
|
super message
|
74
82
|
end
|
83
|
+
rescue UnknownComponent, UnknownCommand, UnknownStatus,
|
84
|
+
MessageRejected, MissingAttribute => e
|
85
|
+
dont_acknowledge message, '', e.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
def process_deferred
|
89
|
+
site.process_deferred
|
75
90
|
end
|
76
91
|
|
77
92
|
def acknowledged_first_ingoing message
|
@@ -110,9 +125,9 @@ module RSMP
|
|
110
125
|
|
111
126
|
def send_aggregated_status component
|
112
127
|
message = AggregatedStatus.new({
|
113
|
-
"aSTS" =>
|
128
|
+
"aSTS" => clock.to_s,
|
114
129
|
"cId" => component.c_id,
|
115
|
-
"fP" =>
|
130
|
+
"fP" => 'NormalControl',
|
116
131
|
"fS" => nil,
|
117
132
|
"se" => component.aggregated_status_bools
|
118
133
|
})
|
@@ -135,48 +150,66 @@ module RSMP
|
|
135
150
|
acknowledge message
|
136
151
|
end
|
137
152
|
|
153
|
+
# reorganize rmsp command request arg attribute:
|
154
|
+
# [{"cCI":"M0002","cO":"setPlan","n":"status","v":"True"},{"cCI":"M0002","cO":"setPlan","n":"securityCode","v":"5678"},{"cCI":"M0002","cO":"setPlan","n":"timeplan","v":"3"}]
|
155
|
+
# into the simpler, but equivalent:
|
156
|
+
# {"M0002"=>{"status"=>"True", "securityCode"=>"5678", "timeplan"=>"3"}}
|
157
|
+
def simplify_command_requests arg
|
158
|
+
sorted = {}
|
159
|
+
arg.each do |item|
|
160
|
+
sorted[item['cCI']] ||= {}
|
161
|
+
sorted[item['cCI']][item['n']] = item['v']
|
162
|
+
end
|
163
|
+
sorted
|
164
|
+
end
|
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
|
+
|
138
174
|
def process_command_request message
|
139
175
|
log "Received #{message.type}", message: message, level: :log
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
end
|
146
|
-
rvs << { "cCI" => arg["cCI"],
|
147
|
-
"n" => arg["n"],
|
148
|
-
"v" => arg["v"],
|
149
|
-
"age" => "recent" }
|
176
|
+
component_id = message.attributes["cId"]
|
177
|
+
component = @site.find_component component_id
|
178
|
+
commands = simplify_command_requests message.attributes["arg"]
|
179
|
+
commands.each_pair do |command_code,arg|
|
180
|
+
component.handle_command command_code,arg
|
150
181
|
end
|
151
182
|
|
183
|
+
rvs = message.attributes["arg"].map do |item|
|
184
|
+
item = item.dup.merge('age'=>'recent')
|
185
|
+
item.delete 'cO'
|
186
|
+
item
|
187
|
+
end
|
152
188
|
response = CommandResponse.new({
|
153
|
-
"cId"=>
|
154
|
-
"cTS"=>
|
189
|
+
"cId"=>component_id,
|
190
|
+
"cTS"=>clock.to_s,
|
155
191
|
"rvs"=>rvs
|
156
192
|
})
|
157
193
|
acknowledge message
|
158
194
|
send_message response
|
159
195
|
end
|
160
196
|
|
161
|
-
def process_status_request message
|
197
|
+
def process_status_request message, options={}
|
162
198
|
component_id = message.attributes["cId"]
|
163
199
|
component = @site.find_component component_id
|
164
|
-
|
165
200
|
log "Received #{message.type}", message: message, level: :log
|
166
|
-
sS = message.attributes["sS"].
|
167
|
-
|
168
|
-
|
169
|
-
request
|
201
|
+
sS = message.attributes["sS"].map do |arg|
|
202
|
+
value, quality = component.get_status arg['sCI'], arg['n']
|
203
|
+
{ "s" => value.to_s, "q" => quality.to_s }.merge arg
|
170
204
|
end
|
171
205
|
response = StatusResponse.new({
|
172
206
|
"cId"=>component_id,
|
173
|
-
"sTs"=>
|
174
|
-
"sS"=>sS
|
207
|
+
"sTs"=>clock.to_s,
|
208
|
+
"sS"=>sS,
|
209
|
+
"mId" => options[:m_id]
|
175
210
|
})
|
176
211
|
acknowledge message
|
177
212
|
send_message response
|
178
|
-
rescue UnknownComponent => e
|
179
|
-
dont_acknowledge message, '', e.to_s
|
180
213
|
end
|
181
214
|
|
182
215
|
def process_status_subcribe message
|
@@ -198,10 +231,11 @@ module RSMP
|
|
198
231
|
update_list[component] ||= {}
|
199
232
|
|
200
233
|
subs = @status_subscriptions[component]
|
234
|
+
now = Time.now # internal timestamp
|
201
235
|
|
202
236
|
message.attributes["sS"].each do |arg|
|
203
237
|
sCI = arg["sCI"]
|
204
|
-
subcription = {interval: arg["uRt"].to_i, last_sent_at:
|
238
|
+
subcription = {interval: arg["uRt"].to_i, last_sent_at: now}
|
205
239
|
subs[sCI] ||= {}
|
206
240
|
subs[sCI][arg["n"]] = subcription
|
207
241
|
|
@@ -235,18 +269,40 @@ module RSMP
|
|
235
269
|
status_update_timer now if ready?
|
236
270
|
end
|
237
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
|
+
|
238
287
|
def status_update_timer now
|
239
288
|
update_list = {}
|
240
289
|
# go through subscriptons and build a similarly organized list,
|
241
290
|
# that only contains what should be send
|
242
291
|
|
243
292
|
@status_subscriptions.each_pair do |component,by_code|
|
293
|
+
component_object = @site.find_component component
|
244
294
|
by_code.each_pair do |code,by_name|
|
245
295
|
by_name.each_pair do |name,subscription|
|
296
|
+
current = nil
|
246
297
|
if subscription[:interval] == 0
|
247
298
|
# send as soon as the data changes
|
248
|
-
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
|
249
304
|
should_send = true
|
305
|
+
store_last_sent_status component, code, name, current
|
250
306
|
end
|
251
307
|
else
|
252
308
|
# send at regular intervals
|
@@ -257,31 +313,35 @@ module RSMP
|
|
257
313
|
if should_send
|
258
314
|
subscription[:last_sent_at] = now
|
259
315
|
update_list[component] ||= {}
|
260
|
-
update_list[component][code] ||=
|
261
|
-
update_list[component][code]
|
316
|
+
update_list[component][code] ||= {}
|
317
|
+
update_list[component][code][name] = current
|
262
318
|
end
|
263
319
|
end
|
264
320
|
end
|
265
321
|
end
|
266
322
|
send_status_updates update_list
|
267
|
-
rescue StandardError => e
|
268
|
-
log ["Status update exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
|
269
323
|
end
|
270
324
|
|
271
325
|
def send_status_updates update_list
|
272
|
-
now =
|
273
|
-
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
|
274
329
|
sS = []
|
275
330
|
by_code.each_pair do |code,names|
|
276
|
-
names.
|
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
|
277
337
|
sS << { "sCI" => code,
|
278
|
-
"n" =>
|
279
|
-
"s" =>
|
280
|
-
"q" =>
|
338
|
+
"n" => status_name,
|
339
|
+
"s" => value.to_s,
|
340
|
+
"q" => quality }
|
281
341
|
end
|
282
342
|
end
|
283
343
|
update = StatusUpdate.new({
|
284
|
-
"cId"=>
|
344
|
+
"cId"=>component_id,
|
285
345
|
"sTs"=>now,
|
286
346
|
"sS"=>sS
|
287
347
|
})
|
@@ -291,7 +351,7 @@ module RSMP
|
|
291
351
|
|
292
352
|
def send_alarm
|
293
353
|
message = Alarm.new({
|
294
|
-
"aSTS"=>
|
354
|
+
"aSTS"=>clock.to_s,
|
295
355
|
"fP"=>nil,
|
296
356
|
"fS"=>nil,
|
297
357
|
"se"=>@site.aggregated_status_bools
|