rsmp 0.1.10 → 0.1.19

Sign up to get free protection for your applications and to get access to all the features.
@@ -98,7 +98,7 @@ module RSMP
98
98
  true
99
99
  end
100
100
 
101
- def build_connector settings
101
+ def build_proxy settings
102
102
  SiteProxy.new settings
103
103
  end
104
104
 
@@ -117,7 +117,7 @@ module RSMP
117
117
  level: :info,
118
118
  timestamp: RSMP.now_object
119
119
 
120
- proxy = build_connector({
120
+ proxy = build_proxy({
121
121
  supervisor: self,
122
122
  task: @task,
123
123
  settings: @supervisor_settings[:sites],
@@ -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
 
@@ -159,7 +160,7 @@ module RSMP
159
160
 
160
161
  def find_site site_id
161
162
  @proxies.each do |site|
162
- return site if site.site_id == site_id
163
+ return site if site_id == :any || site.site_id == site_id
163
164
  end
164
165
  nil
165
166
  end
@@ -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,6 +79,13 @@ module RSMP
72
79
  else
73
80
  super message
74
81
  end
82
+ rescue UnknownComponent, UnknownCommand, UnknownStatus,
83
+ MessageRejected, MissingAttribute => e
84
+ dont_acknowledge message, '', e.to_s
85
+ end
86
+
87
+ def process_deferred
88
+ site.process_deferred
75
89
  end
76
90
 
77
91
  def acknowledged_first_ingoing message
@@ -112,7 +126,7 @@ module RSMP
112
126
  message = AggregatedStatus.new({
113
127
  "aSTS" => RSMP.now_string,
114
128
  "cId" => component.c_id,
115
- "fP" => nil,
129
+ "fP" => 'NormalControl',
116
130
  "fS" => nil,
117
131
  "se" => component.aggregated_status_bools
118
132
  })
@@ -135,22 +149,35 @@ module RSMP
135
149
  acknowledge message
136
150
  end
137
151
 
152
+ # reorganize rmsp command request arg attribute:
153
+ # [{"cCI":"M0002","cO":"setPlan","n":"status","v":"True"},{"cCI":"M0002","cO":"setPlan","n":"securityCode","v":"5678"},{"cCI":"M0002","cO":"setPlan","n":"timeplan","v":"3"}]
154
+ # into the simpler, but equivalent:
155
+ # {"M0002"=>{"status"=>"True", "securityCode"=>"5678", "timeplan"=>"3"}}
156
+ def simplify_command_requests arg
157
+ sorted = {}
158
+ arg.each do |item|
159
+ sorted[item['cCI']] ||= {}
160
+ sorted[item['cCI']][item['n']] = item['v']
161
+ end
162
+ sorted
163
+ end
164
+
138
165
  def process_command_request message
139
166
  log "Received #{message.type}", message: message, level: :log
140
- rvs = []
141
- message.attributes["arg"].each do |arg|
142
- unless arg['cCI'] && arg['n'] && arg['v']
143
- dont_acknowledge message, '', 'bad arguments'
144
- return
145
- end
146
- rvs << { "cCI" => arg["cCI"],
147
- "n" => arg["n"],
148
- "v" => arg["v"],
149
- "age" => "recent" }
167
+ component_id = message.attributes["cId"]
168
+ component = @site.find_component component_id
169
+ commands = simplify_command_requests message.attributes["arg"]
170
+ commands.each_pair do |command_code,arg|
171
+ component.handle_command command_code,arg
150
172
  end
151
173
 
174
+ rvs = message.attributes["arg"].map do |item|
175
+ item = item.dup.merge('age'=>'recent')
176
+ item.delete 'cO'
177
+ item
178
+ end
152
179
  response = CommandResponse.new({
153
- "cId"=>message.attributes["cId"],
180
+ "cId"=>component_id,
154
181
  "cTS"=>RSMP.now_string,
155
182
  "rvs"=>rvs
156
183
  })
@@ -158,25 +185,22 @@ module RSMP
158
185
  send_message response
159
186
  end
160
187
 
161
- def process_status_request message
188
+ def process_status_request message, options={}
162
189
  component_id = message.attributes["cId"]
163
190
  component = @site.find_component component_id
164
-
165
191
  log "Received #{message.type}", message: message, level: :log
166
- sS = message.attributes["sS"].clone.map do |request|
167
- request["s"] = rand(100).to_s
168
- request["q"] = "recent"
169
- request
192
+ sS = message.attributes["sS"].map do |arg|
193
+ value, quality = component.get_status arg['sCI'], arg['n']
194
+ { "s" => value.to_s, "q" => quality.to_s }.merge arg
170
195
  end
171
196
  response = StatusResponse.new({
172
197
  "cId"=>component_id,
173
198
  "sTs"=>RSMP.now_string,
174
- "sS"=>sS
199
+ "sS"=>sS,
200
+ "mId" => options[:m_id]
175
201
  })
176
202
  acknowledge message
177
203
  send_message response
178
- rescue UnknownComponent => e
179
- dont_acknowledge message, '', e.to_s
180
204
  end
181
205
 
182
206
  def process_status_subcribe message
@@ -198,10 +222,11 @@ module RSMP
198
222
  update_list[component] ||= {}
199
223
 
200
224
  subs = @status_subscriptions[component]
225
+ now = RSMP::now_object
201
226
 
202
227
  message.attributes["sS"].each do |arg|
203
228
  sCI = arg["sCI"]
204
- subcription = {interval: arg["uRt"].to_i, last_sent_at: nil}
229
+ subcription = {interval: arg["uRt"].to_i, last_sent_at: now}
205
230
  subs[sCI] ||= {}
206
231
  subs[sCI][arg["n"]] = subcription
207
232
 
@@ -235,18 +260,40 @@ module RSMP
235
260
  status_update_timer now if ready?
236
261
  end
237
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
+
238
278
  def status_update_timer now
239
279
  update_list = {}
240
280
  # go through subscriptons and build a similarly organized list,
241
281
  # that only contains what should be send
242
282
 
243
283
  @status_subscriptions.each_pair do |component,by_code|
284
+ component_object = @site.find_component component
244
285
  by_code.each_pair do |code,by_name|
245
286
  by_name.each_pair do |name,subscription|
287
+ current = nil
246
288
  if subscription[:interval] == 0
247
289
  # send as soon as the data changes
248
- 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
249
295
  should_send = true
296
+ store_last_sent_status component, code, name, current
250
297
  end
251
298
  else
252
299
  # send at regular intervals
@@ -257,8 +304,8 @@ module RSMP
257
304
  if should_send
258
305
  subscription[:last_sent_at] = now
259
306
  update_list[component] ||= {}
260
- update_list[component][code] ||= []
261
- update_list[component][code] << name
307
+ update_list[component][code] ||= {}
308
+ update_list[component][code][name] = current
262
309
  end
263
310
  end
264
311
  end
@@ -270,18 +317,24 @@ module RSMP
270
317
 
271
318
  def send_status_updates update_list
272
319
  now = RSMP.now_string
273
- update_list.each_pair do |component,by_code|
320
+ update_list.each_pair do |component_id,by_code|
321
+ component = @site.find_component component_id
274
322
  sS = []
275
323
  by_code.each_pair do |code,names|
276
- names.each do |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
277
330
  sS << { "sCI" => code,
278
- "n" => name,
279
- "s" => rand(100).to_s,
280
- "q" => "recent" }
331
+ "n" => status_name,
332
+ "s" => value.to_s,
333
+ "q" => quality }
281
334
  end
282
335
  end
283
336
  update = StatusUpdate.new({
284
- "cId"=>component,
337
+ "cId"=>component_id,
285
338
  "sTs"=>now,
286
339
  "sS"=>sS
287
340
  })
@@ -0,0 +1,869 @@
1
+ # Simulates a Traffic Light Controller
2
+
3
+ module RSMP
4
+
5
+ class TrafficController < Component
6
+ attr_reader :pos, :cycle_time
7
+
8
+ def initialize node:, id:, cycle_time:
9
+ super node: node, id: id, grouped: true
10
+ @signal_groups = []
11
+ @detector_logics = []
12
+ @plans = []
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
22
+ @plan = 0
23
+ @dark_mode = false
24
+ @yellow_flash = false
25
+ @booting = false
26
+ @control_mode = 'control'
27
+ @police_key = 0
28
+ @intersection = 0
29
+ @is_starting = false
30
+ @emergency_route = false
31
+ @emergency_route_number = 0
32
+ @traffic_situation = 0
33
+ @manual_control = false
34
+ @fixed_time_control = false
35
+ @isolated_control = false
36
+ @yellow_flash = false
37
+ @all_red = false
38
+
39
+ @inputs = '0'*@num_inputs
40
+ @input_activations = '0'*@num_inputs
41
+ @input_results = '0'*@num_inputs
42
+ end
43
+
44
+ def add_signal_group group
45
+ @signal_groups << group
46
+ end
47
+
48
+ def add_detector_logic logic
49
+ @detector_logics << logic
50
+ end
51
+ def timer now
52
+ pos = now.to_i % @cycle_time
53
+ if pos != @pos
54
+ @pos = pos
55
+ move pos
56
+ end
57
+ end
58
+
59
+ def move pos
60
+ @signal_groups.each do |group|
61
+ group.move pos
62
+ end
63
+ if pos == 0
64
+ aggrated_status_changed
65
+ end
66
+ end
67
+
68
+ def output_states
69
+ str = @signal_groups.map do |group|
70
+ s = "#{group.c_id}:#{group.state}"
71
+ if group.state =~ /^[1-9]$/
72
+ s.colorize(:green)
73
+ elsif group.state =~ /^[NOP]$/
74
+ s.colorize(:yellow)
75
+ else
76
+ s.colorize(:red)
77
+ end
78
+ end.join ' '
79
+ print "\t#{pos.to_s.ljust(3)} #{str}\r"
80
+ end
81
+
82
+ def format_signal_group_status
83
+ @signal_groups.map { |group| group.state }.join
84
+ end
85
+
86
+ def handle_command command_code, arg
87
+ case command_code
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)
94
+ else
95
+ raise UnknownCommand.new "Unknown command #{command_code}"
96
+ end
97
+ end
98
+
99
+ def handle_m0001 arg
100
+ @node.verify_security_code 2, arg['securityCode']
101
+ switch_mode arg['status']
102
+ end
103
+
104
+ def handle_m0002 arg
105
+ @node.verify_security_code 2, arg['securityCode']
106
+ if RSMP::Tlc.from_rsmp_bool(arg['status'])
107
+ switch_plan arg['timeplan']
108
+ else
109
+ switch_plan 0 # TODO use clock/calender
110
+ end
111
+ end
112
+
113
+ def handle_m0003 arg
114
+ @node.verify_security_code 2, arg['securityCode']
115
+ @traffic_situation = arg['traficsituation'].to_i
116
+ end
117
+
118
+ def handle_m0004 arg
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
124
+ end
125
+
126
+ def handle_m0005 arg
127
+ @node.verify_security_code 2, arg['securityCode']
128
+ @emergency_route = arg['status'] == 'True'
129
+ @emergency_route_number = arg['emergencyroute'].to_i
130
+ end
131
+
132
+ def handle_m0006 arg
133
+ @node.verify_security_code 2, arg['securityCode']
134
+ input = arg['input'].to_i
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']
194
+ end
195
+
196
+ def set_input i, value
197
+ return unless i>=0 && i<@num_inputs
198
+ @inputs[i] = (arg['value'] ? '1' : '0')
199
+ end
200
+
201
+ def set_fixed_time_control status
202
+ @fixed_time_control = status
203
+ end
204
+
205
+ def switch_plan plan
206
+ log "Switching to plan #{plan}", level: :info
207
+ @plan = plan.to_i
208
+ end
209
+
210
+ def switch_mode mode
211
+ log "Switching to mode #{mode}", level: :info
212
+ case mode
213
+ when 'NormalControl'
214
+ @yellow_flash = false
215
+ @dark_mode = false
216
+ when 'YellowFlash'
217
+ @yellow_flash = true
218
+ @dark_mode = false
219
+ when 'Dark'
220
+ @yellow_flash = false
221
+ @dark_mode = true
222
+ end
223
+ mode
224
+ end
225
+
226
+ def get_status code, name=nil
227
+ case code
228
+ when 'S0001', 'S0002', 'S0003', 'S0004', 'S0005', 'S0006', 'S0007',
229
+ 'S0008', 'S0009', 'S0010', 'S0011', 'S0012', 'S0013', 'S0014',
230
+ 'S0015', 'S0016', 'S0017', 'S0018', 'S0019', 'S0020', 'S0021',
231
+ 'S0022', 'S0023', 'S0024', 'S0026', 'S0027', 'S0028',
232
+ 'S0029', 'S0030', 'S0031',
233
+ 'S0091', 'S0092', 'S0095', 'S0096', 'S0097',
234
+ 'S0205', 'S0206', 'S0207', 'S0208'
235
+ return send("handle_#{code.downcase}", code, name)
236
+ else
237
+ raise InvalidMessage.new "unknown status code #{code}"
238
+ end
239
+ end
240
+
241
+ def handle_s0001 status_code, status_name=nil
242
+ case status_name
243
+ when 'signalgroupstatus'
244
+ RSMP::Tlc.make_status format_signal_group_status
245
+ when 'cyclecounter'
246
+ RSMP::Tlc.make_status @pos.to_s
247
+ when 'basecyclecounter'
248
+ RSMP::Tlc.make_status @pos.to_s
249
+ when 'stage'
250
+ RSMP::Tlc.make_status 0.to_s
251
+ end
252
+ end
253
+
254
+ def handle_s0002 status_code, status_name=nil
255
+ case status_name
256
+ when 'detectorlogicstatus'
257
+ RSMP::Tlc.make_status @detector_logics.map { |dl| dl.forced ? '1' : '0' }.join
258
+ end
259
+ end
260
+
261
+ def handle_s0003 status_code, status_name=nil
262
+ case status_name
263
+ when 'inputstatus'
264
+ RSMP::Tlc.make_status @input_results
265
+ when 'extendedinputstatus'
266
+ RSMP::Tlc.make_status 0.to_s
267
+ end
268
+ end
269
+
270
+ def handle_s0004 status_code, status_name=nil
271
+ case status_name
272
+ when 'outputstatus'
273
+ RSMP::Tlc.make_status 0
274
+ when 'extendedoutputstatus'
275
+ RSMP::Tlc.make_status 0
276
+ end
277
+ end
278
+
279
+ def handle_s0005 status_code, status_name=nil
280
+ case status_name
281
+ when 'status'
282
+ RSMP::Tlc.make_status @is_starting
283
+ end
284
+ end
285
+
286
+ def handle_s0006 status_code, status_name=nil
287
+ case status_name
288
+ when 'status'
289
+ RSMP::Tlc.make_status @emergency_route
290
+ when 'emergencystage'
291
+ RSMP::Tlc.make_status @emergency_route_number
292
+ end
293
+ end
294
+
295
+ def handle_s0007 status_code, status_name=nil
296
+ case status_name
297
+ when 'intersection'
298
+ RSMP::Tlc.make_status @intersection
299
+ when 'status'
300
+ RSMP::Tlc.make_status !@dark_mode
301
+ end
302
+ end
303
+
304
+ def handle_s0008 status_code, status_name=nil
305
+ case status_name
306
+ when 'intersection'
307
+ RSMP::Tlc.make_status @intersection
308
+ when 'status'
309
+ RSMP::Tlc.make_status @manual_control
310
+ end
311
+ end
312
+
313
+ def handle_s0009 status_code, status_name=nil
314
+ case status_name
315
+ when 'intersection'
316
+ RSMP::Tlc.make_status @intersection
317
+ when 'status'
318
+ RSMP::Tlc.make_status @fixed_time_control
319
+ end
320
+ end
321
+
322
+ def handle_s0010 status_code, status_name=nil
323
+ case status_name
324
+ when 'intersection'
325
+ RSMP::Tlc.make_status @intersection
326
+ when 'status'
327
+ RSMP::Tlc.make_status @isolated_control
328
+ end
329
+ end
330
+
331
+ def handle_s0011 status_code, status_name=nil
332
+ case status_name
333
+ when 'intersection'
334
+ RSMP::Tlc.make_status @intersection
335
+ when 'status'
336
+ RSMP::Tlc.make_status @yellow_flash
337
+ end
338
+ end
339
+
340
+ def handle_s0012 status_code, status_name=nil
341
+ case status_name
342
+ when 'intersection'
343
+ RSMP::Tlc.make_status @intersection
344
+ when 'status'
345
+ RSMP::Tlc.make_status @all_red
346
+ end
347
+ end
348
+
349
+ def handle_s0013 status_code, status_name=nil
350
+ case status_name
351
+ when 'intersection'
352
+ RSMP::Tlc.make_status @intersection
353
+ when 'status'
354
+ RSMP::Tlc.make_status @police_key
355
+ end
356
+ end
357
+
358
+ def handle_s0014 status_code, status_name=nil
359
+ case status_name
360
+ when 'status'
361
+ RSMP::Tlc.make_status @plan
362
+ end
363
+ end
364
+
365
+ def handle_s0015 status_code, status_name=nil
366
+ case status_name
367
+ when 'status'
368
+ RSMP::Tlc.make_status @traffic_situation
369
+ end
370
+ end
371
+
372
+ def handle_s0016 status_code, status_name=nil
373
+ case status_name
374
+ when 'number'
375
+ RSMP::Tlc.make_status @detector_logics.size
376
+ end
377
+ end
378
+
379
+ def handle_s0017 status_code, status_name=nil
380
+ case status_name
381
+ when 'number'
382
+ RSMP::Tlc.make_status @signal_groups.size
383
+ end
384
+ end
385
+
386
+ def handle_s0018 status_code, status_name=nil
387
+ case status_name
388
+ when 'number'
389
+ RSMP::Tlc.make_status @plans.size
390
+ end
391
+ end
392
+
393
+ def handle_s0019 status_code, status_name=nil
394
+ case status_name
395
+ when 'number'
396
+ RSMP::Tlc.make_status @num_traffic_situations
397
+ end
398
+ end
399
+
400
+ def handle_s0020 status_code, status_name=nil
401
+ case status_name
402
+ when 'intersection'
403
+ RSMP::Tlc.make_status @intersection
404
+ when 'controlmode'
405
+ RSMP::Tlc.make_status @control_mode
406
+ end
407
+ end
408
+
409
+ def handle_s0021 status_code, status_name=nil
410
+ case status_name
411
+ when 'detectorlogics'
412
+ RSMP::Tlc.make_status @detector_logics.map { |logic| logic.forced=='True' ? '1' : '0'}.join
413
+ end
414
+ end
415
+
416
+ def handle_s0022 status_code, status_name=nil
417
+ case status_name
418
+ when 'status'
419
+ RSMP::Tlc.make_status '1'
420
+ end
421
+ end
422
+
423
+ def handle_s0023 status_code, status_name=nil
424
+ case status_name
425
+ when 'status'
426
+ RSMP::Tlc.make_status '1-1-0'
427
+ end
428
+ end
429
+
430
+ def handle_s0024 status_code, status_name=nil
431
+ case status_name
432
+ when 'status'
433
+ RSMP::Tlc.make_status '1-0'
434
+ end
435
+ end
436
+
437
+ def handle_s0026 status_code, status_name=nil
438
+ case status_name
439
+ when 'status'
440
+ RSMP::Tlc.make_status '0-00'
441
+ end
442
+ end
443
+
444
+ def handle_s0027 status_code, status_name=nil
445
+ case status_name
446
+ when 'status'
447
+ RSMP::Tlc.make_status '00-00-00-00'
448
+ end
449
+ end
450
+
451
+ def handle_s0028 status_code, status_name=nil
452
+ case status_name
453
+ when 'status'
454
+ RSMP::Tlc.make_status '00-00'
455
+ end
456
+ end
457
+
458
+ def handle_s0029 status_code, status_name=nil
459
+ case status_name
460
+ when 'status'
461
+ RSMP::Tlc.make_status ''
462
+ end
463
+ end
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
+
479
+ def handle_s0091 status_code, status_name=nil
480
+ case status_name
481
+ when 'user'
482
+ RSMP::Tlc.make_status 'nobody'
483
+ when 'status'
484
+ RSMP::Tlc.make_status 'logout'
485
+ end
486
+ end
487
+
488
+ def handle_s0092 status_code, status_name=nil
489
+ case status_name
490
+ when 'user'
491
+ RSMP::Tlc.make_status 'nobody'
492
+ when 'status'
493
+ RSMP::Tlc.make_status 'logout'
494
+ end
495
+ end
496
+
497
+ def handle_s0095 status_code, status_name=nil
498
+ case status_name
499
+ when 'status'
500
+ RSMP::Tlc.make_status RSMP::VERSION
501
+ end
502
+ end
503
+
504
+ def handle_s0096 status_code, status_name=nil
505
+ case status_name
506
+ when 'year'
507
+ RSMP::Tlc.make_status RSMP.now_object.year.to_s.rjust(4, "0")
508
+ when 'month'
509
+ RSMP::Tlc.make_status RSMP.now_object.month.to_s.rjust(2, "0")
510
+ when 'day'
511
+ RSMP::Tlc.make_status RSMP.now_object.day.to_s.rjust(2, "0")
512
+ when 'hour'
513
+ RSMP::Tlc.make_status RSMP.now_object.hour.to_s.rjust(2, "0")
514
+ when 'minute'
515
+ RSMP::Tlc.make_status RSMP.now_object.min.to_s.rjust(2, "0")
516
+ when 'second'
517
+ RSMP::Tlc.make_status RSMP.now_object.sec.to_s.rjust(2, "0")
518
+ end
519
+ end
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
+
530
+ def handle_s0205 status_code, status_name=nil
531
+ case status_name
532
+ when 'start'
533
+ RSMP::Tlc.make_status RSMP.now_string
534
+ when 'vehicles'
535
+ RSMP::Tlc.make_status 0
536
+ end
537
+ end
538
+
539
+ def handle_s0206 status_code, status_name=nil
540
+ case status_name
541
+ when 'start'
542
+ RSMP::Tlc.make_status RSMP.now_string
543
+ when 'speed'
544
+ RSMP::Tlc.make_status 0
545
+ end
546
+ end
547
+
548
+ def handle_s0207 status_code, status_name=nil
549
+ case status_name
550
+ when 'start'
551
+ RSMP::Tlc.make_status RSMP.now_string
552
+ when 'occupancy'
553
+ RSMP::Tlc.make_status 0
554
+ end
555
+ end
556
+
557
+ def handle_s0208 status_code, status_name=nil
558
+ case status_name
559
+ when 'start'
560
+ RSMP::Tlc.make_status RSMP.now_string
561
+ when 'P'
562
+ RSMP::Tlc.make_status 0
563
+ when 'PS'
564
+ RSMP::Tlc.make_status 0
565
+ when 'L'
566
+ RSMP::Tlc.make_status 0
567
+ when 'LS'
568
+ RSMP::Tlc.make_status 0
569
+ when 'B'
570
+ RSMP::Tlc.make_status 0
571
+ when 'SP'
572
+ RSMP::Tlc.make_status 0
573
+ when 'MC'
574
+ RSMP::Tlc.make_status 0
575
+ when 'C'
576
+ RSMP::Tlc.make_status 0
577
+ when 'F'
578
+ RSMP::Tlc.make_status 0
579
+ end
580
+ end
581
+
582
+ end
583
+
584
+ class SignalGroup < Component
585
+ attr_reader :plan, :state
586
+
587
+ def initialize node:, id:, plan:
588
+ super node: node, id: id, grouped: false
589
+ @plan = plan
590
+ move 0
591
+ end
592
+
593
+ def get_state pos
594
+ if pos > @plan.length
595
+ '.'
596
+ else
597
+ @plan[pos]
598
+ end
599
+ end
600
+
601
+ def move pos
602
+ @state = get_state pos
603
+ end
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
+
630
+ def get_status code, name=nil
631
+ case code
632
+ when 'S0025'
633
+ return send("handle_#{code.downcase}", code, name)
634
+ else
635
+ raise InvalidMessage.new "unknown status code #{code}"
636
+ end
637
+ end
638
+
639
+ def handle_s0025 status_code, status_name=nil
640
+ case status_name
641
+ when 'minToGEstimate'
642
+ RSMP::Tlc.make_status RSMP.now_string
643
+ when 'maxToGEstimate'
644
+ RSMP::Tlc.make_status RSMP.now_string
645
+ when 'likelyToGEstimate'
646
+ RSMP::Tlc.make_status RSMP.now_string
647
+ when 'ToGConfidence'
648
+ RSMP::Tlc.make_status 0
649
+ when 'minToREstimate'
650
+ RSMP::Tlc.make_status RSMP.now_string
651
+ when 'maxToREstimate'
652
+ RSMP::Tlc.make_status RSMP.now_string
653
+ when 'likelyToREstimate'
654
+ RSMP::Tlc.make_status RSMP.now_string
655
+ when 'ToRConfidence'
656
+ RSMP::Tlc.make_status 0
657
+ end
658
+ end
659
+ end
660
+
661
+ class DetectorLogic < Component
662
+ attr_reader :status, :forced, :value
663
+
664
+ def initialize node:, id:
665
+ super node: node, id: id, grouped: false
666
+ @forced = 0
667
+ @value = 0
668
+ end
669
+
670
+ def get_status code, name=nil
671
+ case code
672
+ when 'S0201', 'S0202', 'S0203', 'S0204'
673
+ return send("handle_#{code.downcase}", code, name)
674
+ else
675
+ raise InvalidMessage.new "unknown status code #{code}"
676
+ end
677
+ end
678
+
679
+ def handle_s0201 status_code, status_name=nil
680
+ case status_name
681
+ when 'starttime'
682
+ RSMP::Tlc.make_status RSMP.now_string
683
+ when 'vehicles'
684
+ RSMP::Tlc.make_status 0
685
+ end
686
+ end
687
+
688
+ def handle_s0202 status_code, status_name=nil
689
+ case status_name
690
+ when 'starttime'
691
+ RSMP::Tlc.make_status RSMP.now_string
692
+ when 'speed'
693
+ RSMP::Tlc.make_status 0
694
+ end
695
+ end
696
+
697
+ def handle_s0203 status_code, status_name=nil
698
+ case status_name
699
+ when 'starttime'
700
+ RSMP::Tlc.make_status RSMP.now_string
701
+ when 'occupancy'
702
+ RSMP::Tlc.make_status 0
703
+ end
704
+ end
705
+
706
+ def handle_s0204 status_code, status_name=nil
707
+ case status_name
708
+ when 'starttime'
709
+ RSMP::Tlc.make_status RSMP.now_string
710
+ when 'P'
711
+ RSMP::Tlc.make_status 0
712
+ when 'PS'
713
+ RSMP::Tlc.make_status 0
714
+ when 'L'
715
+ RSMP::Tlc.make_status 0
716
+ when 'LS'
717
+ RSMP::Tlc.make_status 0
718
+ when 'B'
719
+ RSMP::Tlc.make_status 0
720
+ when 'SP'
721
+ RSMP::Tlc.make_status 0
722
+ when 'MC'
723
+ RSMP::Tlc.make_status 0
724
+ when 'C'
725
+ RSMP::Tlc.make_status 0
726
+ when 'F'
727
+ RSMP::Tlc.make_status 0
728
+ end
729
+ end
730
+
731
+ def handle_command command_code, arg
732
+ case command_code
733
+ when 'M0008'
734
+ handle_m0008 arg
735
+ else
736
+ raise UnknownCommand.new "Unknown command #{command_code}"
737
+ end
738
+ end
739
+
740
+ def handle_m0008 arg
741
+ @node.verify_security_code 2, arg['securityCode']
742
+ force_detector_logic arg['status']=='True', arg['value']='True'
743
+ arg
744
+ end
745
+
746
+ def force_detector_logic status, value
747
+ @forced = status
748
+ @value = value
749
+ end
750
+
751
+ end
752
+
753
+ class Tlc < Site
754
+ def initialize options={}
755
+ super options
756
+ @sxl = 'traffic_light_controller'
757
+ @security_codes = options[:site_settings]['security_codes']
758
+ @interval = options[:site_settings]['interval'] || 1
759
+ unless @main
760
+ raise ConfigurationError.new "TLC must have a main component"
761
+ end
762
+ end
763
+
764
+ def build_component id:, type:, settings:{}
765
+ component = case type
766
+ when 'main'
767
+ @main = TrafficController.new node: self, id: id, cycle_time: settings['cycle_time']
768
+ when 'signal_group'
769
+ group = SignalGroup.new node: self, id: id, plan: settings['plan']
770
+ @main.add_signal_group group
771
+ group
772
+ when 'detector_logic'
773
+ logic = DetectorLogic.new node: self, id: id
774
+ @main.add_detector_logic logic
775
+ logic
776
+ end
777
+ end
778
+
779
+ def start_action
780
+ super
781
+ start_timer
782
+ end
783
+
784
+ def start_timer
785
+ task_name = "tlc timer"
786
+ log "Starting #{task_name} with interval #{@interval} seconds", level: :debug
787
+
788
+ @timer = @task.async do |task|
789
+ task.annotate task_name
790
+ next_time = Time.now.to_f
791
+ loop do
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
818
+ end
819
+ end
820
+ end
821
+
822
+ def timer now
823
+ return unless @main
824
+ @main.timer now
825
+ end
826
+
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
837
+ end
838
+
839
+ def self.to_rmsp_bool bool
840
+ if bool
841
+ 'True'
842
+ else
843
+ 'False'
844
+ end
845
+ end
846
+
847
+ def self.from_rsmp_bool str
848
+ str == 'True'
849
+ end
850
+
851
+ def self.make_status value, q='recent'
852
+ case value
853
+ when true, false
854
+ return to_rmsp_bool(value), q
855
+ else
856
+ return value, q
857
+ end
858
+ end
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
+
868
+ end
869
+ end