rsmp 0.1.12 → 0.1.27

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 = []
@@ -26,8 +26,9 @@ module RSMP
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_version' => '1.0.7',
29
+ 'rsmp_versions' => ['3.1.1','3.1.2','3.1.3','3.1.4','3.1.5'],
30
+ 'sxl' => 'tlc',
31
+ 'sxl_version' => '1.0.15',
31
32
  'timer_interval' => 0.1,
32
33
  'watchdog_interval' => 1,
33
34
  'watchdog_timeout' => 2,
@@ -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
@@ -50,9 +54,17 @@ module RSMP
50
54
  :acknowledgement_timeout,:command_response_timeout]
51
55
  check_required_settings @site_settings, required
52
56
 
57
+ check_sxl_version
58
+
53
59
  setup_components @site_settings['components']
54
60
  end
55
61
 
62
+ def check_sxl_version
63
+ sxl = @site_settings['sxl']
64
+ version = @site_settings['sxl_version']
65
+ RSMP::Schemer::find_schema! sxl, version
66
+ end
67
+
56
68
  def reconnect
57
69
  @sleep_condition.signal
58
70
  end
@@ -60,13 +72,15 @@ module RSMP
60
72
  def start_action
61
73
  @site_settings["supervisors"].each do |supervisor_settings|
62
74
  @task.async do |task|
63
- task.annotate "site_proxy"
75
+ task.annotate "site proxy"
64
76
  connect_to_supervisor task, supervisor_settings
77
+ rescue StandardError => e
78
+ notify_error e, level: :internal
65
79
  end
66
80
  end
67
81
  end
68
82
 
69
- def build_connector settings
83
+ def build_proxy settings
70
84
  SupervisorProxy.new settings
71
85
  end
72
86
 
@@ -77,7 +91,7 @@ module RSMP
77
91
  end
78
92
 
79
93
  def connect_to_supervisor task, supervisor_settings
80
- proxy = build_connector({
94
+ proxy = build_proxy({
81
95
  site: self,
82
96
  task: @task,
83
97
  settings: @site_settings,
@@ -97,16 +111,16 @@ module RSMP
97
111
  proxy.run # run until disconnected
98
112
  rescue IOError => e
99
113
  log "Stream error: #{e}", level: :warning
100
- rescue SystemCallError => e # all ERRNO errors
101
- log "Reader exception: #{e.to_s}", level: :error
102
114
  rescue StandardError => e
103
- log ["Reader exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
115
+ notify_error e, level: :internal
104
116
  ensure
105
117
  begin
106
118
  if @site_settings["reconnect_interval"] != :no
107
119
  # sleep until waken by reconnect() or the reconnect interval passed
108
120
  proxy.set_state :wait_for_reconnect
109
- task.with_timeout(@site_settings["reconnect_interval"]) { @sleep_condition.wait }
121
+ task.with_timeout(@site_settings["reconnect_interval"]) do
122
+ @sleep_condition.wait
123
+ end
110
124
  else
111
125
  proxy.set_state :cannot_connect
112
126
  break
@@ -129,7 +143,7 @@ module RSMP
129
143
  def starting
130
144
  log "Starting site #{@site_settings["site_id"]}",
131
145
  level: :info,
132
- timestamp: RSMP.now_object
146
+ timestamp: @clock.now
133
147
  end
134
148
 
135
149
  def alarm
@@ -137,5 +151,6 @@ module RSMP
137
151
  proxy.stop
138
152
  end
139
153
  end
154
+
140
155
  end
141
156
  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 #{@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,20 +63,56 @@ module RSMP
50
63
  end
51
64
  end
52
65
 
53
- def version_accepted message
54
- log "Received Version message for site #{@site_id} using RSMP #{@rsmp_version}", message: message, level: :log
55
- start_timer
66
+ def process_command_response message
67
+ log "Received #{message.type}", message: message, level: :log
56
68
  acknowledge message
57
- send_version @site_id, @rsmp_version
58
- @version_determined = true
69
+ end
59
70
 
71
+ def process_deferred
72
+ supervisor.process_deferred
73
+ end
74
+
75
+ def version_accepted message
60
76
  if @settings['sites']
61
77
  @site_settings = @settings['sites'][@site_id]
62
- @site_settings =@settings['sites'][:any] unless @site_settings
78
+ @site_settings = @settings['sites'][:any] unless @site_settings
63
79
  if @site_settings
80
+ @sxl = @site_settings['sxl']
64
81
  setup_components @site_settings['components']
82
+ else
83
+ dont_acknowledge message, 'Rejected', "No config found for site #{@site_id}"
65
84
  end
66
85
  end
86
+
87
+ log "Received Version message for site #{@site_id}", message: message, level: :log
88
+ start_timer
89
+ acknowledge message
90
+ send_version @site_id, @settings['rsmp_versions']
91
+ @version_determined = true
92
+
93
+ end
94
+
95
+ def request_aggregated_status component, options={}
96
+ raise NotReady unless ready?
97
+ m_id = options[:m_id] || RSMP::Message.make_m_id
98
+
99
+ message = RSMP::AggregatedStatusRequest.new({
100
+ "ntsOId" => '',
101
+ "xNId" => '',
102
+ "cId" => component,
103
+ "mId" => m_id
104
+ })
105
+ if options[:collect]
106
+ result = nil
107
+ task = @task.async do |task|
108
+ wait_for_aggregated_status task, options[:collect]
109
+ end
110
+ send_message message, validate: options[:validate]
111
+ return message, task.wait
112
+ else
113
+ send_message message, validate: options[:validate]
114
+ message
115
+ end
67
116
  end
68
117
 
69
118
  def validate_aggregated_status message, se
@@ -81,7 +130,7 @@ module RSMP
81
130
  component = @components[c_id]
82
131
  if component == nil
83
132
  if @site_settings == nil || @site_settings['components'] == nil
84
- component = build_component c_id
133
+ component = build_component(id:c_id, type:nil)
85
134
  @components[c_id] = component
86
135
  log "Adding component #{c_id} to site #{@site_id}", level: :info
87
136
  else
@@ -123,16 +172,39 @@ module RSMP
123
172
  @supervisor.site_ids_changed
124
173
  end
125
174
 
126
- def request_status component, status_list, timeout=nil
127
- raise NotReady unless @state == :ready
175
+ def request_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 request
181
+ request_list = status_list.map { |item| item.slice('sCI','n') }
182
+
128
183
  message = RSMP::StatusRequest.new({
129
184
  "ntsOId" => '',
130
185
  "xNId" => '',
131
186
  "cId" => component,
132
- "sS" => status_list
187
+ "sS" => request_list,
188
+ "mId" => m_id
133
189
  })
134
- send_message message
135
- return message, wait_for_status_response(message: message, 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_responses task, collect_options, m_id
195
+ end
196
+ send_message message, validate: options[:validate]
197
+
198
+ # task.wait return the result of the task. if the task raised an exception
199
+ # it will be reraised. but that mechanish does not work if multiple values
200
+ # are returned. so manually raise if first element is an exception
201
+ result = task.wait
202
+ raise result.first if result.first.is_a? Exception
203
+ return message, *result
204
+ else
205
+ send_message message, validate: options[:validate]
206
+ message
207
+ end
136
208
  end
137
209
 
138
210
  def process_status_response message
@@ -140,43 +212,50 @@ module RSMP
140
212
  acknowledge message
141
213
  end
142
214
 
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
215
+ def subscribe_to_status component, status_list, options={}
216
+ raise NotReady unless ready?
217
+ m_id = options[:m_id] || RSMP::Message.make_m_id
218
+
219
+ # additional items can be used when verifying the response,
220
+ # but must to remove from the subscribe message
221
+ subscribe_list = status_list.map { |item| item.slice('sCI','n','uRt') }
158
222
 
159
- def subscribe_to_status component, status_list, timeout
160
- raise NotReady unless @state == :ready
161
223
  message = RSMP::StatusSubscribe.new({
162
224
  "ntsOId" => '',
163
225
  "xNId" => '',
164
226
  "cId" => component,
165
- "sS" => status_list
227
+ "sS" => subscribe_list,
228
+ 'mId' => m_id
166
229
  })
167
- send_message message
168
- return message, wait_for_status_update(component: component, timeout: timeout)
230
+ if options[:collect]
231
+ result = nil
232
+ task = @task.async do |task|
233
+ collect_options = options[:collect].merge status_list: status_list
234
+ collect_status_updates task, collect_options, m_id
235
+ end
236
+ send_message message, validate: options[:validate]
237
+
238
+ # task.wait return the result of the task. if the task raised an exception
239
+ # it will be reraised. but that mechanish does not work if multiple values
240
+ # are returned. so manually raise if first element is an exception
241
+ result = task.wait
242
+ raise result.first if result.first.is_a? Exception
243
+ return message, *result
244
+ else
245
+ send_message message, validate: options[:validate]
246
+ message
247
+ end
169
248
  end
170
249
 
171
- def unsubscribe_to_status component, status_list
172
- raise NotReady unless @state == :ready
250
+ def unsubscribe_to_status component, status_list, options={}
251
+ raise NotReady unless ready?
173
252
  message = RSMP::StatusUnsubscribe.new({
174
253
  "ntsOId" => '',
175
254
  "xNId" => '',
176
255
  "cId" => component,
177
256
  "sS" => status_list
178
257
  })
179
- send_message message
258
+ send_message message, validate: options[:validate]
180
259
  message
181
260
  end
182
261
 
@@ -185,48 +264,48 @@ module RSMP
185
264
  acknowledge message
186
265
  end
187
266
 
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
- next if options[:sCI] && options[:sCI] != status['sCI']
196
- next if options[:n] && options[:n] != status['n']
197
- next if options[:q] && options[:q] != status['q']
198
- next 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({
267
+ def send_alarm_acknowledgement component, alarm_code, options={}
268
+ message = RSMP::AlarmAcknowledged.new({
210
269
  "ntsOId" => '',
211
270
  "xNId" => '',
212
271
  "cId" => component,
213
- "arg" => args
272
+ "aCId" => alarm_code,
273
+ "xACId" => '',
274
+ "xNACId" => '',
275
+ "aSp" => 'Acknowledge'
214
276
  })
215
- send_message message
277
+ send_message message, validate: options[:validate]
216
278
  message
217
279
  end
218
280
 
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
281
+ def send_command component, command_list, options={}
282
+ raise NotReady unless ready?
283
+ m_id = options[:m_id] || RSMP::Message.make_m_id
284
+ message = RSMP::CommandRequest.new({
285
+ "ntsOId" => '',
286
+ "xNId" => '',
287
+ "cId" => component,
288
+ "arg" => command_list,
289
+ "mId" => m_id
290
+ })
291
+ if options[:collect]
292
+ result = nil
293
+ task = @task.async do |task|
294
+ collect_options = options[:collect].merge command_list: command_list
295
+ collect_command_responses task, collect_options, m_id
296
+ end
297
+ send_message message, validate: options[:validate]
298
+
299
+ # task.wait return the result of the task. if the task raised an exception
300
+ # it will be reraised. but that mechanish does not work if multiple values
301
+ # are returned. so manually raise if first element is an exception
302
+ result = task.wait
303
+ raise result.first if result.first.is_a? Exception
304
+ return message, *result
305
+ else
306
+ send_message message, validate: options[:validate]
307
+ message
228
308
  end
229
- item[:message] if item
230
309
  end
231
310
 
232
311
  def set_watchdog_interval interval
@@ -234,6 +313,14 @@ module RSMP
234
313
  end
235
314
 
236
315
  def check_sxl_version message
316
+
317
+ # check that we have a schema for specified sxl type and version
318
+ # note that the type comes from the site config, while the version
319
+ # comes from the Version message send by the site
320
+ type = 'tlc'
321
+ version = message.attribute 'SXL'
322
+ RSMP::Schemer::find_schema! type, version
323
+
237
324
  # store sxl version requested by site
238
325
  # TODO should check agaist site settings
239
326
  @site_sxl_version = message.attribute 'SXL'
@@ -251,6 +338,8 @@ module RSMP
251
338
  check_rsmp_version message
252
339
  check_sxl_version message
253
340
  version_accepted message
341
+ rescue RSMP::Schemer::UnknownSchemaError => e
342
+ dont_acknowledge message, "Rejected #{message.type} message,", "#{e}"
254
343
  end
255
344
 
256
345
  def check_site_ids message
@@ -261,6 +350,9 @@ module RSMP
261
350
  site_ids_changed
262
351
  end
263
352
 
353
+ def notify_error e, options={}
354
+ @supervisor.notify_error e, options if @supervisor
355
+ end
264
356
 
265
357
  end
266
- end
358
+ end