rsmp 0.1.12 → 0.1.27

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 = []
@@ -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