rsmp 0.1.17 → 0.1.19

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.
@@ -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