rsmp 0.1.17 → 0.1.19

Sign up to get free protection for your applications and to get access to all the features.
@@ -110,7 +110,9 @@ module RSMP
110
110
  if @site_settings["reconnect_interval"] != :no
111
111
  # sleep until waken by reconnect() or the reconnect interval passed
112
112
  proxy.set_state :wait_for_reconnect
113
- task.with_timeout(@site_settings["reconnect_interval"]) { @sleep_condition.wait }
113
+ task.with_timeout(@site_settings["reconnect_interval"]) do
114
+ @sleep_condition.wait
115
+ end
114
116
  else
115
117
  proxy.set_state :cannot_connect
116
118
  break
@@ -141,5 +143,6 @@ module RSMP
141
143
  proxy.stop
142
144
  end
143
145
  end
146
+
144
147
  end
145
148
  end
@@ -23,6 +23,11 @@ module RSMP
23
23
  start_reader
24
24
  end
25
25
 
26
+ def stop
27
+ log "Closing connection to site", level: :info
28
+ super
29
+ end
30
+
26
31
  def connection_complete
27
32
  super
28
33
  log "Connection to site #{@site_id} established", level: :info
@@ -50,6 +55,10 @@ module RSMP
50
55
  end
51
56
  end
52
57
 
58
+ def process_deferred
59
+ supervisor.process_deferred
60
+ end
61
+
53
62
  def version_accepted message
54
63
  log "Received Version message for site #{@site_id} using RSMP #{@rsmp_version}", message: message, level: :log
55
64
  start_timer
@@ -123,16 +132,38 @@ module RSMP
123
132
  @supervisor.site_ids_changed
124
133
  end
125
134
 
126
- def request_status component, status_list, timeout=nil
127
- raise NotReady unless @state == :ready
135
+ def fetch_status parent_task, options
136
+ wait_for_status_responses(parent_task,options) do |m_id|
137
+ request_status options.merge(m_id: m_id)
138
+ end
139
+ end
140
+
141
+ # Convert from a short ruby hash:
142
+ # {:S0001=>[:signalgroupstatus, :cyclecounter, :basecyclecounter, :stage]}
143
+ # to an rsmp-style list:
144
+ # [{"sCI"=>"S0001", "n"=>"signalgroupstatus"}, {"sCI"=>"S0001", "n"=>"cyclecounter"}, {"sCI"=>"S0001", "n"=>"basecyclecounter"}, {"sCI"=>"S0001", "n"=>"stage"}]
145
+ #
146
+ # If the input is already an array, just return it
147
+ def convert_status_list list
148
+ return list.clone if list.is_a? Array
149
+ list.map do |status_code_id,names|
150
+ names.map do |name|
151
+ { 'sCI' => status_code_id.to_s, 'n' => name.to_s }
152
+ end
153
+ end.flatten
154
+ end
155
+
156
+ def request_status options
157
+ raise NotReady unless ready?
128
158
  message = RSMP::StatusRequest.new({
129
159
  "ntsOId" => '',
130
160
  "xNId" => '',
131
- "cId" => component,
132
- "sS" => status_list
161
+ "cId" => options[:component],
162
+ "sS" => convert_status_list(options[:status_list]),
163
+ "mId" => options[:m_id]
133
164
  })
134
165
  send_message message
135
- return message, wait_for_status_response(message: message, timeout: timeout)
166
+ message
136
167
  end
137
168
 
138
169
  def process_status_response message
@@ -140,41 +171,26 @@ module RSMP
140
171
  acknowledge message
141
172
  end
142
173
 
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
158
-
159
- def subscribe_to_status component, status_list, timeout
160
- raise NotReady unless @state == :ready
174
+ def subscribe_to_status component, status_list, options={}
175
+ raise NotReady unless ready?
161
176
  message = RSMP::StatusSubscribe.new({
162
177
  "ntsOId" => '',
163
178
  "xNId" => '',
164
179
  "cId" => component,
165
- "sS" => status_list
180
+ "sS" => convert_status_list(status_list),
181
+ 'mId'=>options[:m_id]
166
182
  })
167
183
  send_message message
168
- return message, wait_for_status_update(component: component, timeout: timeout)[:message]
184
+ return message
169
185
  end
170
186
 
171
187
  def unsubscribe_to_status component, status_list
172
- raise NotReady unless @state == :ready
188
+ raise NotReady unless ready?
173
189
  message = RSMP::StatusUnsubscribe.new({
174
190
  "ntsOId" => '',
175
191
  "xNId" => '',
176
192
  "cId" => component,
177
- "sS" => status_list
193
+ "sS" => convert_status_list(status_list)
178
194
  })
179
195
  send_message message
180
196
  message
@@ -185,34 +201,20 @@ module RSMP
185
201
  acknowledge message
186
202
  end
187
203
 
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 }
204
+ def status_match? query, item
205
+ return false if query[:sCI] && query[:sCI] != item['sCI']
206
+ return false if query[:n] && query[:n] != item['n']
207
+ return false if query[:q] && query[:q] != item['q']
208
+ if query[:s].is_a? Regexp
209
+ return false if query[:s] && item['s'] !~ query[:s]
210
+ else
211
+ return false if query[:s] && item['s'] != query[:s]
211
212
  end
213
+ true
212
214
  end
213
215
 
214
216
  def wait_for_alarm options={}
215
- raise ArgumentError unless options[:component]
217
+ raise ArgumentError.new("component argument is missing") unless options[:component]
216
218
  matching_alarm = nil
217
219
  item = @archive.capture(@task,options.merge(type: "Alarm", with_message: true, num: 1)) do |item|
218
220
  # TODO check components
@@ -229,31 +231,49 @@ module RSMP
229
231
  end
230
232
  end
231
233
 
232
- def send_command component, args
233
- raise NotReady unless @state == :ready
234
- message = RSMP::CommandRequest.new({
234
+ def send_alarm_acknowledgement component, alarm_code
235
+ message = RSMP::AlarmAcknowledged.new({
235
236
  "ntsOId" => '',
236
237
  "xNId" => '',
237
238
  "cId" => component,
238
- "arg" => args
239
+ "aCId" => alarm_code,
240
+ "xACId" => '',
241
+ "xNACId" => '',
242
+ "aSp" => 'Acknowledge'
239
243
  })
240
244
  send_message message
241
245
  message
242
246
  end
243
247
 
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
248
+ def wait_for_alarm_acknowledgement_response options
249
+ raise ArgumentError.new("component argument is missing") unless options[:component]
250
+ item = @archive.capture(@task,options.merge(
251
+ num: 1,
252
+ type: ['AlarmAcknowledgedResponse','MessageNotAck'],
253
+ with_message: true
254
+ )) do |item|
255
+ if item[:message].type == 'MessageNotAck'
256
+ next item[:message].attribute('oMId') == options[:message].m_id
257
+ elsif item[:message].type == 'AlarmAcknowledgedResponse'
258
+ next item[:message].attribute('cId') == options[:message].attribute('cId')
259
+ end
253
260
  end
254
261
  item[:message] if item
255
262
  end
256
263
 
264
+ def send_command component, args, options={}
265
+ raise NotReady unless ready?
266
+ message = RSMP::CommandRequest.new({
267
+ "ntsOId" => '',
268
+ "xNId" => '',
269
+ "cId" => component,
270
+ "arg" => args,
271
+ "mId" => options[:m_id]
272
+ })
273
+ send_message message
274
+ message
275
+ end
276
+
257
277
  def set_watchdog_interval interval
258
278
  @settings["watchdog_interval"] = interval
259
279
  end
@@ -286,6 +306,5 @@ module RSMP
286
306
  site_ids_changed
287
307
  end
288
308
 
289
-
290
309
  end
291
310
  end
@@ -129,6 +129,7 @@ module RSMP
129
129
  @proxies.push proxy
130
130
 
131
131
  proxy.run # will run until the site disconnects
132
+ ensure
132
133
  @proxies.delete proxy
133
134
  site_ids_changed
134
135
 
@@ -167,13 +168,13 @@ module RSMP
167
168
  def wait_for_site site_id, timeout
168
169
  site = find_site site_id
169
170
  return site if site
170
- RSMP::Wait.wait_for(@task,@site_id_condition,timeout) { find_site site_id }
171
+ wait_for(@site_id_condition,timeout) { find_site site_id }
171
172
  rescue Async::TimeoutError
172
173
  nil
173
174
  end
174
175
 
175
176
  def wait_for_site_disconnect site_id, timeout
176
- RSMP::Wait.wait_for(@task,@site_id_condition,timeout) { true unless find_site site_id }
177
+ wait_for(@site_id_condition,timeout) { true unless find_site site_id }
177
178
  rescue Async::TimeoutError
178
179
  false
179
180
  end
@@ -28,6 +28,7 @@ module RSMP
28
28
  super
29
29
  connect
30
30
  @logger.unmute @ip, @port
31
+ log "Connected to superviser at #{@ip}:#{@port}", level: :info
31
32
  start_reader
32
33
  send_version @site_settings['site_id'], @site_settings["rsmp_versions"]
33
34
  rescue Errno::ECONNREFUSED
@@ -38,6 +39,12 @@ module RSMP
38
39
  end
39
40
  end
40
41
 
42
+ def stop
43
+ log "Closing connection to supervisor", level: :info
44
+ super
45
+ @last_status_sent = nil
46
+ end
47
+
41
48
  def connect
42
49
  return if @socket
43
50
  @endpoint = Async::IO::Endpoint.tcp(@ip, @port)
@@ -72,14 +79,15 @@ module RSMP
72
79
  else
73
80
  super message
74
81
  end
75
- rescue UnknownComponent => e
76
- dont_acknowledge message, '', e.to_s
77
- rescue UnknownCommand => e
78
- dont_acknowledge message, '', e.to_s
79
- rescue UnknownStatus => e
82
+ rescue UnknownComponent, UnknownCommand, UnknownStatus,
83
+ MessageRejected, MissingAttribute => e
80
84
  dont_acknowledge message, '', e.to_s
81
85
  end
82
86
 
87
+ def process_deferred
88
+ site.process_deferred
89
+ end
90
+
83
91
  def acknowledged_first_ingoing message
84
92
  # TODO
85
93
  # aggregateds status should only be send for later version of rsmp
@@ -118,7 +126,7 @@ module RSMP
118
126
  message = AggregatedStatus.new({
119
127
  "aSTS" => RSMP.now_string,
120
128
  "cId" => component.c_id,
121
- "fP" => nil,
129
+ "fP" => 'NormalControl',
122
130
  "fS" => nil,
123
131
  "se" => component.aggregated_status_bools
124
132
  })
@@ -177,7 +185,7 @@ module RSMP
177
185
  send_message response
178
186
  end
179
187
 
180
- def process_status_request message
188
+ def process_status_request message, options={}
181
189
  component_id = message.attributes["cId"]
182
190
  component = @site.find_component component_id
183
191
  log "Received #{message.type}", message: message, level: :log
@@ -188,7 +196,8 @@ module RSMP
188
196
  response = StatusResponse.new({
189
197
  "cId"=>component_id,
190
198
  "sTs"=>RSMP.now_string,
191
- "sS"=>sS
199
+ "sS"=>sS,
200
+ "mId" => options[:m_id]
192
201
  })
193
202
  acknowledge message
194
203
  send_message response
@@ -213,10 +222,11 @@ module RSMP
213
222
  update_list[component] ||= {}
214
223
 
215
224
  subs = @status_subscriptions[component]
225
+ now = RSMP::now_object
216
226
 
217
227
  message.attributes["sS"].each do |arg|
218
228
  sCI = arg["sCI"]
219
- subcription = {interval: arg["uRt"].to_i, last_sent_at: nil}
229
+ subcription = {interval: arg["uRt"].to_i, last_sent_at: now}
220
230
  subs[sCI] ||= {}
221
231
  subs[sCI][arg["n"]] = subcription
222
232
 
@@ -250,18 +260,40 @@ module RSMP
250
260
  status_update_timer now if ready?
251
261
  end
252
262
 
263
+ def fetch_last_sent_status component, code, name
264
+ if @last_status_sent && @last_status_sent[component] && @last_status_sent[component][code]
265
+ @last_status_sent[component][code][name]
266
+ else
267
+ nil
268
+ end
269
+ end
270
+
271
+ def store_last_sent_status component, code, name, value
272
+ @last_status_sent ||= {}
273
+ @last_status_sent[component] ||= {}
274
+ @last_status_sent[component][code] ||= {}
275
+ @last_status_sent[component][code][name] = value
276
+ end
277
+
253
278
  def status_update_timer now
254
279
  update_list = {}
255
280
  # go through subscriptons and build a similarly organized list,
256
281
  # that only contains what should be send
257
282
 
258
283
  @status_subscriptions.each_pair do |component,by_code|
284
+ component_object = @site.find_component component
259
285
  by_code.each_pair do |code,by_name|
260
286
  by_name.each_pair do |name,subscription|
287
+ current = nil
261
288
  if subscription[:interval] == 0
262
289
  # send as soon as the data changes
263
- if rand(100) >= 90
290
+ if component_object
291
+ current, age = *(component_object.get_status code, name)
292
+ end
293
+ last_sent = fetch_last_sent_status component, code, name
294
+ if current != last_sent
264
295
  should_send = true
296
+ store_last_sent_status component, code, name, current
265
297
  end
266
298
  else
267
299
  # send at regular intervals
@@ -272,8 +304,8 @@ module RSMP
272
304
  if should_send
273
305
  subscription[:last_sent_at] = now
274
306
  update_list[component] ||= {}
275
- update_list[component][code] ||= []
276
- update_list[component][code] << name
307
+ update_list[component][code] ||= {}
308
+ update_list[component][code][name] = current
277
309
  end
278
310
  end
279
311
  end
@@ -289,8 +321,12 @@ module RSMP
289
321
  component = @site.find_component component_id
290
322
  sS = []
291
323
  by_code.each_pair do |code,names|
292
- names.map do |status_name|
293
- value,quality = component.get_status code, status_name
324
+ names.map do |status_name,value|
325
+ if value
326
+ quality = 'recent'
327
+ else
328
+ value,quality = component.get_status code, status_name
329
+ end
294
330
  sS << { "sCI" => code,
295
331
  "n" => status_name,
296
332
  "s" => value.to_s,
@@ -4,13 +4,21 @@ module RSMP
4
4
 
5
5
  class TrafficController < Component
6
6
  attr_reader :pos, :cycle_time
7
+
7
8
  def initialize node:, id:, cycle_time:
8
9
  super node: node, id: id, grouped: true
9
10
  @signal_groups = []
10
11
  @detector_logics = []
11
12
  @plans = []
12
- @pos = 0
13
13
  @cycle_time = cycle_time
14
+ @num_traffic_situations = 1
15
+ @num_inputs = 8
16
+
17
+ reset
18
+ end
19
+
20
+ def reset
21
+ @pos = 0
14
22
  @plan = 0
15
23
  @dark_mode = false
16
24
  @yellow_flash = false
@@ -22,14 +30,12 @@ module RSMP
22
30
  @emergency_route = false
23
31
  @emergency_route_number = 0
24
32
  @traffic_situation = 0
25
- @num_traffic_situations = 1
26
33
  @manual_control = false
27
34
  @fixed_time_control = false
28
35
  @isolated_control = false
29
36
  @yellow_flash = false
30
37
  @all_red = false
31
38
 
32
- @num_inputs = 8
33
39
  @inputs = '0'*@num_inputs
34
40
  @input_activations = '0'*@num_inputs
35
41
  @input_results = '0'*@num_inputs
@@ -79,32 +85,24 @@ module RSMP
79
85
 
80
86
  def handle_command command_code, arg
81
87
  case command_code
82
- when 'M0001'
83
- handle_m0001 arg
84
- when 'M0002'
85
- handle_m0002 arg
86
- when 'M0003'
87
- handle_m0003 arg
88
- when 'M0004'
89
- handle_m0004 arg
90
- when 'M0005'
91
- handle_m0005 arg
92
- when 'M0006'
93
- handle_m0006 arg
94
- when 'M0007'
95
- handle_m0007 arg
88
+ when 'M0001', 'M0002', 'M0003', 'M0004', 'M0005', 'M0006', 'M0007',
89
+ 'M0012', 'M0013', 'M0014', 'M0015', 'M0016', 'M0017', 'M0018',
90
+ 'M0019', 'M0020', 'M0021', 'M0022',
91
+ 'M0103', 'M0104'
92
+
93
+ return send("handle_#{command_code.downcase}", arg)
96
94
  else
97
95
  raise UnknownCommand.new "Unknown command #{command_code}"
98
96
  end
99
97
  end
100
98
 
101
99
  def handle_m0001 arg
102
- @node.verify_security_code arg['securityCode']
100
+ @node.verify_security_code 2, arg['securityCode']
103
101
  switch_mode arg['status']
104
102
  end
105
103
 
106
104
  def handle_m0002 arg
107
- @node.verify_security_code arg['securityCode']
105
+ @node.verify_security_code 2, arg['securityCode']
108
106
  if RSMP::Tlc.from_rsmp_bool(arg['status'])
109
107
  switch_plan arg['timeplan']
110
108
  else
@@ -113,32 +111,86 @@ module RSMP
113
111
  end
114
112
 
115
113
  def handle_m0003 arg
116
- @node.verify_security_code arg['securityCode']
114
+ @node.verify_security_code 2, arg['securityCode']
117
115
  @traffic_situation = arg['traficsituation'].to_i
118
116
  end
119
117
 
120
118
  def handle_m0004 arg
121
- @node.verify_security_code arg['securityCode']
122
- # restarting the node means we will disconnect and reconnect.
123
- # note that this will happen immediately, and no
124
- # command response will therefore be sent
125
- log "Restarting TLC", level: :info
126
- @node.restart
119
+ @node.verify_security_code 2, arg['securityCode']
120
+ # don't restart immeediately, since we need to first send command response
121
+ # instead, defer an action, which will be handled by the TLC site
122
+ log "Sheduling restart of TLC", level: :info
123
+ @node.defer :restart
127
124
  end
128
125
 
129
126
  def handle_m0005 arg
130
- @node.verify_security_code arg['securityCode']
127
+ @node.verify_security_code 2, arg['securityCode']
131
128
  @emergency_route = arg['status'] == 'True'
132
129
  @emergency_route_number = arg['emergencyroute'].to_i
133
130
  end
134
131
 
135
132
  def handle_m0006 arg
136
- @node.verify_security_code arg['securityCode']
133
+ @node.verify_security_code 2, arg['securityCode']
137
134
  input = arg['input'].to_i
138
- return unless input>=0 && input<@num_inputs
139
- @input_activations[input] = (arg['status']=='True' ? '1' : '0')
140
- result = @input_activations[input]=='1' || @inputs[input]=='1'
141
- @input_results[input] = (result ? '1' : '0')
135
+ idx = input - 1
136
+ return unless idx>=0 && input<@num_inputs
137
+ @input_activations[idx] = (arg['status']=='True' ? '1' : '0')
138
+ result = @input_activations[idx]=='1' || @inputs[idx]=='1'
139
+ @input_results[idx] = (result ? '1' : '0')
140
+ end
141
+
142
+ def handle_m0007 arg
143
+ @node.verify_security_code 2, arg['securityCode']
144
+ set_fixed_time_control arg['status']
145
+ end
146
+
147
+ def handle_m0012 arg
148
+ @node.verify_security_code 2, arg['securityCode']
149
+ end
150
+
151
+ def handle_m0013 arg
152
+ @node.verify_security_code 2, arg['securityCode']
153
+ end
154
+
155
+ def handle_m0014 arg
156
+ @node.verify_security_code 2, arg['securityCode']
157
+ end
158
+
159
+ def handle_m0015 arg
160
+ @node.verify_security_code 2, arg['securityCode']
161
+ end
162
+
163
+ def handle_m0016 arg
164
+ @node.verify_security_code 2, arg['securityCode']
165
+ end
166
+
167
+ def handle_m0017 arg
168
+ @node.verify_security_code 2, arg['securityCode']
169
+ end
170
+
171
+ def handle_m0018 arg
172
+ @node.verify_security_code 2, arg['securityCode']
173
+ end
174
+
175
+ def handle_m0019 arg
176
+ @node.verify_security_code 2, arg['securityCode']
177
+ end
178
+
179
+ def handle_m0020 arg
180
+ @node.verify_security_code 2, arg['securityCode']
181
+ end
182
+
183
+ def handle_m0021 arg
184
+ @node.verify_security_code 2, arg['securityCode']
185
+ end
186
+
187
+ def handle_m0103 arg
188
+ level = {'Level1'=>1,'Level2'=>2}[arg['status']]
189
+ @node.change_security_code level, arg['oldSecurityCode'], arg['newSecurityCode']
190
+ end
191
+
192
+ def handle_m0104 arg
193
+ @node.verify_security_code 1, arg['securityCode']
142
194
  end
143
195
 
144
196
  def set_input i, value
@@ -146,11 +198,6 @@ module RSMP
146
198
  @inputs[i] = (arg['value'] ? '1' : '0')
147
199
  end
148
200
 
149
- def handle_m0007 arg
150
- @node.verify_security_code arg['securityCode']
151
- set_fixed_time_control arg['status']
152
- end
153
-
154
201
  def set_fixed_time_control status
155
202
  @fixed_time_control = status
156
203
  end
@@ -182,8 +229,8 @@ module RSMP
182
229
  'S0008', 'S0009', 'S0010', 'S0011', 'S0012', 'S0013', 'S0014',
183
230
  'S0015', 'S0016', 'S0017', 'S0018', 'S0019', 'S0020', 'S0021',
184
231
  'S0022', 'S0023', 'S0024', 'S0026', 'S0027', 'S0028',
185
- 'S0029',
186
- 'S0091', 'S0092', 'S0095', 'S0096',
232
+ 'S0029', 'S0030', 'S0031',
233
+ 'S0091', 'S0092', 'S0095', 'S0096', 'S0097',
187
234
  'S0205', 'S0206', 'S0207', 'S0208'
188
235
  return send("handle_#{code.downcase}", code, name)
189
236
  else
@@ -194,7 +241,7 @@ module RSMP
194
241
  def handle_s0001 status_code, status_name=nil
195
242
  case status_name
196
243
  when 'signalgroupstatus'
197
- return RSMP::Tlc.make_status format_signal_group_status
244
+ RSMP::Tlc.make_status format_signal_group_status
198
245
  when 'cyclecounter'
199
246
  RSMP::Tlc.make_status @pos.to_s
200
247
  when 'basecyclecounter'
@@ -415,6 +462,20 @@ module RSMP
415
462
  end
416
463
  end
417
464
 
465
+ def handle_s0030 status_code, status_name=nil
466
+ case status_name
467
+ when 'status'
468
+ RSMP::Tlc.make_status ''
469
+ end
470
+ end
471
+
472
+ def handle_s0031 status_code, status_name=nil
473
+ case status_name
474
+ when 'status'
475
+ RSMP::Tlc.make_status ''
476
+ end
477
+ end
478
+
418
479
  def handle_s0091 status_code, status_name=nil
419
480
  case status_name
420
481
  when 'user'
@@ -457,6 +518,15 @@ module RSMP
457
518
  end
458
519
  end
459
520
 
521
+ def handle_s0097 status_code, status_name=nil
522
+ case status_name
523
+ when 'version'
524
+ RSMP::Tlc.make_status '1'
525
+ when 'hash'
526
+ RSMP::Tlc.make_status '1'
527
+ end
528
+ end
529
+
460
530
  def handle_s0205 status_code, status_name=nil
461
531
  case status_name
462
532
  when 'start'
@@ -532,6 +602,31 @@ module RSMP
532
602
  @state = get_state pos
533
603
  end
534
604
 
605
+ def handle_command command_code, arg
606
+ case command_code
607
+ when 'M0010', 'M0011'
608
+ return send("handle_#{command_code.downcase}", arg)
609
+ else
610
+ raise UnknownCommand.new "Unknown command #{command_code}"
611
+ end
612
+ end
613
+
614
+ # Start of signal group. Orders a signal group to green
615
+ def handle_m0010 arg
616
+ @node.verify_security_code 2, arg['securityCode']
617
+ if RSMP::Tlc.from_rsmp_bool arg['status']
618
+ log "Start signal group #{c_id}, go to green", level: :info
619
+ end
620
+ end
621
+
622
+ # Stop of signal group. Orders a signal group to red
623
+ def handle_m0011 arg
624
+ @node.verify_security_code 2, arg['securityCode']
625
+ if RSMP::Tlc.from_rsmp_bool arg['status']
626
+ log "Stop signal group #{c_id}, go to red", level: :info
627
+ end
628
+ end
629
+
535
630
  def get_status code, name=nil
536
631
  case code
537
632
  when 'S0025'
@@ -643,7 +738,7 @@ module RSMP
643
738
  end
644
739
 
645
740
  def handle_m0008 arg
646
- @node.verify_security_code arg['securityCode']
741
+ @node.verify_security_code 2, arg['securityCode']
647
742
  force_detector_logic arg['status']=='True', arg['value']='True'
648
743
  arg
649
744
  end
@@ -658,9 +753,9 @@ module RSMP
658
753
  class Tlc < Site
659
754
  def initialize options={}
660
755
  super options
661
-
662
756
  @sxl = 'traffic_light_controller'
663
-
757
+ @security_codes = options[:site_settings]['security_codes']
758
+ @interval = options[:site_settings]['interval'] || 1
664
759
  unless @main
665
760
  raise ConfigurationError.new "TLC must have a main component"
666
761
  end
@@ -687,31 +782,39 @@ module RSMP
687
782
  end
688
783
 
689
784
  def start_timer
690
- name = "tlc timer"
691
- interval = 1 #@settings["timer_interval"] || 1
692
- log "Starting #{name} with interval #{interval} seconds", level: :debug
785
+ task_name = "tlc timer"
786
+ log "Starting #{task_name} with interval #{@interval} seconds", level: :debug
693
787
 
694
788
  @timer = @task.async do |task|
695
- task.annotate "timer"
789
+ task.annotate task_name
696
790
  next_time = Time.now.to_f
697
791
  loop do
698
- now = RSMP.now_object
699
- break if timer(now) == false
700
- rescue StandardError => e
701
- log ["#{name} exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
702
- ensure
703
- # adjust sleep duration to avoid drift. so wake up always happens on the
704
- # same fractional second.
705
- # note that Time.now is not monotonic. If the clock si changed,
706
- # either manaully or via NTP, the sleep interval might jump.
707
- # an alternative is to use ::Process.clock_gettime(::Process::CLOCK_MONOTONIC),
708
- # to get the current time. this ensures a constant interval, but
709
- # if the clock is changed, the wake up would then happen on a different
710
- # fractional second
711
-
712
- next_time += interval
713
- duration = next_time - Time.now.to_f
714
- task.sleep duration
792
+ begin
793
+ now = RSMP.now_object
794
+ timer(now)
795
+ rescue EOFError => e
796
+ log "TLC timer: Connection closed: #{e}", level: :warning
797
+ rescue IOError => e
798
+ log "TLC timer: IOError", level: :warning
799
+ rescue Errno::ECONNRESET
800
+ log "TLC timer: Connection reset by peer", level: :warning
801
+ rescue Errno::EPIPE => e
802
+ log "TLC timer: Broken pipe", level: :warning
803
+ rescue StandardError => e
804
+ log "TLC timer: #{e}", level: :debug
805
+ ensure
806
+ # adjust sleep duration to avoid drift. so wake up always happens on the
807
+ # same fractional second.
808
+ # note that Time.now is not monotonic. If the clock si changed,
809
+ # either manaully or via NTP, the sleep interval might jump.
810
+ # an alternative is to use ::Process.clock_gettime(::Process::CLOCK_MONOTONIC),
811
+ # to get the current time. this ensures a constant interval, but
812
+ # if the clock is changed, the wake up would then happen on a different
813
+ # fractional second
814
+ next_time += @interval
815
+ duration = next_time - Time.now.to_f
816
+ task.sleep duration
817
+ end
715
818
  end
716
819
  end
717
820
  end
@@ -721,7 +824,16 @@ module RSMP
721
824
  @main.timer now
722
825
  end
723
826
 
724
- def verify_security_code code
827
+ def verify_security_code level, code
828
+ raise ArgumentError.new("Level must be 1-2, got #{level}") unless (1..2).include?(level)
829
+ if @security_codes[level] != code
830
+ raise MessageRejected.new("Wrong security code for level #{level}")
831
+ end
832
+ end
833
+
834
+ def change_security_code level, old_code, new_code
835
+ verify_security_code level, old_code
836
+ @security_codes[level] = new_code
725
837
  end
726
838
 
727
839
  def self.to_rmsp_bool bool
@@ -745,5 +857,13 @@ module RSMP
745
857
  end
746
858
  end
747
859
 
860
+ def do_deferred item
861
+ case item
862
+ when :restart
863
+ log "Restarting TLC", level: :info
864
+ restart
865
+ end
866
+ end
867
+
748
868
  end
749
869
  end