rsmp 0.1.19 → 0.1.31

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rsmp/rsmp.rb CHANGED
@@ -1,31 +1,42 @@
1
- # RSMP module
1
+ # Get the current time in UTC, with optional adjustment
2
+ # Convertion to string uses the RSMP format 2015-06-08T12:01:39.654Z
3
+ # Note that using to_s on a my_clock.to_s will not produce an RSMP formatted timestamp,
4
+ # you need to use Clock.to_s my_clock
5
+
6
+ require 'time'
2
7
 
3
8
  module RSMP
4
- WRAPPING_DELIMITER = "\f"
5
9
 
6
- def self.now_object
7
- # date using UTC time zone
8
- Time.now.utc
9
- end
10
+ class Clock
11
+ attr_reader :adjustment
10
12
 
11
- def self.now_object_to_string now
12
- # date in the format required by rsmp, using UTC time zone
13
- # example: 2015-06-08T12:01:39.654Z
14
- time ||= now.utc
15
- time.strftime("%FT%T.%3NZ")
16
- end
13
+ def initialize
14
+ @adjustment = 0
15
+ end
17
16
 
18
- def self.now_string time=nil
19
- time ||= Time.now
20
- now_object_to_string time
21
- end
17
+ def set target
18
+ @adjustment = target - Time.now
19
+ end
22
20
 
23
- def self.parse_time time_str
24
- Time.parse time_str
25
- end
26
-
27
- def self.log_prefix ip
28
- "#{now_string} #{ip.ljust(20)}"
29
- end
21
+ def reset
22
+ @adjustment = 0
23
+ end
30
24
 
25
+ def now
26
+ Time.now.utc + @adjustment
27
+ end
28
+
29
+ def to_s
30
+ Clock.to_s now
31
+ end
32
+
33
+ def self.now
34
+ Time.now.utc
35
+ end
36
+
37
+ def self.to_s time=nil
38
+ (time || now).strftime("%FT%T.%3NZ")
39
+ end
40
+
41
+ end
31
42
  end
data/lib/rsmp/site.rb CHANGED
@@ -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 = []
@@ -20,43 +20,41 @@ module RSMP
20
20
  @site_settings['site_id']
21
21
  end
22
22
 
23
- def handle_site_settings options
24
- @site_settings = {
23
+ def handle_site_settings options={}
24
+ defaults = {
25
25
  'site_id' => 'RN+SI0001',
26
26
  'supervisors' => [
27
27
  { 'ip' => '127.0.0.1', 'port' => 12111 }
28
28
  ],
29
- 'rsmp_versions' => ['3.1.1','3.1.2','3.1.3','3.1.4'],
30
- 'sxl' => 'traffic_light_controller',
31
- 'sxl_version' => '1.0.7',
32
- 'timer_interval' => 0.1,
33
- 'watchdog_interval' => 1,
34
- 'watchdog_timeout' => 2,
35
- 'acknowledgement_timeout' => 2,
36
- 'command_response_timeout' => 1,
37
- 'status_response_timeout' => 1,
38
- 'status_update_timeout' => 1,
39
- 'site_connect_timeout' => 2,
40
- 'site_ready_timeout' => 1,
41
- 'reconnect_interval' => 0.1,
29
+ 'rsmp_versions' => 'all',
30
+ 'sxl' => 'tlc',
31
+ 'sxl_version' => '1.0.15',
32
+ 'intervals' => {
33
+ 'timer_' => 0.1,
34
+ 'watchdog' => 1,
35
+ 'reconnect' => 0.1
36
+ },
37
+ 'timeouts' => {
38
+ 'watchdog' => 2,
39
+ 'acknowledgement' => 2
40
+ },
42
41
  'send_after_connect' => true,
43
42
  'components' => {
44
43
  'C1' => {}
45
44
  }
46
45
  }
47
- if options[:site_settings]
48
- converted = options[:site_settings].map { |k,v| [k.to_s,v] }.to_h #convert symbol keys to string keys
49
- converted.compact!
50
- @site_settings.merge! converted
51
- end
52
-
53
- required = [:supervisors,:rsmp_versions,:site_id,:watchdog_interval,:watchdog_timeout,
54
- :acknowledgement_timeout,:command_response_timeout]
55
- check_required_settings @site_settings, required
56
-
46
+
47
+ @site_settings = defaults.deep_merge options[:site_settings]
48
+ check_sxl_version
57
49
  setup_components @site_settings['components']
58
50
  end
59
51
 
52
+ def check_sxl_version
53
+ sxl = @site_settings['sxl']
54
+ version = @site_settings['sxl_version']
55
+ RSMP::Schemer::find_schema! sxl, version
56
+ end
57
+
60
58
  def reconnect
61
59
  @sleep_condition.signal
62
60
  end
@@ -64,8 +62,10 @@ module RSMP
64
62
  def start_action
65
63
  @site_settings["supervisors"].each do |supervisor_settings|
66
64
  @task.async do |task|
67
- task.annotate "site_proxy"
65
+ task.annotate "site proxy"
68
66
  connect_to_supervisor task, supervisor_settings
67
+ rescue StandardError => e
68
+ notify_error e, level: :internal
69
69
  end
70
70
  end
71
71
  end
@@ -101,16 +101,14 @@ module RSMP
101
101
  proxy.run # run until disconnected
102
102
  rescue IOError => e
103
103
  log "Stream error: #{e}", level: :warning
104
- rescue SystemCallError => e # all ERRNO errors
105
- log "Reader exception: #{e.to_s}", level: :error
106
104
  rescue StandardError => e
107
- log ["Reader exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
105
+ notify_error e, level: :internal
108
106
  ensure
109
107
  begin
110
- if @site_settings["reconnect_interval"] != :no
108
+ if @site_settings['intervals']['watchdog'] != :no
111
109
  # sleep until waken by reconnect() or the reconnect interval passed
112
110
  proxy.set_state :wait_for_reconnect
113
- task.with_timeout(@site_settings["reconnect_interval"]) do
111
+ task.with_timeout(@site_settings['intervals']['watchdog']) do
114
112
  @sleep_condition.wait
115
113
  end
116
114
  else
@@ -135,7 +133,7 @@ module RSMP
135
133
  def starting
136
134
  log "Starting site #{@site_settings["site_id"]}",
137
135
  level: :info,
138
- timestamp: RSMP.now_object
136
+ timestamp: @clock.now
139
137
  end
140
138
 
141
139
  def alarm
@@ -2,18 +2,24 @@
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
15
16
  end
16
17
 
18
+ def inspect
19
+ "#<#{self.class.name}:#{self.object_id}, #{inspector(
20
+ :@acknowledgements,:@settings,:@site_settings,:@components
21
+ )}>"
22
+ end
17
23
  def node
18
24
  supervisor
19
25
  end
@@ -30,7 +36,7 @@ module RSMP
30
36
 
31
37
  def connection_complete
32
38
  super
33
- log "Connection to site #{@site_id} established", level: :info
39
+ log "Connection to site #{@site_id} established, using core #{@rsmp_version}, #{@sxl} #{@site_sxl_version}", level: :info
34
40
  end
35
41
 
36
42
  def process_message message
@@ -42,6 +48,8 @@ module RSMP
42
48
  will_not_handle message
43
49
  when AggregatedStatus
44
50
  process_aggregated_status message
51
+ when AggregatedStatusRequest
52
+ will_not_handle message
45
53
  when Alarm
46
54
  process_alarm message
47
55
  when CommandResponse
@@ -55,23 +63,44 @@ module RSMP
55
63
  end
56
64
  end
57
65
 
66
+ def process_command_response message
67
+ log "Received #{message.type}", message: message, level: :log
68
+ acknowledge message
69
+ end
70
+
58
71
  def process_deferred
59
72
  supervisor.process_deferred
60
73
  end
61
74
 
62
75
  def version_accepted message
63
- log "Received Version message for site #{@site_id} using RSMP #{@rsmp_version}", message: message, level: :log
76
+ log "Received Version message for site #{@site_id}", message: message, level: :log
64
77
  start_timer
65
78
  acknowledge message
66
- send_version @site_id, @settings['rsmp_versions']
79
+ send_version @site_id, rsmp_versions
67
80
  @version_determined = true
68
81
 
69
- if @settings['sites']
70
- @site_settings = @settings['sites'][@site_id]
71
- @site_settings =@settings['sites'][:any] unless @site_settings
72
- if @site_settings
73
- setup_components @site_settings['components']
82
+ end
83
+
84
+ def request_aggregated_status component, options={}
85
+ raise NotReady unless ready?
86
+ m_id = options[:m_id] || RSMP::Message.make_m_id
87
+
88
+ message = RSMP::AggregatedStatusRequest.new({
89
+ "ntsOId" => '',
90
+ "xNId" => '',
91
+ "cId" => component,
92
+ "mId" => m_id
93
+ })
94
+ if options[:collect]
95
+ result = nil
96
+ task = @task.async do |task|
97
+ wait_for_aggregated_status task, options[:collect]
74
98
  end
99
+ send_message message, validate: options[:validate]
100
+ return message, task.wait
101
+ else
102
+ send_message message, validate: options[:validate]
103
+ message
75
104
  end
76
105
  end
77
106
 
@@ -132,38 +161,39 @@ module RSMP
132
161
  @supervisor.site_ids_changed
133
162
  end
134
163
 
135
- def fetch_status parent_task, options
136
- wait_for_status_responses(parent_task,options) do |m_id|
137
- request_status options.merge(m_id: m_id)
138
- end
139
- end
164
+ def request_status component, status_list, options={}
165
+ raise NotReady unless ready?
166
+ m_id = options[:m_id] || RSMP::Message.make_m_id
140
167
 
141
- # Convert from a short ruby hash:
142
- # {:S0001=>[:signalgroupstatus, :cyclecounter, :basecyclecounter, :stage]}
143
- # to an rsmp-style list:
144
- # [{"sCI"=>"S0001", "n"=>"signalgroupstatus"}, {"sCI"=>"S0001", "n"=>"cyclecounter"}, {"sCI"=>"S0001", "n"=>"basecyclecounter"}, {"sCI"=>"S0001", "n"=>"stage"}]
145
- #
146
- # If the input is already an array, just return it
147
- def convert_status_list list
148
- return list.clone if list.is_a? Array
149
- list.map do |status_code_id,names|
150
- names.map do |name|
151
- { 'sCI' => status_code_id.to_s, 'n' => name.to_s }
152
- end
153
- end.flatten
154
- end
168
+ # additional items can be used when verifying the response,
169
+ # but must to remove from the request
170
+ request_list = status_list.map { |item| item.slice('sCI','n') }
155
171
 
156
- def request_status options
157
- raise NotReady unless ready?
158
172
  message = RSMP::StatusRequest.new({
159
173
  "ntsOId" => '',
160
174
  "xNId" => '',
161
- "cId" => options[:component],
162
- "sS" => convert_status_list(options[:status_list]),
163
- "mId" => options[:m_id]
175
+ "cId" => component,
176
+ "sS" => request_list,
177
+ "mId" => m_id
164
178
  })
165
- send_message message
166
- message
179
+ if options[:collect]
180
+ result = nil
181
+ task = @task.async do |task|
182
+ collect_options = options[:collect].merge status_list: status_list
183
+ collect_status_responses task, collect_options, m_id
184
+ end
185
+ send_message message, validate: options[:validate]
186
+
187
+ # task.wait return the result of the task. if the task raised an exception
188
+ # it will be reraised. but that mechanish does not work if multiple values
189
+ # are returned. so manually raise if first element is an exception
190
+ result = task.wait
191
+ raise result.first if result.first.is_a? Exception
192
+ return message, *result
193
+ else
194
+ send_message message, validate: options[:validate]
195
+ message
196
+ end
167
197
  end
168
198
 
169
199
  def process_status_response message
@@ -173,26 +203,48 @@ module RSMP
173
203
 
174
204
  def subscribe_to_status component, status_list, options={}
175
205
  raise NotReady unless ready?
206
+ m_id = options[:m_id] || RSMP::Message.make_m_id
207
+
208
+ # additional items can be used when verifying the response,
209
+ # but must to remove from the subscribe message
210
+ subscribe_list = status_list.map { |item| item.slice('sCI','n','uRt') }
211
+
176
212
  message = RSMP::StatusSubscribe.new({
177
213
  "ntsOId" => '',
178
214
  "xNId" => '',
179
215
  "cId" => component,
180
- "sS" => convert_status_list(status_list),
181
- 'mId'=>options[:m_id]
216
+ "sS" => subscribe_list,
217
+ 'mId' => m_id
182
218
  })
183
- send_message message
184
- return message
219
+ if options[:collect]
220
+ result = nil
221
+ task = @task.async do |task|
222
+ collect_options = options[:collect].merge status_list: status_list
223
+ collect_status_updates task, collect_options, m_id
224
+ end
225
+ send_message message, validate: options[:validate]
226
+
227
+ # task.wait return the result of the task. if the task raised an exception
228
+ # it will be reraised. but that mechanish does not work if multiple values
229
+ # are returned. so manually raise if first element is an exception
230
+ result = task.wait
231
+ raise result.first if result.first.is_a? Exception
232
+ return message, *result
233
+ else
234
+ send_message message, validate: options[:validate]
235
+ message
236
+ end
185
237
  end
186
238
 
187
- def unsubscribe_to_status component, status_list
239
+ def unsubscribe_to_status component, status_list, options={}
188
240
  raise NotReady unless ready?
189
241
  message = RSMP::StatusUnsubscribe.new({
190
242
  "ntsOId" => '',
191
243
  "xNId" => '',
192
244
  "cId" => component,
193
- "sS" => convert_status_list(status_list)
245
+ "sS" => status_list
194
246
  })
195
- send_message message
247
+ send_message message, validate: options[:validate]
196
248
  message
197
249
  end
198
250
 
@@ -201,37 +253,7 @@ module RSMP
201
253
  acknowledge message
202
254
  end
203
255
 
204
- def status_match? query, item
205
- return false if query[:sCI] && query[:sCI] != item['sCI']
206
- return false if query[:n] && query[:n] != item['n']
207
- return false if query[:q] && query[:q] != item['q']
208
- if query[:s].is_a? Regexp
209
- return false if query[:s] && item['s'] !~ query[:s]
210
- else
211
- return false if query[:s] && item['s'] != query[:s]
212
- end
213
- true
214
- end
215
-
216
- def wait_for_alarm options={}
217
- raise ArgumentError.new("component argument is missing") unless options[:component]
218
- matching_alarm = nil
219
- item = @archive.capture(@task,options.merge(type: "Alarm", with_message: true, num: 1)) do |item|
220
- # TODO check components
221
- matching_alarm = nil
222
- alarm = item[:message]
223
- next if options[:aCId] && options[:aCId] != alarm.attribute("aCId")
224
- next if options[:aSp] && options[:aSp] != alarm.attribute("aSp")
225
- next if options[:aS] && options[:aS] != alarm.attribute("aS")
226
- matching_alarm = alarm
227
- break
228
- end
229
- if item
230
- { message: item[:message], status: matching_alarm }
231
- end
232
- end
233
-
234
- def send_alarm_acknowledgement component, alarm_code
256
+ def send_alarm_acknowledgement component, alarm_code, options={}
235
257
  message = RSMP::AlarmAcknowledged.new({
236
258
  "ntsOId" => '',
237
259
  "xNId" => '',
@@ -241,44 +263,53 @@ module RSMP
241
263
  "xNACId" => '',
242
264
  "aSp" => 'Acknowledge'
243
265
  })
244
- send_message message
266
+ send_message message, validate: options[:validate]
245
267
  message
246
268
  end
247
269
 
248
- def wait_for_alarm_acknowledgement_response options
249
- raise ArgumentError.new("component argument is missing") unless options[:component]
250
- item = @archive.capture(@task,options.merge(
251
- num: 1,
252
- type: ['AlarmAcknowledgedResponse','MessageNotAck'],
253
- with_message: true
254
- )) do |item|
255
- if item[:message].type == 'MessageNotAck'
256
- next item[:message].attribute('oMId') == options[:message].m_id
257
- elsif item[:message].type == 'AlarmAcknowledgedResponse'
258
- next item[:message].attribute('cId') == options[:message].attribute('cId')
259
- end
260
- end
261
- item[:message] if item
262
- end
263
-
264
- def send_command component, args, options={}
270
+ def send_command component, command_list, options={}
265
271
  raise NotReady unless ready?
272
+ m_id = options[:m_id] || RSMP::Message.make_m_id
266
273
  message = RSMP::CommandRequest.new({
267
274
  "ntsOId" => '',
268
275
  "xNId" => '',
269
276
  "cId" => component,
270
- "arg" => args,
271
- "mId" => options[:m_id]
277
+ "arg" => command_list,
278
+ "mId" => m_id
272
279
  })
273
- send_message message
274
- message
280
+ if options[:collect]
281
+ result = nil
282
+ task = @task.async do |task|
283
+ collect_options = options[:collect].merge command_list: command_list
284
+ collect_command_responses task, collect_options, m_id
285
+ end
286
+ send_message message, validate: options[:validate]
287
+
288
+ # task.wait return the result of the task. if the task raised an exception
289
+ # it will be reraised. but that mechanish does not work if multiple values
290
+ # are returned. so manually raise if first element is an exception
291
+ result = task.wait
292
+ raise result.first if result.first.is_a? Exception
293
+ return message, *result
294
+ else
295
+ send_message message, validate: options[:validate]
296
+ message
297
+ end
275
298
  end
276
299
 
277
300
  def set_watchdog_interval interval
278
- @settings["watchdog_interval"] = interval
301
+ @settings['intervals']['watchdog'] = interval
279
302
  end
280
303
 
281
304
  def check_sxl_version message
305
+
306
+ # check that we have a schema for specified sxl type and version
307
+ # note that the type comes from the site config, while the version
308
+ # comes from the Version message send by the site
309
+ type = 'tlc'
310
+ version = message.attribute 'SXL'
311
+ RSMP::Schemer::find_schema! type, version
312
+
282
313
  # store sxl version requested by site
283
314
  # TODO should check agaist site settings
284
315
  @site_sxl_version = message.attribute 'SXL'
@@ -296,6 +327,8 @@ module RSMP
296
327
  check_rsmp_version message
297
328
  check_sxl_version message
298
329
  version_accepted message
330
+ rescue RSMP::Schemer::UnknownSchemaError => e
331
+ dont_acknowledge message, "Rejected #{message.type} message,", "#{e}"
299
332
  end
300
333
 
301
334
  def check_site_ids message
@@ -303,8 +336,38 @@ module RSMP
303
336
  site_id = message.attribute("siteId").map { |item| item["sId"] }.first
304
337
  @supervisor.check_site_id site_id
305
338
  @site_id = site_id
339
+ setup_site_settings
306
340
  site_ids_changed
307
341
  end
308
342
 
343
+ def find_site_settings site_id
344
+ if @settings['sites'] && @settings['sites'][@site_id]
345
+ log "Using site settings for site id #{@site_id}", level: :debug
346
+ return @settings['sites'][@site_id]
347
+ end
348
+
349
+ settings = @settings['guest']
350
+ if @settings['guest']
351
+ log "Using site settings for guest", level: :debug
352
+ return @settings['guest']
353
+ end
354
+
355
+ nil
356
+ end
357
+
358
+ def setup_site_settings
359
+ @site_settings = find_site_settings @site_id
360
+ if @site_settings
361
+ @sxl = @site_settings['sxl']
362
+ setup_components @site_settings['components']
363
+ else
364
+ dont_acknowledge message, 'Rejected', "No config found for site #{@site_id}"
365
+ end
366
+ end
367
+
368
+ def notify_error e, options={}
369
+ @supervisor.notify_error e, options if @supervisor
370
+ end
371
+
309
372
  end
310
373
  end