rsmp 0.1.12 → 0.1.27

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