rsmp 0.1.11 → 0.1.21

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.
@@ -4,12 +4,12 @@
4
4
 
5
5
  module RSMP
6
6
  class Site < Node
7
- include SiteBase
7
+ include Components
8
8
 
9
9
  attr_reader :rsmp_versions, :site_settings, :logger, :proxies
10
10
 
11
11
  def initialize options={}
12
- initialize_site
12
+ initialize_components
13
13
  handle_site_settings options
14
14
  super options
15
15
  @proxies = []
@@ -27,6 +27,7 @@ module RSMP
27
27
  { 'ip' => '127.0.0.1', 'port' => 12111 }
28
28
  ],
29
29
  'rsmp_versions' => ['3.1.1','3.1.2','3.1.3','3.1.4'],
30
+ 'sxl' => 'traffic_light_controller',
30
31
  'sxl_version' => '1.0.7',
31
32
  'timer_interval' => 0.1,
32
33
  'watchdog_interval' => 1,
@@ -39,6 +40,9 @@ module RSMP
39
40
  'site_ready_timeout' => 1,
40
41
  'reconnect_interval' => 0.1,
41
42
  'send_after_connect' => true,
43
+ 'components' => {
44
+ 'C1' => {}
45
+ }
42
46
  }
43
47
  if options[:site_settings]
44
48
  converted = options[:site_settings].map { |k,v| [k.to_s,v] }.to_h #convert symbol keys to string keys
@@ -66,7 +70,7 @@ module RSMP
66
70
  end
67
71
  end
68
72
 
69
- def build_connector settings
73
+ def build_proxy settings
70
74
  SupervisorProxy.new settings
71
75
  end
72
76
 
@@ -77,7 +81,7 @@ module RSMP
77
81
  end
78
82
 
79
83
  def connect_to_supervisor task, supervisor_settings
80
- proxy = build_connector({
84
+ proxy = build_proxy({
81
85
  site: self,
82
86
  task: @task,
83
87
  settings: @site_settings,
@@ -106,7 +110,9 @@ module RSMP
106
110
  if @site_settings["reconnect_interval"] != :no
107
111
  # sleep until waken by reconnect() or the reconnect interval passed
108
112
  proxy.set_state :wait_for_reconnect
109
- task.with_timeout(@site_settings["reconnect_interval"]) { @sleep_condition.wait }
113
+ task.with_timeout(@site_settings["reconnect_interval"]) do
114
+ @sleep_condition.wait
115
+ end
110
116
  else
111
117
  proxy.set_state :cannot_connect
112
118
  break
@@ -137,5 +143,6 @@ module RSMP
137
143
  proxy.stop
138
144
  end
139
145
  end
146
+
140
147
  end
141
148
  end
@@ -2,13 +2,14 @@
2
2
 
3
3
  module RSMP
4
4
  class SiteProxy < Proxy
5
- include SiteBase
5
+ include Components
6
+ include SiteProxyWait
6
7
 
7
8
  attr_reader :supervisor, :site_id
8
9
 
9
10
  def initialize options
10
11
  super options
11
- initialize_site
12
+ initialize_components
12
13
  @supervisor = options[:supervisor]
13
14
  @settings = @supervisor.supervisor_settings.clone
14
15
  @site_id = nil
@@ -23,6 +24,11 @@ module RSMP
23
24
  start_reader
24
25
  end
25
26
 
27
+ def stop
28
+ log "Closing connection to site", level: :info
29
+ super
30
+ end
31
+
26
32
  def connection_complete
27
33
  super
28
34
  log "Connection to site #{@site_id} established", level: :info
@@ -50,11 +56,20 @@ module RSMP
50
56
  end
51
57
  end
52
58
 
59
+ def process_command_response message
60
+ log "Received #{message.type}", message: message, level: :log
61
+ acknowledge message
62
+ end
63
+
64
+ def process_deferred
65
+ supervisor.process_deferred
66
+ end
67
+
53
68
  def version_accepted message
54
69
  log "Received Version message for site #{@site_id} using RSMP #{@rsmp_version}", message: message, level: :log
55
70
  start_timer
56
71
  acknowledge message
57
- send_version @site_id, @rsmp_version
72
+ send_version @site_id, @settings['rsmp_versions']
58
73
  @version_determined = true
59
74
 
60
75
  if @settings['sites']
@@ -81,7 +96,7 @@ module RSMP
81
96
  component = @components[c_id]
82
97
  if component == nil
83
98
  if @site_settings == nil || @site_settings['components'] == nil
84
- component = build_component c_id
99
+ component = build_component(id:c_id, type:nil)
85
100
  @components[c_id] = component
86
101
  log "Adding component #{c_id} to site #{@site_id}", level: :info
87
102
  else
@@ -123,16 +138,33 @@ module RSMP
123
138
  @supervisor.site_ids_changed
124
139
  end
125
140
 
126
- def request_status component, status_list, timeout=nil
127
- raise NotReady unless @state == :ready
141
+ def request_status component, status_list, options={}
142
+ raise NotReady unless ready?
143
+ m_id = options[:m_id] || RSMP::Message.make_m_id
144
+
145
+ # additional items can be used when verifying the response,
146
+ # but must to remove from the request
147
+ request_list = status_list.map { |item| item.slice('sCI','n') }
148
+
128
149
  message = RSMP::StatusRequest.new({
129
150
  "ntsOId" => '',
130
151
  "xNId" => '',
131
152
  "cId" => component,
132
- "sS" => status_list
153
+ "sS" => request_list,
154
+ "mId" => m_id
133
155
  })
134
- send_message message
135
- return message, wait_for_status_response(message: message, timeout: timeout)
156
+ if options[:collect]
157
+ result = nil
158
+ task = @task.async do |task|
159
+ collect_options = options[:collect].merge status_list: status_list
160
+ collect_status_responses task, collect_options, m_id
161
+ end
162
+ send_message message
163
+ return message, task.wait
164
+ else
165
+ send_message message
166
+ message
167
+ end
136
168
  end
137
169
 
138
170
  def process_status_response message
@@ -140,36 +172,37 @@ module RSMP
140
172
  acknowledge message
141
173
  end
142
174
 
143
- def wait_for_status_response options
144
- raise ArgumentError unless options[:message]
145
- item = @archive.capture(@task, options.merge(
146
- type: ['StatusResponse','MessageNotAck'],
147
- with_message: true,
148
- num: 1
149
- )) do |item|
150
- if item[:message].type == 'MessageNotAck'
151
- next item[:message].attribute('oMId') == options[:message].m_id
152
- elsif item[:message].type == 'StatusResponse'
153
- next item[:message].attribute('cId') == options[:message].attribute('cId')
154
- end
155
- end
156
- item[:message] if item
157
- end
175
+ def subscribe_to_status component, status_list, options={}
176
+ raise NotReady unless ready?
177
+ m_id = options[:m_id] || RSMP::Message.make_m_id
178
+
179
+ # additional items can be used when verifying the response,
180
+ # but must to remove from the subscribe message
181
+ subscribe_list = status_list.map { |item| item.slice('sCI','n','uRt') }
158
182
 
159
- def subscribe_to_status component, status_list, timeout
160
- raise NotReady unless @state == :ready
161
183
  message = RSMP::StatusSubscribe.new({
162
184
  "ntsOId" => '',
163
185
  "xNId" => '',
164
186
  "cId" => component,
165
- "sS" => status_list
187
+ "sS" => subscribe_list,
188
+ 'mId' => m_id
166
189
  })
167
- send_message message
168
- return message, wait_for_status_update(component: component, timeout: timeout)
190
+ if options[:collect]
191
+ result = nil
192
+ task = @task.async do |task|
193
+ collect_options = options[:collect].merge status_list: status_list
194
+ collect_status_updates task, collect_options, m_id
195
+ end
196
+ send_message message
197
+ return message, task.wait
198
+ else
199
+ send_message message
200
+ message
201
+ end
169
202
  end
170
203
 
171
204
  def unsubscribe_to_status component, status_list
172
- raise NotReady unless @state == :ready
205
+ raise NotReady unless ready?
173
206
  message = RSMP::StatusUnsubscribe.new({
174
207
  "ntsOId" => '',
175
208
  "xNId" => '',
@@ -185,48 +218,42 @@ module RSMP
185
218
  acknowledge message
186
219
  end
187
220
 
188
- def wait_for_status_update options={}
189
- raise ArgumentError unless options[:component]
190
- item = @archive.capture(@task,options.merge(type: "StatusUpdate", with_message: true, num: 1)) do |item|
191
- # TODO check components
192
- found = false
193
- sS = item[:message].attributes['sS']
194
- sS.each do |status|
195
- break if options[:sCI] && options[:sCI] != status['sCI']
196
- break if options[:n] && options[:n] != status['n']
197
- break if options[:q] && options[:q] != status['q']
198
- break if options[:s] && options[:s] != status['s']
199
- found = true
200
- break
201
- end
202
- found
203
- end
204
- item[:message] if item
205
- end
206
-
207
- def send_command component, args
208
- raise NotReady unless @state == :ready
209
- message = RSMP::CommandRequest.new({
221
+ def send_alarm_acknowledgement component, alarm_code
222
+ message = RSMP::AlarmAcknowledged.new({
210
223
  "ntsOId" => '',
211
224
  "xNId" => '',
212
225
  "cId" => component,
213
- "arg" => args
226
+ "aCId" => alarm_code,
227
+ "xACId" => '',
228
+ "xNACId" => '',
229
+ "aSp" => 'Acknowledge'
214
230
  })
215
231
  send_message message
216
232
  message
217
233
  end
218
234
 
219
- def process_command_response message
220
- log "Received #{message.type}", message: message, level: :log
221
- acknowledge message
222
- end
223
-
224
- def wait_for_command_response options
225
- raise ArgumentError unless options[:component]
226
- item = @archive.capture(@task,options.merge(num: 1, type: "CommandResponse", with_message: true)) do |item|
227
- # check component
235
+ def send_command component, command_list, options={}
236
+ raise NotReady unless ready?
237
+ m_id = options[:m_id] || RSMP::Message.make_m_id
238
+ message = RSMP::CommandRequest.new({
239
+ "ntsOId" => '',
240
+ "xNId" => '',
241
+ "cId" => component,
242
+ "arg" => command_list,
243
+ "mId" => m_id
244
+ })
245
+ if options[:collect]
246
+ result = nil
247
+ task = @task.async do |task|
248
+ collect_options = options[:collect].merge command_list: command_list
249
+ collect_command_responses task, collect_options, m_id
250
+ end
251
+ send_message message
252
+ return message, task.wait
253
+ else
254
+ send_message message
255
+ message
228
256
  end
229
- item[:message] if item
230
257
  end
231
258
 
232
259
  def set_watchdog_interval interval
@@ -261,6 +288,5 @@ module RSMP
261
288
  site_ids_changed
262
289
  end
263
290
 
264
-
265
291
  end
266
- end
292
+ end
@@ -0,0 +1,181 @@
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 options={}
24
+ matching_alarm = nil
25
+ item = collect(@task,options.merge(type: "Alarm", with_message: true, num: 1)) do |item|
26
+ # TODO check components
27
+ matching_alarm = nil
28
+ alarm = item[: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: item[: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
+ item = collect(parent_task,options.merge({
53
+ type: ['CommandResponse','MessageNotAck'],
54
+ num: 1
55
+ })) do |item|
56
+ message = item[: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
+ found = []
72
+ # look through querues
73
+ want.each_with_index do |query,i|
74
+ # look through items in message
75
+ item[:message].attributes['rvs'].each do |input|
76
+ ok = command_match? query, input
77
+ if ok
78
+ result[query] = input
79
+ found << i # record which queries where matched succesfully
80
+ end
81
+ end
82
+ end
83
+ # remove queries that where matched
84
+ found.sort.reverse.each do |i|
85
+ want.delete_at i
86
+ end
87
+ want.empty? # any queries left to match?
88
+ end
89
+ end
90
+ result
91
+ rescue Async::TimeoutError
92
+ raise RSMP::TimeoutError.new "Did not receive command response to #{m_id} within #{options[:timeout]}s"
93
+ end
94
+
95
+ def collect_status_updates_or_responses task, type, options, m_id
96
+ want = options[:status_list]
97
+ result = {}
98
+ # wait for a status update
99
+ item = collect(task,options.merge({
100
+ type: [type,'MessageNotAck'],
101
+ num: 1
102
+ })) do |item|
103
+ message = item[:message]
104
+ if message.is_a?(MessageNotAck)
105
+ if message.attribute('oMId') == m_id
106
+ # set result to an exception, but don't raise it.
107
+ # this will be returned by the task and stored as the task result
108
+ # when the parent task call wait() on the task, the exception
109
+ # will be raised in the parent task, and caught by rspec.
110
+ # rspec will then show the error and record the test as failed
111
+ m_id_short = RSMP::Message.shorten_m_id m_id, 8
112
+ result = RSMP::MessageRejected.new "Status request #{m_id_short} was rejected: #{message.attribute('rea')}"
113
+ next true # done, no more messages wanted
114
+ end
115
+ false
116
+ else
117
+ found = []
118
+ # look through querues
119
+ want.each_with_index do |query,i|
120
+ # look through status items in message
121
+ item[:message].attributes['sS'].each do |input|
122
+ ok = status_match? query, input
123
+ if ok
124
+ result[query] = input
125
+ found << i # record which queries where matched succesfully
126
+ end
127
+ end
128
+ end
129
+ # remove queries that where matched
130
+ found.sort.reverse.each do |i|
131
+ want.delete_at i
132
+ end
133
+ want.empty? # any queries left to match?
134
+ end
135
+ end
136
+ result
137
+ rescue Async::TimeoutError
138
+ type_str = {'StatusUpdate'=>'update', 'StatusResponse'=>'response'}[type]
139
+ raise RSMP::TimeoutError.new "Did not received status #{type_str} in reply to #{m_id} within #{options[:timeout]}s"
140
+ end
141
+
142
+ def status_match? query, item
143
+ return false if query['sCI'] && query['sCI'] != item['sCI']
144
+ return false if query['n'] && query['n'] != item['n']
145
+ return false if query['q'] && query['q'] != item['q']
146
+ if query['s'].is_a? Regexp
147
+ return false if query['s'] && item['s'] !~ query['s']
148
+ else
149
+ return false if query['s'] && item['s'] != query['s']
150
+ end
151
+ true
152
+ end
153
+
154
+ def command_match? query, item
155
+ return false if query['cCI'] && query['cCI'] != item['cCI']
156
+ return false if query['n'] && query['n'] != item['n']
157
+ if query['v'].is_a? Regexp
158
+ return false if query['v'] && item['v'] !~ query['v']
159
+ else
160
+ return false if query['v'] && item['v'] != query['v']
161
+ end
162
+ true
163
+ end
164
+
165
+ def send_while_collecting parent_task, send_block, &collect_block
166
+ m_id = RSMP::Message.make_m_id # make message id so we can start waiting for it
167
+
168
+ # wait for command responses in an async task
169
+ task = parent_task.async do |task|
170
+ collect_block.call task, m_id
171
+ end
172
+
173
+ # call block, it should send command request using the given m_id
174
+ send_block.call m_id
175
+
176
+ # wait for the response and return it, raise exception if NotAck received, it it timed out
177
+ task.wait
178
+ end
179
+
180
+ end
181
+ end