rsmp 0.1.17 → 0.1.30

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.
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,16 @@ 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"]) { @sleep_condition.wait }
111
+ task.with_timeout(@site_settings['intervals']['watchdog']) do
112
+ @sleep_condition.wait
113
+ end
114
114
  else
115
115
  proxy.set_state :cannot_connect
116
116
  break
@@ -133,7 +133,7 @@ module RSMP
133
133
  def starting
134
134
  log "Starting site #{@site_settings["site_id"]}",
135
135
  level: :info,
136
- timestamp: RSMP.now_object
136
+ timestamp: @clock.now
137
137
  end
138
138
 
139
139
  def alarm
@@ -141,5 +141,6 @@ module RSMP
141
141
  proxy.stop
142
142
  end
143
143
  end
144
+
144
145
  end
145
146
  end
@@ -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
@@ -23,9 +29,14 @@ module RSMP
23
29
  start_reader
24
30
  end
25
31
 
32
+ def stop
33
+ log "Closing connection to site", level: :info
34
+ super
35
+ end
36
+
26
37
  def connection_complete
27
38
  super
28
- 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
29
40
  end
30
41
 
31
42
  def process_message message
@@ -37,6 +48,8 @@ module RSMP
37
48
  will_not_handle message
38
49
  when AggregatedStatus
39
50
  process_aggregated_status message
51
+ when AggregatedStatusRequest
52
+ will_not_handle message
40
53
  when Alarm
41
54
  process_alarm message
42
55
  when CommandResponse
@@ -50,19 +63,44 @@ module RSMP
50
63
  end
51
64
  end
52
65
 
66
+ def process_command_response message
67
+ log "Received #{message.type}", message: message, level: :log
68
+ acknowledge message
69
+ end
70
+
71
+ def process_deferred
72
+ supervisor.process_deferred
73
+ end
74
+
53
75
  def version_accepted message
54
- 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
55
77
  start_timer
56
78
  acknowledge message
57
- send_version @site_id, @settings['rsmp_versions']
79
+ send_version @site_id, rsmp_versions
58
80
  @version_determined = true
59
81
 
60
- if @settings['sites']
61
- @site_settings = @settings['sites'][@site_id]
62
- @site_settings =@settings['sites'][:any] unless @site_settings
63
- if @site_settings
64
- 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]
65
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
66
104
  end
67
105
  end
68
106
 
@@ -123,16 +161,39 @@ module RSMP
123
161
  @supervisor.site_ids_changed
124
162
  end
125
163
 
126
- def request_status component, status_list, timeout=nil
127
- raise NotReady unless @state == :ready
164
+ def request_status component, status_list, options={}
165
+ raise NotReady unless ready?
166
+ m_id = options[:m_id] || RSMP::Message.make_m_id
167
+
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') }
171
+
128
172
  message = RSMP::StatusRequest.new({
129
173
  "ntsOId" => '',
130
174
  "xNId" => '',
131
175
  "cId" => component,
132
- "sS" => status_list
176
+ "sS" => request_list,
177
+ "mId" => m_id
133
178
  })
134
- send_message message
135
- return message, wait_for_status_response(message: message, timeout: timeout)
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
136
197
  end
137
198
 
138
199
  def process_status_response message
@@ -140,43 +201,50 @@ module RSMP
140
201
  acknowledge message
141
202
  end
142
203
 
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
204
+ def subscribe_to_status component, status_list, options={}
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') }
158
211
 
159
- def subscribe_to_status component, status_list, timeout
160
- raise NotReady unless @state == :ready
161
212
  message = RSMP::StatusSubscribe.new({
162
213
  "ntsOId" => '',
163
214
  "xNId" => '',
164
215
  "cId" => component,
165
- "sS" => status_list
216
+ "sS" => subscribe_list,
217
+ 'mId' => m_id
166
218
  })
167
- send_message message
168
- return message, wait_for_status_update(component: component, timeout: timeout)[: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
169
237
  end
170
238
 
171
- def unsubscribe_to_status component, status_list
172
- raise NotReady unless @state == :ready
239
+ def unsubscribe_to_status component, status_list, options={}
240
+ raise NotReady unless ready?
173
241
  message = RSMP::StatusUnsubscribe.new({
174
242
  "ntsOId" => '',
175
243
  "xNId" => '',
176
244
  "cId" => component,
177
245
  "sS" => status_list
178
246
  })
179
- send_message message
247
+ send_message message, validate: options[:validate]
180
248
  message
181
249
  end
182
250
 
@@ -185,80 +253,63 @@ module RSMP
185
253
  acknowledge message
186
254
  end
187
255
 
188
- def wait_for_status_update options={}
189
- raise ArgumentError unless options[:component]
190
- matching_status = nil
191
- item = @archive.capture(@task,options.merge(type: "StatusUpdate", with_message: true, num: 1)) do |item|
192
- # TODO check components
193
- matching_status = nil
194
- sS = item[:message].attributes['sS']
195
- sS.each do |status|
196
- next if options[:sCI] && options[:sCI] != status['sCI']
197
- next if options[:n] && options[:n] != status['n']
198
- next if options[:q] && options[:q] != status['q']
199
- if options[:s].is_a? Regexp
200
- next if options[:s] && status['s'] !~ options[:s]
201
- else
202
- next if options[:s] && options[:s] != status['s']
203
- end
204
- matching_status = status
205
- break
206
- end
207
- matching_status != nil
208
- end
209
- if item
210
- { message: item[:message], status: matching_status }
211
- end
212
- end
213
-
214
- def wait_for_alarm options={}
215
- raise ArgumentError unless options[:component]
216
- matching_alarm = nil
217
- item = @archive.capture(@task,options.merge(type: "Alarm", with_message: true, num: 1)) do |item|
218
- # TODO check components
219
- matching_alarm = nil
220
- alarm = item[:message]
221
- next if options[:aCId] && options[:aCId] != alarm.attribute("aCId")
222
- next if options[:aSp] && options[:aSp] != alarm.attribute("aSp")
223
- next if options[:aS] && options[:aS] != alarm.attribute("aS")
224
- matching_alarm = alarm
225
- break
226
- end
227
- if item
228
- { message: item[:message], status: matching_alarm }
229
- end
230
- end
231
-
232
- def send_command component, args
233
- raise NotReady unless @state == :ready
234
- message = RSMP::CommandRequest.new({
256
+ def send_alarm_acknowledgement component, alarm_code, options={}
257
+ message = RSMP::AlarmAcknowledged.new({
235
258
  "ntsOId" => '',
236
259
  "xNId" => '',
237
260
  "cId" => component,
238
- "arg" => args
261
+ "aCId" => alarm_code,
262
+ "xACId" => '',
263
+ "xNACId" => '',
264
+ "aSp" => 'Acknowledge'
239
265
  })
240
- send_message message
266
+ send_message message, validate: options[:validate]
241
267
  message
242
268
  end
243
269
 
244
- def process_command_response message
245
- log "Received #{message.type}", message: message, level: :log
246
- acknowledge message
247
- end
248
-
249
- def wait_for_command_response options
250
- raise ArgumentError unless options[:component]
251
- item = @archive.capture(@task,options.merge(num: 1, type: "CommandResponse", with_message: true)) do |item|
252
- # check component
270
+ def send_command component, command_list, options={}
271
+ raise NotReady unless ready?
272
+ m_id = options[:m_id] || RSMP::Message.make_m_id
273
+ message = RSMP::CommandRequest.new({
274
+ "ntsOId" => '',
275
+ "xNId" => '',
276
+ "cId" => component,
277
+ "arg" => command_list,
278
+ "mId" => m_id
279
+ })
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
253
297
  end
254
- item[:message] if item
255
298
  end
256
299
 
257
300
  def set_watchdog_interval interval
258
- @settings["watchdog_interval"] = interval
301
+ @settings['intervals']['watchdog'] = interval
259
302
  end
260
303
 
261
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
+
262
313
  # store sxl version requested by site
263
314
  # TODO should check agaist site settings
264
315
  @site_sxl_version = message.attribute 'SXL'
@@ -276,6 +327,8 @@ module RSMP
276
327
  check_rsmp_version message
277
328
  check_sxl_version message
278
329
  version_accepted message
330
+ rescue RSMP::Schemer::UnknownSchemaError => e
331
+ dont_acknowledge message, "Rejected #{message.type} message,", "#{e}"
279
332
  end
280
333
 
281
334
  def check_site_ids message
@@ -283,9 +336,38 @@ module RSMP
283
336
  site_id = message.attribute("siteId").map { |item| item["sId"] }.first
284
337
  @supervisor.check_site_id site_id
285
338
  @site_id = site_id
339
+ setup_site_settings
286
340
  site_ids_changed
287
341
  end
288
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
289
371
 
290
372
  end
291
373
  end