rsmp 0.1.13 → 0.1.29

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,42 @@ 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
+ pp @site_settings
49
+ check_sxl_version
57
50
  setup_components @site_settings['components']
58
51
  end
59
52
 
53
+ def check_sxl_version
54
+ sxl = @site_settings['sxl']
55
+ version = @site_settings['sxl_version']
56
+ RSMP::Schemer::find_schema! sxl, version
57
+ end
58
+
60
59
  def reconnect
61
60
  @sleep_condition.signal
62
61
  end
@@ -64,8 +63,10 @@ module RSMP
64
63
  def start_action
65
64
  @site_settings["supervisors"].each do |supervisor_settings|
66
65
  @task.async do |task|
67
- task.annotate "site_proxy"
66
+ task.annotate "site proxy"
68
67
  connect_to_supervisor task, supervisor_settings
68
+ rescue StandardError => e
69
+ notify_error e, level: :internal
69
70
  end
70
71
  end
71
72
  end
@@ -101,16 +102,16 @@ module RSMP
101
102
  proxy.run # run until disconnected
102
103
  rescue IOError => e
103
104
  log "Stream error: #{e}", level: :warning
104
- rescue SystemCallError => e # all ERRNO errors
105
- log "Reader exception: #{e.to_s}", level: :error
106
105
  rescue StandardError => e
107
- log ["Reader exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
106
+ notify_error e, level: :internal
108
107
  ensure
109
108
  begin
110
- if @site_settings["reconnect_interval"] != :no
109
+ if @site_settings['intervals']['watchdog'] != :no
111
110
  # sleep until waken by reconnect() or the reconnect interval passed
112
111
  proxy.set_state :wait_for_reconnect
113
- task.with_timeout(@site_settings["reconnect_interval"]) { @sleep_condition.wait }
112
+ task.with_timeout(@site_settings['intervals']['watchdog']) do
113
+ @sleep_condition.wait
114
+ end
114
115
  else
115
116
  proxy.set_state :cannot_connect
116
117
  break
@@ -133,7 +134,7 @@ module RSMP
133
134
  def starting
134
135
  log "Starting site #{@site_settings["site_id"]}",
135
136
  level: :info,
136
- timestamp: RSMP.now_object
137
+ timestamp: @clock.now
137
138
  end
138
139
 
139
140
  def alarm
@@ -142,13 +143,5 @@ module RSMP
142
143
  end
143
144
  end
144
145
 
145
- def handle_command command_code, arg
146
- raise UnknownCommand.new "Command #{command_code} not implemented"
147
- end
148
-
149
- def get_status status_code, status_name=nil
150
- raise UnknownStatus.new "Status #{status_code}/#{status_name} not implemented"
151
- end
152
-
153
146
  end
154
147
  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
 
@@ -81,7 +119,7 @@ module RSMP
81
119
  component = @components[c_id]
82
120
  if component == nil
83
121
  if @site_settings == nil || @site_settings['components'] == nil
84
- component = build_component c_id
122
+ component = build_component(id:c_id, type:nil)
85
123
  @components[c_id] = component
86
124
  log "Adding component #{c_id} to site #{@site_id}", level: :info
87
125
  else
@@ -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,58 +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
- next if options[:s] && options[:s] != status['s']
200
- matching_status = status
201
- break
202
- end
203
- matching_status != nil
204
- end
205
- if item
206
- { message: item[:message], status: matching_status }
207
- end
208
- end
209
-
210
- def send_command component, args
211
- raise NotReady unless @state == :ready
212
- message = RSMP::CommandRequest.new({
256
+ def send_alarm_acknowledgement component, alarm_code, options={}
257
+ message = RSMP::AlarmAcknowledged.new({
213
258
  "ntsOId" => '',
214
259
  "xNId" => '',
215
260
  "cId" => component,
216
- "arg" => args
261
+ "aCId" => alarm_code,
262
+ "xACId" => '',
263
+ "xNACId" => '',
264
+ "aSp" => 'Acknowledge'
217
265
  })
218
- send_message message
266
+ send_message message, validate: options[:validate]
219
267
  message
220
268
  end
221
269
 
222
- def process_command_response message
223
- log "Received #{message.type}", message: message, level: :log
224
- acknowledge message
225
- end
226
-
227
- def wait_for_command_response options
228
- raise ArgumentError unless options[:component]
229
- item = @archive.capture(@task,options.merge(num: 1, type: "CommandResponse", with_message: true)) do |item|
230
- # 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
231
297
  end
232
- item[:message] if item
233
298
  end
234
299
 
235
300
  def set_watchdog_interval interval
236
- @settings["watchdog_interval"] = interval
301
+ @settings['intervals']['watchdog'] = interval
237
302
  end
238
303
 
239
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
+
240
313
  # store sxl version requested by site
241
314
  # TODO should check agaist site settings
242
315
  @site_sxl_version = message.attribute 'SXL'
@@ -254,6 +327,8 @@ module RSMP
254
327
  check_rsmp_version message
255
328
  check_sxl_version message
256
329
  version_accepted message
330
+ rescue RSMP::Schemer::UnknownSchemaError => e
331
+ dont_acknowledge message, "Rejected #{message.type} message,", "#{e}"
257
332
  end
258
333
 
259
334
  def check_site_ids message
@@ -261,9 +336,38 @@ module RSMP
261
336
  site_id = message.attribute("siteId").map { |item| item["sId"] }.first
262
337
  @supervisor.check_site_id site_id
263
338
  @site_id = site_id
339
+ setup_site_settings
264
340
  site_ids_changed
265
341
  end
266
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
267
371
 
268
372
  end
269
- end
373
+ end