rsmp 0.1.13 → 0.1.29

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,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