rsmp 0.1.11 → 0.1.21

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,18 +117,19 @@ module RSMP
117
117
  level: :info,
118
118
  timestamp: RSMP.now_object
119
119
 
120
- proxy = build_connector({
120
+ settings = @supervisor_settings['sites'][info[:ip]] || @supervisor_settings['sites'][:any]
121
+ proxy = build_proxy({
121
122
  supervisor: self,
122
123
  task: @task,
123
- settings: @supervisor_settings[:sites],
124
+ settings: settings,
124
125
  socket: socket,
125
126
  info: info,
126
127
  logger: @logger,
127
128
  archive: @archive
128
129
  })
129
130
  @proxies.push proxy
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
@@ -14,7 +14,6 @@ module RSMP
14
14
  @ip = options[:ip]
15
15
  @port = options[:port]
16
16
  @status_subscriptions = {}
17
- @status_subscriptions_mutex = Mutex.new
18
17
  @sxl = @site_settings['sxl']
19
18
  @synthetic_id = Supervisor.build_id_from_ip_port @ip, @port
20
19
  end
@@ -28,6 +27,7 @@ module RSMP
28
27
  super
29
28
  connect
30
29
  @logger.unmute @ip, @port
30
+ log "Connected to superviser at #{@ip}:#{@port}", level: :info
31
31
  start_reader
32
32
  send_version @site_settings['site_id'], @site_settings["rsmp_versions"]
33
33
  rescue Errno::ECONNREFUSED
@@ -38,6 +38,12 @@ module RSMP
38
38
  end
39
39
  end
40
40
 
41
+ def stop
42
+ log "Closing connection to supervisor", level: :info
43
+ super
44
+ @last_status_sent = nil
45
+ end
46
+
41
47
  def connect
42
48
  return if @socket
43
49
  @endpoint = Async::IO::Endpoint.tcp(@ip, @port)
@@ -72,6 +78,13 @@ module RSMP
72
78
  else
73
79
  super message
74
80
  end
81
+ rescue UnknownComponent, UnknownCommand, UnknownStatus,
82
+ MessageRejected, MissingAttribute => e
83
+ dont_acknowledge message, '', e.to_s
84
+ end
85
+
86
+ def process_deferred
87
+ site.process_deferred
75
88
  end
76
89
 
77
90
  def acknowledged_first_ingoing message
@@ -112,7 +125,7 @@ module RSMP
112
125
  message = AggregatedStatus.new({
113
126
  "aSTS" => RSMP.now_string,
114
127
  "cId" => component.c_id,
115
- "fP" => nil,
128
+ "fP" => 'NormalControl',
116
129
  "fS" => nil,
117
130
  "se" => component.aggregated_status_bools
118
131
  })
@@ -135,22 +148,35 @@ module RSMP
135
148
  acknowledge message
136
149
  end
137
150
 
151
+ # reorganize rmsp command request arg attribute:
152
+ # [{"cCI":"M0002","cO":"setPlan","n":"status","v":"True"},{"cCI":"M0002","cO":"setPlan","n":"securityCode","v":"5678"},{"cCI":"M0002","cO":"setPlan","n":"timeplan","v":"3"}]
153
+ # into the simpler, but equivalent:
154
+ # {"M0002"=>{"status"=>"True", "securityCode"=>"5678", "timeplan"=>"3"}}
155
+ def simplify_command_requests arg
156
+ sorted = {}
157
+ arg.each do |item|
158
+ sorted[item['cCI']] ||= {}
159
+ sorted[item['cCI']][item['n']] = item['v']
160
+ end
161
+ sorted
162
+ end
163
+
138
164
  def process_command_request message
139
165
  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" }
166
+ component_id = message.attributes["cId"]
167
+ component = @site.find_component component_id
168
+ commands = simplify_command_requests message.attributes["arg"]
169
+ commands.each_pair do |command_code,arg|
170
+ component.handle_command command_code,arg
150
171
  end
151
172
 
173
+ rvs = message.attributes["arg"].map do |item|
174
+ item = item.dup.merge('age'=>'recent')
175
+ item.delete 'cO'
176
+ item
177
+ end
152
178
  response = CommandResponse.new({
153
- "cId"=>message.attributes["cId"],
179
+ "cId"=>component_id,
154
180
  "cTS"=>RSMP.now_string,
155
181
  "rvs"=>rvs
156
182
  })
@@ -158,25 +184,22 @@ module RSMP
158
184
  send_message response
159
185
  end
160
186
 
161
- def process_status_request message
187
+ def process_status_request message, options={}
162
188
  component_id = message.attributes["cId"]
163
189
  component = @site.find_component component_id
164
-
165
190
  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
191
+ sS = message.attributes["sS"].map do |arg|
192
+ value, quality = component.get_status arg['sCI'], arg['n']
193
+ { "s" => value.to_s, "q" => quality.to_s }.merge arg
170
194
  end
171
195
  response = StatusResponse.new({
172
196
  "cId"=>component_id,
173
197
  "sTs"=>RSMP.now_string,
174
- "sS"=>sS
198
+ "sS"=>sS,
199
+ "mId" => options[:m_id]
175
200
  })
176
201
  acknowledge message
177
202
  send_message response
178
- rescue UnknownComponent => e
179
- dont_acknowledge message, '', e.to_s
180
203
  end
181
204
 
182
205
  def process_status_subcribe message
@@ -198,10 +221,11 @@ module RSMP
198
221
  update_list[component] ||= {}
199
222
 
200
223
  subs = @status_subscriptions[component]
224
+ now = RSMP::now_object
201
225
 
202
226
  message.attributes["sS"].each do |arg|
203
227
  sCI = arg["sCI"]
204
- subcription = {interval: arg["uRt"].to_i, last_sent_at: nil}
228
+ subcription = {interval: arg["uRt"].to_i, last_sent_at: now}
205
229
  subs[sCI] ||= {}
206
230
  subs[sCI][arg["n"]] = subcription
207
231
 
@@ -235,18 +259,40 @@ module RSMP
235
259
  status_update_timer now if ready?
236
260
  end
237
261
 
262
+ def fetch_last_sent_status component, code, name
263
+ if @last_status_sent && @last_status_sent[component] && @last_status_sent[component][code]
264
+ @last_status_sent[component][code][name]
265
+ else
266
+ nil
267
+ end
268
+ end
269
+
270
+ def store_last_sent_status component, code, name, value
271
+ @last_status_sent ||= {}
272
+ @last_status_sent[component] ||= {}
273
+ @last_status_sent[component][code] ||= {}
274
+ @last_status_sent[component][code][name] = value
275
+ end
276
+
238
277
  def status_update_timer now
239
278
  update_list = {}
240
279
  # go through subscriptons and build a similarly organized list,
241
280
  # that only contains what should be send
242
281
 
243
282
  @status_subscriptions.each_pair do |component,by_code|
283
+ component_object = @site.find_component component
244
284
  by_code.each_pair do |code,by_name|
245
285
  by_name.each_pair do |name,subscription|
286
+ current = nil
246
287
  if subscription[:interval] == 0
247
288
  # send as soon as the data changes
248
- if rand(100) >= 90
289
+ if component_object
290
+ current, age = *(component_object.get_status code, name)
291
+ end
292
+ last_sent = fetch_last_sent_status component, code, name
293
+ if current != last_sent
249
294
  should_send = true
295
+ store_last_sent_status component, code, name, current
250
296
  end
251
297
  else
252
298
  # send at regular intervals
@@ -257,8 +303,8 @@ module RSMP
257
303
  if should_send
258
304
  subscription[:last_sent_at] = now
259
305
  update_list[component] ||= {}
260
- update_list[component][code] ||= []
261
- update_list[component][code] << name
306
+ update_list[component][code] ||= {}
307
+ update_list[component][code][name] = current
262
308
  end
263
309
  end
264
310
  end
@@ -270,18 +316,24 @@ module RSMP
270
316
 
271
317
  def send_status_updates update_list
272
318
  now = RSMP.now_string
273
- update_list.each_pair do |component,by_code|
319
+ update_list.each_pair do |component_id,by_code|
320
+ component = @site.find_component component_id
274
321
  sS = []
275
322
  by_code.each_pair do |code,names|
276
- names.each do |name|
323
+ names.map do |status_name,value|
324
+ if value
325
+ quality = 'recent'
326
+ else
327
+ value,quality = component.get_status code, status_name
328
+ end
277
329
  sS << { "sCI" => code,
278
- "n" => name,
279
- "s" => rand(100).to_s,
280
- "q" => "recent" }
330
+ "n" => status_name,
331
+ "s" => value.to_s,
332
+ "q" => quality }
281
333
  end
282
334
  end
283
335
  update = StatusUpdate.new({
284
- "cId"=>component,
336
+ "cId"=>component_id,
285
337
  "sTs"=>now,
286
338
  "sS"=>sS
287
339
  })
@@ -0,0 +1,885 @@
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
+
131
+ if @emergency_route
132
+ log "Switching to emergency route #{@emergency_route_number}", level: :info
133
+ else
134
+ log "Switching off emergency route", level: :info
135
+ end
136
+ end
137
+
138
+ def handle_m0006 arg
139
+ @node.verify_security_code 2, arg['securityCode']
140
+ input = arg['input'].to_i
141
+ idx = input - 1
142
+ return unless idx>=0 && input<@num_inputs # TODO should NotAck
143
+ @input_activations[idx] = (arg['status']=='True' ? '1' : '0')
144
+ result = @input_activations[idx]=='1' || @inputs[idx]=='1'
145
+ @input_results[idx] = (result ? '1' : '0')
146
+ if @input_activations[idx]
147
+ log "Activate input #{idx}", level: :info
148
+ else
149
+ log "Deactivate input #{idx}", level: :info
150
+ end
151
+ end
152
+
153
+ def handle_m0007 arg
154
+ @node.verify_security_code 2, arg['securityCode']
155
+ set_fixed_time_control arg['status']
156
+ end
157
+
158
+ def handle_m0012 arg
159
+ @node.verify_security_code 2, arg['securityCode']
160
+ end
161
+
162
+ def handle_m0013 arg
163
+ @node.verify_security_code 2, arg['securityCode']
164
+ end
165
+
166
+ def handle_m0014 arg
167
+ @node.verify_security_code 2, arg['securityCode']
168
+ end
169
+
170
+ def handle_m0015 arg
171
+ @node.verify_security_code 2, arg['securityCode']
172
+ end
173
+
174
+ def handle_m0016 arg
175
+ @node.verify_security_code 2, arg['securityCode']
176
+ end
177
+
178
+ def handle_m0017 arg
179
+ @node.verify_security_code 2, arg['securityCode']
180
+ end
181
+
182
+ def handle_m0018 arg
183
+ @node.verify_security_code 2, arg['securityCode']
184
+ end
185
+
186
+ def handle_m0019 arg
187
+ @node.verify_security_code 2, arg['securityCode']
188
+ end
189
+
190
+ def handle_m0020 arg
191
+ @node.verify_security_code 2, arg['securityCode']
192
+ end
193
+
194
+ def handle_m0021 arg
195
+ @node.verify_security_code 2, arg['securityCode']
196
+ end
197
+
198
+ def handle_m0103 arg
199
+ level = {'Level1'=>1,'Level2'=>2}[arg['status']]
200
+ @node.change_security_code level, arg['oldSecurityCode'], arg['newSecurityCode']
201
+ end
202
+
203
+ def handle_m0104 arg
204
+ @node.verify_security_code 1, arg['securityCode']
205
+ end
206
+
207
+ def set_input i, value
208
+ return unless i>=0 && i<@num_inputs
209
+ @inputs[i] = (arg['value'] ? '1' : '0')
210
+ end
211
+
212
+ def set_fixed_time_control status
213
+ @fixed_time_control = status
214
+ end
215
+
216
+ def switch_plan plan
217
+ plan_nr = plan.to_i
218
+ if plan_nr == 0
219
+ log "Switching to plan selection by time table", level: :info
220
+ else
221
+ log "Switching to plan #{plan_nr}", level: :info
222
+ end
223
+ @plan = plan_nr
224
+ end
225
+
226
+ def switch_mode mode
227
+ log "Switching to mode #{mode}", level: :info
228
+ case mode
229
+ when 'NormalControl'
230
+ @yellow_flash = false
231
+ @dark_mode = false
232
+ when 'YellowFlash'
233
+ @yellow_flash = true
234
+ @dark_mode = false
235
+ when 'Dark'
236
+ @yellow_flash = false
237
+ @dark_mode = true
238
+ end
239
+ mode
240
+ end
241
+
242
+ def get_status code, name=nil
243
+ case code
244
+ when 'S0001', 'S0002', 'S0003', 'S0004', 'S0005', 'S0006', 'S0007',
245
+ 'S0008', 'S0009', 'S0010', 'S0011', 'S0012', 'S0013', 'S0014',
246
+ 'S0015', 'S0016', 'S0017', 'S0018', 'S0019', 'S0020', 'S0021',
247
+ 'S0022', 'S0023', 'S0024', 'S0026', 'S0027', 'S0028',
248
+ 'S0029', 'S0030', 'S0031',
249
+ 'S0091', 'S0092', 'S0095', 'S0096', 'S0097',
250
+ 'S0205', 'S0206', 'S0207', 'S0208'
251
+ return send("handle_#{code.downcase}", code, name)
252
+ else
253
+ raise InvalidMessage.new "unknown status code #{code}"
254
+ end
255
+ end
256
+
257
+ def handle_s0001 status_code, status_name=nil
258
+ case status_name
259
+ when 'signalgroupstatus'
260
+ RSMP::Tlc.make_status format_signal_group_status
261
+ when 'cyclecounter'
262
+ RSMP::Tlc.make_status @pos.to_s
263
+ when 'basecyclecounter'
264
+ RSMP::Tlc.make_status @pos.to_s
265
+ when 'stage'
266
+ RSMP::Tlc.make_status 0.to_s
267
+ end
268
+ end
269
+
270
+ def handle_s0002 status_code, status_name=nil
271
+ case status_name
272
+ when 'detectorlogicstatus'
273
+ RSMP::Tlc.make_status @detector_logics.map { |dl| dl.forced ? '1' : '0' }.join
274
+ end
275
+ end
276
+
277
+ def handle_s0003 status_code, status_name=nil
278
+ case status_name
279
+ when 'inputstatus'
280
+ RSMP::Tlc.make_status @input_results
281
+ when 'extendedinputstatus'
282
+ RSMP::Tlc.make_status 0.to_s
283
+ end
284
+ end
285
+
286
+ def handle_s0004 status_code, status_name=nil
287
+ case status_name
288
+ when 'outputstatus'
289
+ RSMP::Tlc.make_status 0
290
+ when 'extendedoutputstatus'
291
+ RSMP::Tlc.make_status 0
292
+ end
293
+ end
294
+
295
+ def handle_s0005 status_code, status_name=nil
296
+ case status_name
297
+ when 'status'
298
+ RSMP::Tlc.make_status @is_starting
299
+ end
300
+ end
301
+
302
+ def handle_s0006 status_code, status_name=nil
303
+ case status_name
304
+ when 'status'
305
+ RSMP::Tlc.make_status @emergency_route
306
+ when 'emergencystage'
307
+ RSMP::Tlc.make_status @emergency_route_number
308
+ end
309
+ end
310
+
311
+ def handle_s0007 status_code, status_name=nil
312
+ case status_name
313
+ when 'intersection'
314
+ RSMP::Tlc.make_status @intersection
315
+ when 'status'
316
+ RSMP::Tlc.make_status !@dark_mode
317
+ end
318
+ end
319
+
320
+ def handle_s0008 status_code, status_name=nil
321
+ case status_name
322
+ when 'intersection'
323
+ RSMP::Tlc.make_status @intersection
324
+ when 'status'
325
+ RSMP::Tlc.make_status @manual_control
326
+ end
327
+ end
328
+
329
+ def handle_s0009 status_code, status_name=nil
330
+ case status_name
331
+ when 'intersection'
332
+ RSMP::Tlc.make_status @intersection
333
+ when 'status'
334
+ RSMP::Tlc.make_status @fixed_time_control
335
+ end
336
+ end
337
+
338
+ def handle_s0010 status_code, status_name=nil
339
+ case status_name
340
+ when 'intersection'
341
+ RSMP::Tlc.make_status @intersection
342
+ when 'status'
343
+ RSMP::Tlc.make_status @isolated_control
344
+ end
345
+ end
346
+
347
+ def handle_s0011 status_code, status_name=nil
348
+ case status_name
349
+ when 'intersection'
350
+ RSMP::Tlc.make_status @intersection
351
+ when 'status'
352
+ RSMP::Tlc.make_status @yellow_flash
353
+ end
354
+ end
355
+
356
+ def handle_s0012 status_code, status_name=nil
357
+ case status_name
358
+ when 'intersection'
359
+ RSMP::Tlc.make_status @intersection
360
+ when 'status'
361
+ RSMP::Tlc.make_status @all_red
362
+ end
363
+ end
364
+
365
+ def handle_s0013 status_code, status_name=nil
366
+ case status_name
367
+ when 'intersection'
368
+ RSMP::Tlc.make_status @intersection
369
+ when 'status'
370
+ RSMP::Tlc.make_status @police_key
371
+ end
372
+ end
373
+
374
+ def handle_s0014 status_code, status_name=nil
375
+ case status_name
376
+ when 'status'
377
+ RSMP::Tlc.make_status @plan
378
+ end
379
+ end
380
+
381
+ def handle_s0015 status_code, status_name=nil
382
+ case status_name
383
+ when 'status'
384
+ RSMP::Tlc.make_status @traffic_situation
385
+ end
386
+ end
387
+
388
+ def handle_s0016 status_code, status_name=nil
389
+ case status_name
390
+ when 'number'
391
+ RSMP::Tlc.make_status @detector_logics.size
392
+ end
393
+ end
394
+
395
+ def handle_s0017 status_code, status_name=nil
396
+ case status_name
397
+ when 'number'
398
+ RSMP::Tlc.make_status @signal_groups.size
399
+ end
400
+ end
401
+
402
+ def handle_s0018 status_code, status_name=nil
403
+ case status_name
404
+ when 'number'
405
+ RSMP::Tlc.make_status @plans.size
406
+ end
407
+ end
408
+
409
+ def handle_s0019 status_code, status_name=nil
410
+ case status_name
411
+ when 'number'
412
+ RSMP::Tlc.make_status @num_traffic_situations
413
+ end
414
+ end
415
+
416
+ def handle_s0020 status_code, status_name=nil
417
+ case status_name
418
+ when 'intersection'
419
+ RSMP::Tlc.make_status @intersection
420
+ when 'controlmode'
421
+ RSMP::Tlc.make_status @control_mode
422
+ end
423
+ end
424
+
425
+ def handle_s0021 status_code, status_name=nil
426
+ case status_name
427
+ when 'detectorlogics'
428
+ RSMP::Tlc.make_status @detector_logics.map { |logic| logic.forced=='True' ? '1' : '0'}.join
429
+ end
430
+ end
431
+
432
+ def handle_s0022 status_code, status_name=nil
433
+ case status_name
434
+ when 'status'
435
+ RSMP::Tlc.make_status '1'
436
+ end
437
+ end
438
+
439
+ def handle_s0023 status_code, status_name=nil
440
+ case status_name
441
+ when 'status'
442
+ RSMP::Tlc.make_status '1-1-0'
443
+ end
444
+ end
445
+
446
+ def handle_s0024 status_code, status_name=nil
447
+ case status_name
448
+ when 'status'
449
+ RSMP::Tlc.make_status '1-0'
450
+ end
451
+ end
452
+
453
+ def handle_s0026 status_code, status_name=nil
454
+ case status_name
455
+ when 'status'
456
+ RSMP::Tlc.make_status '0-00'
457
+ end
458
+ end
459
+
460
+ def handle_s0027 status_code, status_name=nil
461
+ case status_name
462
+ when 'status'
463
+ RSMP::Tlc.make_status '00-00-00-00'
464
+ end
465
+ end
466
+
467
+ def handle_s0028 status_code, status_name=nil
468
+ case status_name
469
+ when 'status'
470
+ RSMP::Tlc.make_status '00-00'
471
+ end
472
+ end
473
+
474
+ def handle_s0029 status_code, status_name=nil
475
+ case status_name
476
+ when 'status'
477
+ RSMP::Tlc.make_status ''
478
+ end
479
+ end
480
+
481
+ def handle_s0030 status_code, status_name=nil
482
+ case status_name
483
+ when 'status'
484
+ RSMP::Tlc.make_status ''
485
+ end
486
+ end
487
+
488
+ def handle_s0031 status_code, status_name=nil
489
+ case status_name
490
+ when 'status'
491
+ RSMP::Tlc.make_status ''
492
+ end
493
+ end
494
+
495
+ def handle_s0091 status_code, status_name=nil
496
+ case status_name
497
+ when 'user'
498
+ RSMP::Tlc.make_status 'nobody'
499
+ when 'status'
500
+ RSMP::Tlc.make_status 'logout'
501
+ end
502
+ end
503
+
504
+ def handle_s0092 status_code, status_name=nil
505
+ case status_name
506
+ when 'user'
507
+ RSMP::Tlc.make_status 'nobody'
508
+ when 'status'
509
+ RSMP::Tlc.make_status 'logout'
510
+ end
511
+ end
512
+
513
+ def handle_s0095 status_code, status_name=nil
514
+ case status_name
515
+ when 'status'
516
+ RSMP::Tlc.make_status RSMP::VERSION
517
+ end
518
+ end
519
+
520
+ def handle_s0096 status_code, status_name=nil
521
+ case status_name
522
+ when 'year'
523
+ RSMP::Tlc.make_status RSMP.now_object.year.to_s.rjust(4, "0")
524
+ when 'month'
525
+ RSMP::Tlc.make_status RSMP.now_object.month.to_s.rjust(2, "0")
526
+ when 'day'
527
+ RSMP::Tlc.make_status RSMP.now_object.day.to_s.rjust(2, "0")
528
+ when 'hour'
529
+ RSMP::Tlc.make_status RSMP.now_object.hour.to_s.rjust(2, "0")
530
+ when 'minute'
531
+ RSMP::Tlc.make_status RSMP.now_object.min.to_s.rjust(2, "0")
532
+ when 'second'
533
+ RSMP::Tlc.make_status RSMP.now_object.sec.to_s.rjust(2, "0")
534
+ end
535
+ end
536
+
537
+ def handle_s0097 status_code, status_name=nil
538
+ case status_name
539
+ when 'version'
540
+ RSMP::Tlc.make_status '1'
541
+ when 'hash'
542
+ RSMP::Tlc.make_status '1'
543
+ end
544
+ end
545
+
546
+ def handle_s0205 status_code, status_name=nil
547
+ case status_name
548
+ when 'start'
549
+ RSMP::Tlc.make_status RSMP.now_string
550
+ when 'vehicles'
551
+ RSMP::Tlc.make_status 0
552
+ end
553
+ end
554
+
555
+ def handle_s0206 status_code, status_name=nil
556
+ case status_name
557
+ when 'start'
558
+ RSMP::Tlc.make_status RSMP.now_string
559
+ when 'speed'
560
+ RSMP::Tlc.make_status 0
561
+ end
562
+ end
563
+
564
+ def handle_s0207 status_code, status_name=nil
565
+ case status_name
566
+ when 'start'
567
+ RSMP::Tlc.make_status RSMP.now_string
568
+ when 'occupancy'
569
+ RSMP::Tlc.make_status 0
570
+ end
571
+ end
572
+
573
+ def handle_s0208 status_code, status_name=nil
574
+ case status_name
575
+ when 'start'
576
+ RSMP::Tlc.make_status RSMP.now_string
577
+ when 'P'
578
+ RSMP::Tlc.make_status 0
579
+ when 'PS'
580
+ RSMP::Tlc.make_status 0
581
+ when 'L'
582
+ RSMP::Tlc.make_status 0
583
+ when 'LS'
584
+ RSMP::Tlc.make_status 0
585
+ when 'B'
586
+ RSMP::Tlc.make_status 0
587
+ when 'SP'
588
+ RSMP::Tlc.make_status 0
589
+ when 'MC'
590
+ RSMP::Tlc.make_status 0
591
+ when 'C'
592
+ RSMP::Tlc.make_status 0
593
+ when 'F'
594
+ RSMP::Tlc.make_status 0
595
+ end
596
+ end
597
+
598
+ end
599
+
600
+ class SignalGroup < Component
601
+ attr_reader :plan, :state
602
+
603
+ def initialize node:, id:, plan:
604
+ super node: node, id: id, grouped: false
605
+ @plan = plan
606
+ move 0
607
+ end
608
+
609
+ def get_state pos
610
+ if pos > @plan.length
611
+ '.'
612
+ else
613
+ @plan[pos]
614
+ end
615
+ end
616
+
617
+ def move pos
618
+ @state = get_state pos
619
+ end
620
+
621
+ def handle_command command_code, arg
622
+ case command_code
623
+ when 'M0010', 'M0011'
624
+ return send("handle_#{command_code.downcase}", arg)
625
+ else
626
+ raise UnknownCommand.new "Unknown command #{command_code}"
627
+ end
628
+ end
629
+
630
+ # Start of signal group. Orders a signal group to green
631
+ def handle_m0010 arg
632
+ @node.verify_security_code 2, arg['securityCode']
633
+ if RSMP::Tlc.from_rsmp_bool arg['status']
634
+ log "Start signal group #{c_id}, go to green", level: :info
635
+ end
636
+ end
637
+
638
+ # Stop of signal group. Orders a signal group to red
639
+ def handle_m0011 arg
640
+ @node.verify_security_code 2, arg['securityCode']
641
+ if RSMP::Tlc.from_rsmp_bool arg['status']
642
+ log "Stop signal group #{c_id}, go to red", level: :info
643
+ end
644
+ end
645
+
646
+ def get_status code, name=nil
647
+ case code
648
+ when 'S0025'
649
+ return send("handle_#{code.downcase}", code, name)
650
+ else
651
+ raise InvalidMessage.new "unknown status code #{code}"
652
+ end
653
+ end
654
+
655
+ def handle_s0025 status_code, status_name=nil
656
+ case status_name
657
+ when 'minToGEstimate'
658
+ RSMP::Tlc.make_status RSMP.now_string
659
+ when 'maxToGEstimate'
660
+ RSMP::Tlc.make_status RSMP.now_string
661
+ when 'likelyToGEstimate'
662
+ RSMP::Tlc.make_status RSMP.now_string
663
+ when 'ToGConfidence'
664
+ RSMP::Tlc.make_status 0
665
+ when 'minToREstimate'
666
+ RSMP::Tlc.make_status RSMP.now_string
667
+ when 'maxToREstimate'
668
+ RSMP::Tlc.make_status RSMP.now_string
669
+ when 'likelyToREstimate'
670
+ RSMP::Tlc.make_status RSMP.now_string
671
+ when 'ToRConfidence'
672
+ RSMP::Tlc.make_status 0
673
+ end
674
+ end
675
+ end
676
+
677
+ class DetectorLogic < Component
678
+ attr_reader :status, :forced, :value
679
+
680
+ def initialize node:, id:
681
+ super node: node, id: id, grouped: false
682
+ @forced = 0
683
+ @value = 0
684
+ end
685
+
686
+ def get_status code, name=nil
687
+ case code
688
+ when 'S0201', 'S0202', 'S0203', 'S0204'
689
+ return send("handle_#{code.downcase}", code, name)
690
+ else
691
+ raise InvalidMessage.new "unknown status code #{code}"
692
+ end
693
+ end
694
+
695
+ def handle_s0201 status_code, status_name=nil
696
+ case status_name
697
+ when 'starttime'
698
+ RSMP::Tlc.make_status RSMP.now_string
699
+ when 'vehicles'
700
+ RSMP::Tlc.make_status 0
701
+ end
702
+ end
703
+
704
+ def handle_s0202 status_code, status_name=nil
705
+ case status_name
706
+ when 'starttime'
707
+ RSMP::Tlc.make_status RSMP.now_string
708
+ when 'speed'
709
+ RSMP::Tlc.make_status 0
710
+ end
711
+ end
712
+
713
+ def handle_s0203 status_code, status_name=nil
714
+ case status_name
715
+ when 'starttime'
716
+ RSMP::Tlc.make_status RSMP.now_string
717
+ when 'occupancy'
718
+ RSMP::Tlc.make_status 0
719
+ end
720
+ end
721
+
722
+ def handle_s0204 status_code, status_name=nil
723
+ case status_name
724
+ when 'starttime'
725
+ RSMP::Tlc.make_status RSMP.now_string
726
+ when 'P'
727
+ RSMP::Tlc.make_status 0
728
+ when 'PS'
729
+ RSMP::Tlc.make_status 0
730
+ when 'L'
731
+ RSMP::Tlc.make_status 0
732
+ when 'LS'
733
+ RSMP::Tlc.make_status 0
734
+ when 'B'
735
+ RSMP::Tlc.make_status 0
736
+ when 'SP'
737
+ RSMP::Tlc.make_status 0
738
+ when 'MC'
739
+ RSMP::Tlc.make_status 0
740
+ when 'C'
741
+ RSMP::Tlc.make_status 0
742
+ when 'F'
743
+ RSMP::Tlc.make_status 0
744
+ end
745
+ end
746
+
747
+ def handle_command command_code, arg
748
+ case command_code
749
+ when 'M0008'
750
+ handle_m0008 arg
751
+ else
752
+ raise UnknownCommand.new "Unknown command #{command_code}"
753
+ end
754
+ end
755
+
756
+ def handle_m0008 arg
757
+ @node.verify_security_code 2, arg['securityCode']
758
+ force_detector_logic arg['status']=='True', arg['value']='True'
759
+ arg
760
+ end
761
+
762
+ def force_detector_logic status, value
763
+ @forced = status
764
+ @value = value
765
+ end
766
+
767
+ end
768
+
769
+ class Tlc < Site
770
+ def initialize options={}
771
+ super options
772
+ @sxl = 'traffic_light_controller'
773
+ @security_codes = options[:site_settings]['security_codes']
774
+ @interval = options[:site_settings]['interval'] || 1
775
+ unless @main
776
+ raise ConfigurationError.new "TLC must have a main component"
777
+ end
778
+ end
779
+
780
+ def build_component id:, type:, settings:{}
781
+ component = case type
782
+ when 'main'
783
+ @main = TrafficController.new node: self, id: id, cycle_time: settings['cycle_time']
784
+ when 'signal_group'
785
+ group = SignalGroup.new node: self, id: id, plan: settings['plan']
786
+ @main.add_signal_group group
787
+ group
788
+ when 'detector_logic'
789
+ logic = DetectorLogic.new node: self, id: id
790
+ @main.add_detector_logic logic
791
+ logic
792
+ end
793
+ end
794
+
795
+ def start_action
796
+ super
797
+ start_timer
798
+ end
799
+
800
+ def start_timer
801
+ task_name = "tlc timer"
802
+ log "Starting #{task_name} with interval #{@interval} seconds", level: :debug
803
+
804
+ @timer = @task.async do |task|
805
+ task.annotate task_name
806
+ next_time = Time.now.to_f
807
+ loop do
808
+ begin
809
+ now = RSMP.now_object
810
+ timer(now)
811
+ rescue EOFError => e
812
+ log "TLC timer: Connection closed: #{e}", level: :warning
813
+ rescue IOError => e
814
+ log "TLC timer: IOError", level: :warning
815
+ rescue Errno::ECONNRESET
816
+ log "TLC timer: Connection reset by peer", level: :warning
817
+ rescue Errno::EPIPE => e
818
+ log "TLC timer: Broken pipe", level: :warning
819
+ rescue StandardError => e
820
+ log "TLC timer: #{e}", level: :debug
821
+ ensure
822
+ # adjust sleep duration to avoid drift. so wake up always happens on the
823
+ # same fractional second.
824
+ # note that Time.now is not monotonic. If the clock si changed,
825
+ # either manaully or via NTP, the sleep interval might jump.
826
+ # an alternative is to use ::Process.clock_gettime(::Process::CLOCK_MONOTONIC),
827
+ # to get the current time. this ensures a constant interval, but
828
+ # if the clock is changed, the wake up would then happen on a different
829
+ # fractional second
830
+ next_time += @interval
831
+ duration = next_time - Time.now.to_f
832
+ task.sleep duration
833
+ end
834
+ end
835
+ end
836
+ end
837
+
838
+ def timer now
839
+ return unless @main
840
+ @main.timer now
841
+ end
842
+
843
+ def verify_security_code level, code
844
+ raise ArgumentError.new("Level must be 1-2, got #{level}") unless (1..2).include?(level)
845
+ if @security_codes[level] != code
846
+ raise MessageRejected.new("Wrong security code for level #{level}")
847
+ end
848
+ end
849
+
850
+ def change_security_code level, old_code, new_code
851
+ verify_security_code level, old_code
852
+ @security_codes[level] = new_code
853
+ end
854
+
855
+ def self.to_rmsp_bool bool
856
+ if bool
857
+ 'True'
858
+ else
859
+ 'False'
860
+ end
861
+ end
862
+
863
+ def self.from_rsmp_bool str
864
+ str == 'True'
865
+ end
866
+
867
+ def self.make_status value, q='recent'
868
+ case value
869
+ when true, false
870
+ return to_rmsp_bool(value), q
871
+ else
872
+ return value, q
873
+ end
874
+ end
875
+
876
+ def do_deferred item
877
+ case item
878
+ when :restart
879
+ log "Restarting TLC", level: :info
880
+ restart
881
+ end
882
+ end
883
+
884
+ end
885
+ end