rsmp 0.1.12 → 0.1.27
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/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
|