rsmp 0.37.0 → 0.38.0

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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/devcontainer.json +22 -0
  3. data/.github/workflows/rubocop.yaml +17 -0
  4. data/.gitignore +5 -6
  5. data/.rubocop.yml +80 -0
  6. data/Gemfile +13 -1
  7. data/Gemfile.lock +34 -1
  8. data/Rakefile +3 -3
  9. data/lib/rsmp/cli.rb +147 -124
  10. data/lib/rsmp/collect/ack_collector.rb +8 -7
  11. data/lib/rsmp/collect/aggregated_status_collector.rb +4 -4
  12. data/lib/rsmp/collect/alarm_collector.rb +31 -23
  13. data/lib/rsmp/collect/alarm_matcher.rb +3 -3
  14. data/lib/rsmp/collect/collector/logging.rb +17 -0
  15. data/lib/rsmp/collect/collector/reporting.rb +44 -0
  16. data/lib/rsmp/collect/collector/status.rb +34 -0
  17. data/lib/rsmp/collect/collector.rb +69 -150
  18. data/lib/rsmp/collect/command_matcher.rb +19 -6
  19. data/lib/rsmp/collect/command_response_collector.rb +7 -7
  20. data/lib/rsmp/collect/distributor.rb +14 -11
  21. data/lib/rsmp/collect/filter.rb +31 -15
  22. data/lib/rsmp/collect/matcher.rb +7 -11
  23. data/lib/rsmp/collect/queue.rb +4 -4
  24. data/lib/rsmp/collect/receiver.rb +10 -12
  25. data/lib/rsmp/collect/state_collector.rb +116 -77
  26. data/lib/rsmp/collect/status_collector.rb +6 -6
  27. data/lib/rsmp/collect/status_matcher.rb +17 -7
  28. data/lib/rsmp/{alarm_state.rb → component/alarm_state.rb} +76 -37
  29. data/lib/rsmp/{component.rb → component/component.rb} +15 -15
  30. data/lib/rsmp/component/component_base.rb +89 -0
  31. data/lib/rsmp/component/component_proxy.rb +75 -0
  32. data/lib/rsmp/component/components.rb +63 -0
  33. data/lib/rsmp/convert/export/json_schema.rb +116 -110
  34. data/lib/rsmp/convert/import/yaml.rb +21 -18
  35. data/lib/rsmp/{rsmp.rb → helpers/clock.rb} +5 -6
  36. data/lib/rsmp/{deep_merge.rb → helpers/deep_merge.rb} +2 -1
  37. data/lib/rsmp/helpers/error.rb +71 -0
  38. data/lib/rsmp/{inspect.rb → helpers/inspect.rb} +6 -10
  39. data/lib/rsmp/log/archive.rb +98 -0
  40. data/lib/rsmp/log/colorization.rb +41 -0
  41. data/lib/rsmp/log/filtering.rb +54 -0
  42. data/lib/rsmp/log/logger.rb +206 -0
  43. data/lib/rsmp/{logging.rb → log/logging.rb} +5 -7
  44. data/lib/rsmp/message.rb +159 -148
  45. data/lib/rsmp/{node.rb → node/node.rb} +19 -17
  46. data/lib/rsmp/{protocol.rb → node/protocol.rb} +5 -3
  47. data/lib/rsmp/node/site/site.rb +195 -0
  48. data/lib/rsmp/node/supervisor/modules/configuration.rb +59 -0
  49. data/lib/rsmp/node/supervisor/modules/connection.rb +140 -0
  50. data/lib/rsmp/node/supervisor/modules/sites.rb +64 -0
  51. data/lib/rsmp/node/supervisor/supervisor.rb +72 -0
  52. data/lib/rsmp/{task.rb → node/task.rb} +12 -14
  53. data/lib/rsmp/proxy/modules/acknowledgements.rb +144 -0
  54. data/lib/rsmp/proxy/modules/receive.rb +119 -0
  55. data/lib/rsmp/proxy/modules/send.rb +76 -0
  56. data/lib/rsmp/proxy/modules/state.rb +25 -0
  57. data/lib/rsmp/proxy/modules/tasks.rb +105 -0
  58. data/lib/rsmp/proxy/modules/versions.rb +69 -0
  59. data/lib/rsmp/proxy/modules/watchdogs.rb +66 -0
  60. data/lib/rsmp/proxy/proxy.rb +199 -0
  61. data/lib/rsmp/proxy/site/modules/aggregated_status.rb +52 -0
  62. data/lib/rsmp/proxy/site/modules/alarms.rb +27 -0
  63. data/lib/rsmp/proxy/site/modules/commands.rb +31 -0
  64. data/lib/rsmp/proxy/site/modules/status.rb +110 -0
  65. data/lib/rsmp/proxy/site/site_proxy.rb +205 -0
  66. data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +47 -0
  67. data/lib/rsmp/proxy/supervisor/modules/alarms.rb +73 -0
  68. data/lib/rsmp/proxy/supervisor/modules/commands.rb +53 -0
  69. data/lib/rsmp/proxy/supervisor/modules/status.rb +204 -0
  70. data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +178 -0
  71. data/lib/rsmp/tlc/detector_logic.rb +18 -34
  72. data/lib/rsmp/tlc/input_states.rb +126 -0
  73. data/lib/rsmp/tlc/modules/detector_logics.rb +50 -0
  74. data/lib/rsmp/tlc/modules/display.rb +78 -0
  75. data/lib/rsmp/tlc/modules/helpers.rb +41 -0
  76. data/lib/rsmp/tlc/modules/inputs.rb +173 -0
  77. data/lib/rsmp/tlc/modules/modes.rb +253 -0
  78. data/lib/rsmp/tlc/modules/outputs.rb +30 -0
  79. data/lib/rsmp/tlc/modules/plans.rb +218 -0
  80. data/lib/rsmp/tlc/modules/signal_groups.rb +109 -0
  81. data/lib/rsmp/tlc/modules/startup_sequence.rb +22 -0
  82. data/lib/rsmp/tlc/modules/system.rb +140 -0
  83. data/lib/rsmp/tlc/modules/traffic_data.rb +49 -0
  84. data/lib/rsmp/tlc/signal_group.rb +37 -41
  85. data/lib/rsmp/tlc/signal_plan.rb +14 -11
  86. data/lib/rsmp/tlc/signal_priority.rb +39 -35
  87. data/lib/rsmp/tlc/startup_sequence.rb +59 -0
  88. data/lib/rsmp/tlc/traffic_controller.rb +38 -1010
  89. data/lib/rsmp/tlc/traffic_controller_site.rb +58 -57
  90. data/lib/rsmp/version.rb +1 -1
  91. data/lib/rsmp.rb +82 -48
  92. data/rsmp.gemspec +24 -31
  93. metadata +79 -139
  94. data/lib/rsmp/archive.rb +0 -76
  95. data/lib/rsmp/collect/message_matchers.rb +0 -0
  96. data/lib/rsmp/component_base.rb +0 -87
  97. data/lib/rsmp/component_proxy.rb +0 -57
  98. data/lib/rsmp/components.rb +0 -65
  99. data/lib/rsmp/error.rb +0 -71
  100. data/lib/rsmp/logger.rb +0 -216
  101. data/lib/rsmp/proxy.rb +0 -693
  102. data/lib/rsmp/site.rb +0 -188
  103. data/lib/rsmp/site_proxy.rb +0 -389
  104. data/lib/rsmp/supervisor.rb +0 -302
  105. data/lib/rsmp/supervisor_proxy.rb +0 -510
  106. data/lib/rsmp/tlc/inputs.rb +0 -134
@@ -5,51 +5,33 @@ module RSMP
5
5
  # and keeps track of signal plans, detector logics, inputs, etc. which do
6
6
  # not have dedicated components.
7
7
  class TrafficController < Component
8
+ include TLC::Modules::System
9
+ include TLC::Modules::Modes
10
+ include TLC::Modules::Plans
11
+ include TLC::Modules::SignalGroups
12
+ include TLC::Modules::Inputs
13
+ include TLC::Modules::Outputs
14
+ include TLC::Modules::DetectorLogics
15
+ include TLC::Modules::TrafficData
16
+ include TLC::Modules::StartupSequence
17
+ include TLC::Modules::Display
18
+ include TLC::Modules::Helpers
19
+
8
20
  attr_reader :pos, :cycle_time, :plan, :cycle_counter,
9
- :functional_position,
10
- :startup_sequence_active, :startup_sequence, :startup_sequence_pos
21
+ :functional_position, :startup_sequence
11
22
 
12
- def initialize node:, id:, ntsOId: nil, xNId: nil, signal_plans:,
13
- startup_sequence:, live_output:nil, inputs:{}
14
- super node: node, id: id, ntsOId: ntsOId, xNId: xNId, grouped: true
23
+ def initialize(node:, id:, ntsoid: nil, xnid: nil, **options)
24
+ super(node: node, id: id, ntsoid: ntsoid, xnid: xnid, grouped: true)
15
25
  @signal_groups = []
16
26
  @detector_logics = []
17
- @plans = signal_plans
27
+ @plans = options[:signal_plans]
18
28
  @num_traffic_situations = 1
19
-
20
- if inputs
21
- num_inputs = inputs['total']
22
- @input_programming = inputs['programming']
23
- else
24
- @input_programming = nil
25
- end
26
- @inputs = TLC::Inputs.new num_inputs || 8
27
-
28
- @startup_sequence = startup_sequence
29
- @live_output = live_output
29
+ setup_inputs(options[:inputs])
30
+ @startup_sequence = StartupSequence.new(options[:startup_sequence])
31
+ @live_output = options[:live_output]
30
32
  reset
31
33
  end
32
34
 
33
- def reset_modes
34
- @function_position = 'NormalControl'
35
- @function_position_source = 'startup'
36
- @previous_functional_position = nil
37
- @functional_position_timeout = nil
38
-
39
- @booting = false
40
- @is_starting = false
41
- @control_mode = 'control'
42
- @manual_control = false
43
- @manual_control_source = 'startup'
44
- @fixed_time_control = false
45
- @fixed_time_control_source = 'startup'
46
- @isolated_control = false
47
- @isolated_control_source = 'startup'
48
- @all_red = false
49
- @all_red_source = 'startup'
50
- @police_key = 0
51
- end
52
-
53
35
  def reset
54
36
  reset_modes
55
37
  @cycle_counter = 0
@@ -62,577 +44,57 @@ module RSMP
62
44
  @traffic_situation = 0
63
45
  @traffic_situation_source = 'startup'
64
46
  @day_time_table = {}
65
- @startup_sequence_active = false
66
- @startup_sequence_initiated_at = nil
67
- @startup_sequence_pos = 0
68
47
  @time_int = nil
69
48
  @inputs.reset
70
49
  @signal_priorities = []
71
50
  @dynamic_bands_timeout = 0
72
51
  end
73
52
 
74
- def dark?
75
- @function_position == 'Dark'
76
- end
77
-
78
- def yellow_flash?
79
- @function_position == 'YellowFlash'
80
- end
81
-
82
- def normal_control?
83
- @function_position == 'NormalControl'
84
- end
85
-
86
- def clock
87
- node.clock
88
- end
89
-
90
- def current_plan
91
- # TODO plan 0 should means use time table
92
- if @plans
93
- @plans[ plan ] || @plans.values.first
94
- else
95
- nil
96
- end
97
- end
98
-
99
- def add_signal_group group
100
- @signal_groups << group
101
- end
102
-
103
- def add_detector_logic logic
104
- @detector_logics << logic
105
- end
106
-
107
- def timer now
108
- # TODO use monotone timer, to avoid jumps in case the user sets the system time
53
+ def timer(_now)
54
+ # TODO: use monotone timer, to avoid jumps in case the user sets the system time
109
55
  return unless move_cycle_counter
56
+
110
57
  check_functional_position_timeout
111
- move_startup_sequence if @startup_sequence_active
58
+ @startup_sequence.advance if @startup_sequence.active?
112
59
 
113
- @signal_groups.each { |group| group.timer }
114
- @signal_priorities.each {|priority| priority.timer }
60
+ @signal_groups.each(&:timer)
61
+ @signal_priorities.each(&:timer)
115
62
 
116
63
  output_states
117
64
  end
118
65
 
119
- def signal_priority_changed priority, state
120
- end
121
-
122
- # remove all stale priority requests
123
- def prune_priorities
124
- @signal_priorities.delete_if {|priority| priority.prune? }
125
- end
126
-
127
66
  # this method is called by the supervisor proxy each time status updates have been send
128
- # we can then prune our priority request list
67
+ # we can then prune our priority request list
129
68
  def status_updates_sent
130
69
  prune_priorities
131
70
  end
132
71
 
133
- def get_priority_list
134
- @signal_priorities.map do |priority|
135
- {
136
- "r" => priority.id,
137
- "t" => RSMP::Clock.to_s(priority.updated),
138
- "s" => priority.state
139
- }
140
- end
141
- end
142
-
143
72
  def move_cycle_counter
144
73
  plan = current_plan
145
- if plan
146
- counter = Time.now.to_i % plan.cycle_time
147
- else
148
- counter = 0
149
- end
74
+ counter = if plan
75
+ Time.now.to_i % plan.cycle_time
76
+ else
77
+ 0
78
+ end
150
79
  changed = counter != @cycle_counter
151
80
  @cycle_counter = counter
152
81
  changed
153
82
  end
154
83
 
155
- def check_functional_position_timeout
156
- return unless @functional_position_timeout
157
- if clock.now >= @functional_position_timeout
158
- switch_functional_position @previous_functional_position, reverting: true, source: 'calendar_clock'
159
- @functional_position_timeout = nil
160
- @previous_functional_position = nil
161
- end
162
- end
163
-
164
- def startup_state
165
- return unless @startup_sequence_active
166
- return unless @startup_sequence_pos
167
- @startup_sequence[ @startup_sequence_pos ]
168
- end
169
-
170
- def initiate_startup_sequence
171
- log "Initiating startup sequence", level: :info
172
- reset_modes
173
- @startup_sequence_active = true
174
- @startup_sequence_initiated_at = nil
175
- @startup_sequence_pos = nil
176
- end
177
-
178
- def end_startup_sequence
179
- @startup_sequence_active = false
180
- @startup_sequence_initiated_at = nil
181
- @startup_sequence_pos = nil
182
- end
183
-
184
- def move_startup_sequence
185
- was = @startup_sequence_pos
186
- if @startup_sequence_initiated_at == nil
187
- @startup_sequence_initiated_at = Time.now.to_i + 1
188
- @startup_sequence_pos = 0
189
- else
190
- @startup_sequence_pos = Time.now.to_i - @startup_sequence_initiated_at
191
- end
192
- if @startup_sequence_pos >= @startup_sequence.size
193
- end_startup_sequence
194
- end
195
- end
196
-
197
- def output_states
198
- return unless @live_output
199
-
200
- str = @signal_groups.map do |group|
201
- state = group.state
202
- s = "#{group.c_id}:#{state}"
203
- if state =~ /^[1-9]$/
204
- s.colorize(:green)
205
- elsif state =~ /^[NOP]$/
206
- s.colorize(:yellow)
207
- elsif state =~ /^[ae]$/
208
- s.colorize(:light_black)
209
- elsif state =~ /^[f]$/
210
- s.colorize(:yellow)
211
- elsif state =~ /^[g]$/
212
- s.colorize(:red)
213
- else
214
- s.colorize(:red)
215
- end
216
- end.join ' '
217
-
218
- modes = '.'*9
219
- modes[0] = 'N' if @function_position == 'NormalControl'
220
- modes[1] = 'Y' if @function_position == 'YellowFlash'
221
- modes[2] = 'D' if @function_position == 'Dark'
222
- modes[3] = 'B' if @booting
223
- modes[4] = 'S' if @startup_sequence_active
224
- modes[5] = 'M' if @manual_control
225
- modes[6] = 'F' if @fixed_time_control
226
- modes[7] = 'R' if @all_red
227
- modes[8] = 'I' if @isolated_control
228
- modes[9] = 'P' if @police_key != 0
229
-
230
- plan = "P#{@plan}"
231
-
232
- # create folders if needed
233
- FileUtils.mkdir_p File.dirname(@live_output)
234
-
235
- # append a line with the current state to the file
236
- File.open @live_output, 'w' do |file|
237
- file.puts "#{modes} #{plan.rjust(2)} #{@cycle_counter.to_s.rjust(3)} #{str}\r"
238
- end
239
- end
240
-
241
- def format_signal_group_status
242
- @signal_groups.map { |group| group.state }.join
243
- end
244
-
245
- def handle_command command_code, arg, options={}
84
+ def handle_command(command_code, arg, options = {})
246
85
  case command_code
247
86
  when 'M0001', 'M0002', 'M0003', 'M0004', 'M0005', 'M0006', 'M0007',
248
87
  'M0012', 'M0013', 'M0014', 'M0015', 'M0016', 'M0017', 'M0018',
249
88
  'M0019', 'M0020', 'M0021', 'M0022', 'M0023',
250
89
  'M0103', 'M0104'
251
90
 
252
- return send("handle_#{command_code.downcase}", arg, options)
253
- else
254
- raise UnknownCommand.new "Unknown command #{command_code}"
255
- end
256
- end
257
-
258
- def handle_m0001 arg, options={}
259
- @node.verify_security_code 2, arg['securityCode']
260
-
261
- # timeout is specified in minutes, but we take 1 to mean 1s
262
- # this is not according to the curent rsmp spec, but is done
263
- # to speed up testing
264
- timeout = arg['timeout'].to_i
265
- if timeout == 1
266
- timeout = 1
267
- else
268
- timeout *= 60
269
- end
270
-
271
- switch_functional_position arg['status'],
272
- timeout: timeout,
273
- source: 'forced'
274
- end
275
-
276
- def handle_m0002 arg, options={}
277
- @node.verify_security_code 2, arg['securityCode']
278
- if TrafficControllerSite.from_rsmp_bool(arg['status'])
279
- switch_plan arg['timeplan'], source: 'forced'
280
- else
281
- switch_plan 0, source: 'startup' # TODO use clock/calender
282
- end
283
- end
284
-
285
- def handle_m0003 arg, options={}
286
- @node.verify_security_code 2, arg['securityCode']
287
- switch_traffic_situation arg['traficsituation'], source: 'forced'
288
- end
289
-
290
- def switch_traffic_situation situation, source:
291
- @traffic_situation = situation.to_i
292
- @traffic_situation_source = 'forced'
293
- end
294
-
295
- def handle_m0004 arg, options={}
296
- @node.verify_security_code 2, arg['securityCode']
297
- # don't restart immeediately, since we need to first send command response
298
- # instead, defer an action, which will be handled by the TLC site
299
- log "Sheduling restart of TLC", level: :info
300
- @node.defer :restart
301
- end
302
-
303
- def handle_m0005 arg, options={}
304
- @node.verify_security_code 2, arg['securityCode']
305
- route = arg['emergencyroute'].to_i
306
- enable = (arg['status'] == 'True')
307
- @last_emergency_route = route
308
-
309
- if enable
310
- if @emergency_routes.add? route
311
- log "Enabling emergency route #{route}", level: :info
312
- else
313
- log "Emergency route #{route} already enabled", level: :info
314
- end
315
- else
316
- if @emergency_routes.delete? route
317
- log "Disabling emergency route #{route}", level: :info
318
- else
319
- log "Emergency route #{route} already disabled", level: :info
320
- end
321
- end
322
- end
323
-
324
- def input_logic input, change
325
- return unless @input_programming && change != nil
326
- action = @input_programming[input]
327
- return unless action
328
- if action['raise_alarm']
329
- if action['component']
330
- component = node.find_component action['component']
331
- else
332
- component = node.main
333
- end
334
- alarm_code = action['raise_alarm']
335
- if change
336
- log "Activating input #{input} is programmed to raise alarm #{alarm_code} on #{component.c_id}", level: :info
337
- component.activate_alarm alarm_code
338
- else
339
- log "Deactivating input #{input} is programmed to clear alarm #{alarm_code} on #{component.c_id}", level: :info
340
- component.deactivate_alarm alarm_code
341
- end
342
- end
343
- end
344
-
345
- def handle_m0006 arg, options={}
346
- @node.verify_security_code 2, arg['securityCode']
347
- input = arg['input'].to_i
348
- status = string_to_bool arg['status']
349
- unless input>=1 && input<=@inputs.size
350
- raise MessageRejected.new("Input must be in the range 1-#{@inputs.size}")
351
- end
352
- if status
353
- log "Activating input #{input}", level: :info
354
- else
355
- log "Deactivating input #{input}", level: :info
356
- end
357
- change = @inputs.set input, status
358
- input_logic input, change if change != nil
359
- end
360
-
361
- def handle_m0007 arg, options={}
362
- @node.verify_security_code 2, arg['securityCode']
363
- set_fixed_time_control arg['status'], source: 'forced'
364
- end
365
-
366
- def handle_m0012 arg, options={}
367
- @node.verify_security_code 2, arg['securityCode']
368
- end
369
-
370
- def handle_m0013 arg, options={}
371
- @node.verify_security_code 2, arg['securityCode']
372
- set, clear = [], []
373
- arg['status'].split(';').map do |part|
374
- offset, set_bits, clear_bits = part.split(',').map { |i| i.to_i }
375
- set_bits.to_s(2).reverse.each_char.with_index do |bit,i|
376
- set << i + offset if bit == '1'
377
- end
378
- clear_bits.to_s(2).reverse.each_char.with_index do |bit,i|
379
- clear << i + offset if bit == '1'
380
- end
381
- end
382
-
383
- set = set.uniq.sort
384
- clear = clear.uniq.sort
385
-
386
- # if input is both activated and deacticvated, there is no need to acticate first
387
- set -= (set & clear)
388
-
389
- [set,clear].each do |inputs|
390
- inputs.each do |input|
391
- if input<1
392
- raise MessageRejected.new("Cannot acticate inputs #{set} and deactive inputs #{clear}: input #{input} is invalid (must be 1 or higher)"
393
- ) if input<1
394
- end
395
- if input>@inputs.size
396
- raise MessageRejected.new("Cannot acticate inputs #{set} and deactive inputs #{clear}: input #{input} is invalid (only #{@inputs.size} inputs present)")
397
- end
398
- end
399
- end
400
-
401
- log "Activating inputs #{set} and deactivating inputs #{clear}", level: :info
402
-
403
- set.each do |input|
404
- change = @inputs.set input, true
405
- input_logic input, change if change != nil
406
- end
407
- clear.each do |input|
408
- change = @inputs.set input, false
409
- input_logic input, change if change != nil
410
- end
411
- end
412
-
413
- def find_plan plan_nr
414
- plan = @plans[plan_nr.to_i]
415
- raise InvalidMessage.new "unknown signal plan #{plan_nr}, known only [#{@plans.keys.join(', ')}]" unless plan
416
- plan
417
- end
418
-
419
- def handle_m0014 arg, options={}
420
- @node.verify_security_code 2, arg['securityCode']
421
- plan = find_plan arg['plan']
422
- arg['status'].split(',').each do |item|
423
- matched = /(\d+)-(\d+)/.match item
424
- band = matched[1].to_i
425
- value = matched[2].to_i
426
- log "Set plan #{arg['plan']} dynamic band #{band} to #{value}", level: :info
427
- plan.set_band band, value
428
- end
429
- end
430
-
431
- def handle_m0015 arg, options={}
432
- @node.verify_security_code 2, arg['securityCode']
433
- end
434
-
435
- def handle_m0016 arg, options={}
436
- @node.verify_security_code 2, arg['securityCode']
437
- end
438
-
439
- def handle_m0017 arg, options={}
440
- @node.verify_security_code 2, arg['securityCode']
441
- arg['status'].split(',').each do |item|
442
- elems = item.split('-')
443
- nr = elems[0].to_i
444
- plan = elems[1].to_i
445
- hour = elems[2].to_i
446
- min = elems[3].to_i
447
- if nr<0 || nr>12
448
- raise InvalidMessage.new "time table id must be between 0 and 12, got #{nr}"
449
- end
450
- #p "nr: #{nr}, plan #{plan} at #{hour}:#{min}"
451
- @day_time_table[nr] = {plan: plan, hour: hour, min:min}
452
- end
453
- end
454
-
455
- def handle_m0018 arg, options={}
456
- @node.verify_security_code 2, arg['securityCode']
457
- nr = arg['plan'].to_i
458
- cycle_time = arg['status'].to_i
459
- plan = @plans[nr]
460
- raise RSMP::MessageRejected.new "Plan '#{nr}' not found" unless plan
461
- raise RSMP::MessageRejected.new "Cycle time must be greater or equal to zero" if cycle_time < 0
462
- log "Set plan #{nr} cycle time to #{cycle_time}", level: :info
463
- plan.set_cycle_time cycle_time
464
- end
465
-
466
- def string_to_bool bool_str
467
- case bool_str
468
- when 'True'
469
- true
470
- when 'False'
471
- false
472
- else
473
- raise RSMP::MessageRejected.new "Invalid boolean '#{bool}', must be 'True' or 'False'"
474
- end
475
- end
476
-
477
- def bool_string_to_digit bool
478
- case bool
479
- when 'True'
480
- '1'
481
- when 'False'
482
- '0'
483
- else
484
- raise RSMP::MessageRejected.new "Invalid boolean '#{bool}', must be 'True' or 'False'"
485
- end
486
- end
487
-
488
- def bool_to_digit bool
489
- bool ? '1' : '0'
490
- end
491
-
492
- def handle_m0019 arg, options={}
493
- @node.verify_security_code 2, arg['securityCode']
494
- input = arg['input'].to_i
495
- force = string_to_bool arg['status']
496
- forced_value = string_to_bool arg['inputValue']
497
- unless input>=1 && input<=@inputs.size
498
- raise MessageRejected.new("Input must be in the range 1-#{@inputs.size}")
499
- end
500
- if force
501
- log "Forcing input #{input} to #{forced_value}", level: :info
502
- else
503
- log "Releasing input #{input}", level: :info
504
- end
505
- change = @inputs.set_forcing input, force, forced_value
506
-
507
- input_logic input, change if change != nil
508
- end
509
-
510
- def handle_m0020 arg, options={}
511
- @node.verify_security_code 2, arg['securityCode']
512
- end
513
-
514
- def handle_m0021 arg, options={}
515
- @node.verify_security_code 2, arg['securityCode']
516
- end
517
-
518
- def handle_m0022 arg, options={}
519
- id = arg['requestId']
520
- type = arg['type']
521
- priority = @signal_priorities.find { |priority| priority.id == id }
522
- case type
523
- when 'new'
524
- if priority
525
- raise MessageRejected.new("Priority Request #{id} already exists")
526
- else
527
- #ref = arg.slice('signalGroupId','inputId','connectionId','approachId','laneInId','laneOutId')
528
- if arg['signalGroupId']
529
- signal_group = node.find_component arg['signalGroupId']
530
- end
531
-
532
- level = arg['level']
533
- eta = arg['eta']
534
- vehicleType = arg['vehicleType']
535
- @signal_priorities << SignalPriority.new(node:self, id:id, level:level, eta:eta, vehicleType:vehicleType)
536
- log "Priority request #{id} for signal group #{signal_group.c_id} received.", level: :info
537
- end
538
- when 'update'
539
- if priority
540
- log "Updating Priority Request #{id}", level: :info
541
-
542
- else
543
- raise MessageRejected.new("Cannot update priority request #{id}, not found")
544
- end
545
- when 'cancel'
546
- if priority
547
- priority.cancel
548
- log "Priority request with id #{id} cancelled.", level: :info
549
- else
550
- raise MessageRejected.new("Cannot cancel priority request #{id}, not found")
551
- end
552
- else
553
- raise MessageRejected.new("Unknown type #{type}")
554
- end
555
- end
556
-
557
- def handle_m0023 arg, options={}
558
- @node.verify_security_code 2, arg['securityCode']
559
- timeout = arg['status'].to_i
560
- unless timeout>=0 and timeout <= 65535
561
- raise RSMP::MessageRejected.new "Timeout must be in the range 0-65535, got #{timeout}"
562
- end
563
- if timeout == 0
564
- log "Dynamic bands timeout disabled", level: :info
565
- else
566
- log "Dynamic bands timeout set to #{timeout}min", level: :info
567
- end
568
- @dynamic_bands_timeout = timeout
569
- end
570
-
571
- def handle_m0103 arg, options={}
572
- level = {'Level1'=>1,'Level2'=>2}[arg['status']]
573
- @node.change_security_code level, arg['oldSecurityCode'], arg['newSecurityCode']
574
- end
575
-
576
- def handle_m0104 arg, options={}
577
- @node.verify_security_code 1, arg['securityCode']
578
- time = Time.new(
579
- arg['year'],
580
- arg['month'],
581
- arg['day'],
582
- arg['hour'],
583
- arg['minute'],
584
- arg['second'],
585
- 'UTC'
586
- )
587
- clock.set time
588
- log "Clock set to #{time}, (adjustment is #{clock.adjustment}s)", level: :info
589
- end
590
-
591
- def set_input i, value
592
- return unless i>=0 && i<@num_inputs
593
- @inputs[i] = bool_to_digit arg['value']
594
- end
595
-
596
- def set_fixed_time_control status, source:
597
- @fixed_time_control = status
598
- @fixed_time_control_source = source
599
- end
600
-
601
- def switch_plan plan, source:
602
- plan_nr = plan.to_i
603
- if plan_nr == 0
604
- log "Switching to plan selection by time table", level: :info
91
+ send("handle_#{command_code.downcase}", arg, options)
605
92
  else
606
- plan = find_plan plan_nr
607
- log "Switching to plan #{plan_nr}", level: :info
93
+ raise UnknownCommand, "Unknown command #{command_code}"
608
94
  end
609
- @plan = plan_nr
610
- @plan_source = source
611
95
  end
612
96
 
613
- def switch_functional_position mode, timeout: nil, reverting: false, source:
614
- unless ['NormalControl','YellowFlash','Dark'].include? mode
615
- raise RSMP::MessageRejected.new "Invalid functional position #{mode.inspect}, must be NormalControl, YellowFlash or Dark"
616
- end
617
- if reverting
618
- log "Reverting to functional position #{mode} after timeout", level: :info
619
- elsif timeout && timeout > 0
620
- log "Switching to functional position #{mode} with timeout #{(timeout/60).round(1)}min", level: :info
621
- @previous_functional_position = @function_position
622
- now = clock.now
623
- @functional_position_timeout = now + timeout
624
- else
625
- log "Switching to functional position #{mode}", level: :info
626
- end
627
- if mode == 'NormalControl'
628
- initiate_startup_sequence if @function_position != 'NormalControl'
629
- end
630
- @function_position = mode
631
- @function_position_source = source
632
- mode
633
- end
634
-
635
- def get_status code, name=nil, options={}
97
+ def get_status(code, name = nil, options = {})
636
98
  case code
637
99
  when 'S0001', 'S0002', 'S0003', 'S0004', 'S0005', 'S0006', 'S0007',
638
100
  'S0008', 'S0009', 'S0010', 'S0011', 'S0012', 'S0013', 'S0014',
@@ -641,445 +103,11 @@ module RSMP
641
103
  'S0029', 'S0030', 'S0031', 'S0032', 'S0033', 'S0035',
642
104
  'S0091', 'S0092', 'S0095', 'S0096', 'S0097', 'S0098',
643
105
  'S0205', 'S0206', 'S0207', 'S0208'
644
- return send("handle_#{code.downcase}", code, name, options)
106
+ send("handle_#{code.downcase}", code, name, options)
645
107
  else
646
- raise InvalidMessage.new "unknown status code #{code}"
647
- end
648
- end
649
-
650
- def handle_s0001 status_code, status_name=nil, options={}
651
- case status_name
652
- when 'signalgroupstatus'
653
- TrafficControllerSite.make_status format_signal_group_status
654
- when 'cyclecounter'
655
- TrafficControllerSite.make_status @cycle_counter.to_s
656
- when 'basecyclecounter'
657
- TrafficControllerSite.make_status @cycle_counter.to_s
658
- when 'stage'
659
- TrafficControllerSite.make_status 0.to_s
660
- end
661
- end
662
-
663
- def handle_s0002 status_code, status_name=nil, options={}
664
- case status_name
665
- when 'detectorlogicstatus'
666
- TrafficControllerSite.make_status @detector_logics.map { |dl| bool_to_digit(dl.value) }.join
667
- end
668
- end
669
-
670
- def handle_s0003 status_code, status_name=nil, options={}
671
- case status_name
672
- when 'inputstatus'
673
- TrafficControllerSite.make_status @inputs.actual_string
674
- when 'extendedinputstatus'
675
- TrafficControllerSite.make_status 0.to_s
676
- end
677
- end
678
-
679
- def handle_s0004 status_code, status_name=nil, options={}
680
- case status_name
681
- when 'outputstatus'
682
- TrafficControllerSite.make_status 0
683
- when 'extendedoutputstatus'
684
- TrafficControllerSite.make_status 0
685
- end
686
- end
687
-
688
- def handle_s0005 status_code, status_name=nil, options={}
689
- case status_name
690
- when 'status'
691
- TrafficControllerSite.make_status @is_starting
692
- when 'statusByIntersection' # from sxl 1.2.0
693
- TrafficControllerSite.make_status([
694
- {
695
- "intersection"=>"1",
696
- "startup" => TrafficControllerSite.to_rmsp_bool(@is_starting)
697
- }
698
- ])
699
- end
700
- end
701
-
702
- def handle_s0006 status_code, status_name=nil, options={}
703
- if Proxy.version_meets_requirement? options[:sxl_version], '>=1.2.0'
704
- log "S0006 is depreciated, use S0035 instead.", level: :warning
705
- end
706
- status = @emergency_routes.any?
707
- case status_name
708
- when 'status'
709
- TrafficControllerSite.make_status status
710
- when 'emergencystage'
711
- TrafficControllerSite.make_status status ? @last_emergency_route : 0
712
- end
713
- end
714
-
715
- def handle_s0035 status_code, status_name=nil, options={}
716
- case status_name
717
- when 'emergencyroutes'
718
- list = @emergency_routes.sort.map {|route| {'id'=>route.to_s}}
719
- TrafficControllerSite.make_status list
720
- end
721
- end
722
-
723
- def handle_s0007 status_code, status_name=nil, options={}
724
- case status_name
725
- when 'intersection'
726
- TrafficControllerSite.make_status @intersection
727
- when 'status'
728
- TrafficControllerSite.make_status @function_position != 'Dark'
729
- when 'source'
730
- TrafficControllerSite.make_status @function_position_source
731
- end
732
- end
733
-
734
- def handle_s0008 status_code, status_name=nil, options={}
735
- case status_name
736
- when 'intersection'
737
- TrafficControllerSite.make_status @intersection
738
- when 'status'
739
- TrafficControllerSite.make_status @manual_control
740
- when 'source'
741
- TrafficControllerSite.make_status @manual_control_source
742
- end
743
- end
744
-
745
- def handle_s0009 status_code, status_name=nil, options={}
746
- case status_name
747
- when 'intersection'
748
- TrafficControllerSite.make_status @intersection
749
- when 'status'
750
- TrafficControllerSite.make_status @fixed_time_control
751
- when 'source'
752
- TrafficControllerSite.make_status @fixed_time_control_source
753
- end
754
- end
755
-
756
- def handle_s0010 status_code, status_name=nil, options={}
757
- case status_name
758
- when 'intersection'
759
- TrafficControllerSite.make_status @intersection
760
- when 'status'
761
- TrafficControllerSite.make_status @isolated_control
762
- when 'source'
763
- TrafficControllerSite.make_status @isolated_control_source
764
- end
765
- end
766
-
767
- def handle_s0011 status_code, status_name=nil, options={}
768
- case status_name
769
- when 'intersection'
770
- TrafficControllerSite.make_status @intersection
771
- when 'status'
772
- TrafficControllerSite.make_status TrafficControllerSite.to_rmsp_bool( @function_position == 'YellowFlash' )
773
- when 'source'
774
- TrafficControllerSite.make_status @function_position_source
775
- end
776
- end
777
-
778
- def handle_s0012 status_code, status_name=nil, options={}
779
- case status_name
780
- when 'intersection'
781
- TrafficControllerSite.make_status @intersection
782
- when 'status'
783
- TrafficControllerSite.make_status @all_red
784
- when 'source'
785
- TrafficControllerSite.make_status @all_red_source
786
- end
787
- end
788
-
789
- def handle_s0013 status_code, status_name=nil, options={}
790
- case status_name
791
- when 'intersection'
792
- TrafficControllerSite.make_status @intersection
793
- when 'status'
794
- TrafficControllerSite.make_status @police_key
795
- end
796
- end
797
-
798
- def handle_s0014 status_code, status_name=nil, options={}
799
- case status_name
800
- when 'status'
801
- TrafficControllerSite.make_status @plan
802
- when 'source'
803
- TrafficControllerSite.make_status @plan_source
804
- end
805
- end
806
-
807
- def handle_s0015 status_code, status_name=nil, options={}
808
- case status_name
809
- when 'status'
810
- TrafficControllerSite.make_status @traffic_situation
811
- when 'source'
812
- TrafficControllerSite.make_status @traffic_situation_source
813
- end
814
- end
815
-
816
- def handle_s0016 status_code, status_name=nil, options={}
817
- case status_name
818
- when 'number'
819
- TrafficControllerSite.make_status @detector_logics.size
820
- end
821
- end
822
-
823
- def handle_s0017 status_code, status_name=nil, options={}
824
- case status_name
825
- when 'number'
826
- TrafficControllerSite.make_status @signal_groups.size
827
- end
828
- end
829
-
830
- def handle_s0018 status_code, status_name=nil, options={}
831
- case status_name
832
- when 'number'
833
- TrafficControllerSite.make_status @plans.size
834
- end
835
- end
836
-
837
- def handle_s0019 status_code, status_name=nil, options={}
838
- case status_name
839
- when 'number'
840
- TrafficControllerSite.make_status @num_traffic_situations
841
- end
842
- end
843
-
844
- def handle_s0020 status_code, status_name=nil, options={}
845
- case status_name
846
- when 'intersection'
847
- TrafficControllerSite.make_status @intersection
848
- when 'controlmode'
849
- TrafficControllerSite.make_status @control_mode
850
- end
851
- end
852
-
853
- def handle_s0021 status_code, status_name=nil, options={}
854
- case status_name
855
- when 'detectorlogics'
856
- TrafficControllerSite.make_status @detector_logics.map { |logic| bool_to_digit(logic.forced)}.join
857
- end
858
- end
859
-
860
- def handle_s0022 status_code, status_name=nil, options={}
861
- case status_name
862
- when 'status'
863
- TrafficControllerSite.make_status @plans.keys.join(',')
864
- end
865
- end
866
-
867
- def handle_s0023 status_code, status_name=nil, options={}
868
- case status_name
869
- when 'status'
870
- dynamic_bands = @plans.map { |nr,plan| plan.dynamic_bands_string }
871
- str = dynamic_bands.compact.join(',')
872
- TrafficControllerSite.make_status str
873
- end
874
- end
875
-
876
- def handle_s0024 status_code, status_name=nil, options={}
877
- case status_name
878
- when 'status'
879
- TrafficControllerSite.make_status '1-0'
880
- end
881
- end
882
-
883
- def handle_s0026 status_code, status_name=nil, options={}
884
- case status_name
885
- when 'status'
886
- TrafficControllerSite.make_status '0-00'
887
- end
888
- end
889
-
890
- def handle_s0027 status_code, status_name=nil, options={}
891
- case status_name
892
- when 'status'
893
- status = @day_time_table.map do |i,item|
894
- "#{i}-#{item[:plan]}-#{item[:hour]}-#{item[:min]}"
895
- end.join(',')
896
- TrafficControllerSite.make_status status
897
- end
898
- end
899
-
900
- def handle_s0028 status_code, status_name=nil, options={}
901
- case status_name
902
- when 'status'
903
- times = @plans.map {|nr,plan| "#{"%02d" % plan.nr}-#{"%02d" % plan.cycle_time}"}.join(",")
904
- TrafficControllerSite.make_status times
905
- end
906
- rescue StandardError => e
907
- puts e
908
- end
909
-
910
- def handle_s0029 status_code, status_name=nil, options={}
911
- case status_name
912
- when 'status'
913
- TrafficControllerSite.make_status @inputs.forced_string
914
- end
915
- end
916
-
917
- def handle_s0030 status_code, status_name=nil, options={}
918
- case status_name
919
- when 'status'
920
- TrafficControllerSite.make_status ''
921
- end
922
- end
923
-
924
- def handle_s0031 status_code, status_name=nil, options={}
925
- case status_name
926
- when 'status'
927
- TrafficControllerSite.make_status ''
928
- end
929
- end
930
-
931
- def handle_s0032 status_code, status_name=nil, options={}
932
- case status_name
933
- when 'intersection'
934
- TrafficControllerSite.make_status @intersection
935
- when 'status'
936
- TrafficControllerSite.make_status 'local'
937
- when 'source'
938
- TrafficControllerSite.make_status @intersection_source
939
- end
940
- end
941
-
942
- def handle_s0033 status_code, status_name=nil, options={}
943
- case status_name
944
- when 'status'
945
- TrafficControllerSite.make_status get_priority_list
946
- end
947
- end
948
-
949
- def handle_s0091 status_code, status_name=nil, options={}
950
- if Proxy.version_meets_requirement? options[:sxl_version], '>=1.1'
951
- case status_name
952
- when 'user'
953
- TrafficControllerSite.make_status 0
954
- end
955
- else
956
- case status_name
957
- when 'user'
958
- TrafficControllerSite.make_status 'nobody'
959
- when 'status'
960
- TrafficControllerSite.make_status 'logout'
961
- end
962
- end
963
- end
964
-
965
- def handle_s0092 status_code, status_name=nil, options={}
966
- if Proxy.version_meets_requirement? options[:sxl_version], '>=1.1'
967
- case status_name
968
- when 'user'
969
- TrafficControllerSite.make_status 0
970
- end
971
- else
972
- case status_name
973
- when 'user'
974
- TrafficControllerSite.make_status 'nobody'
975
- when 'status'
976
- TrafficControllerSite.make_status 'logout'
977
- end
978
- end
979
- end
980
-
981
- def handle_s0095 status_code, status_name=nil, options={}
982
- case status_name
983
- when 'status'
984
- TrafficControllerSite.make_status RSMP::VERSION
985
- end
986
- end
987
-
988
- def handle_s0096 status_code, status_name=nil, options={}
989
- now = clock.now
990
- case status_name
991
- when 'year'
992
- TrafficControllerSite.make_status now.year.to_s.rjust(4, "0")
993
- when 'month'
994
- TrafficControllerSite.make_status now.month.to_s.rjust(2, "0")
995
- when 'day'
996
- TrafficControllerSite.make_status now.day.to_s.rjust(2, "0")
997
- when 'hour'
998
- TrafficControllerSite.make_status now.hour.to_s.rjust(2, "0")
999
- when 'minute'
1000
- TrafficControllerSite.make_status now.min.to_s.rjust(2, "0")
1001
- when 'second'
1002
- TrafficControllerSite.make_status now.sec.to_s.rjust(2, "0")
1003
- end
1004
- end
1005
-
1006
- def handle_s0097 status_code, status_name=nil, options={}
1007
- case status_name
1008
- when 'checksum'
1009
- TrafficControllerSite.make_status '1'
1010
- when 'timestamp'
1011
- now = clock.to_s
1012
- TrafficControllerSite.make_status now
1013
- end
1014
- end
1015
-
1016
- def handle_s0098 status_code, status_name=nil, options={}
1017
- settings = node.site_settings.slice('components','signal_plans','inputs','startup_sequence')
1018
- json = JSON.generate(settings)
1019
- case status_name
1020
- when 'config'
1021
- TrafficControllerSite.make_status json
1022
- when 'timestamp'
1023
- now = clock.to_s
1024
- TrafficControllerSite.make_status now
1025
- when 'version'
1026
- TrafficControllerSite.make_status Digest::MD5.hexdigest(json)
1027
- end
1028
- end
1029
-
1030
- def handle_s0205 status_code, status_name=nil, options={}
1031
- case status_name
1032
- when 'start'
1033
- TrafficControllerSite.make_status clock.to_s
1034
- when 'vehicles'
1035
- TrafficControllerSite.make_status 0
1036
- end
1037
- end
1038
-
1039
- def handle_s0206 status_code, status_name=nil, options={}
1040
- case status_name
1041
- when 'start'
1042
- TrafficControllerSite.make_status clock.to_s
1043
- when 'speed'
1044
- TrafficControllerSite.make_status 0
1045
- end
1046
- end
1047
-
1048
- def handle_s0207 status_code, status_name=nil, options={}
1049
- case status_name
1050
- when 'start'
1051
- TrafficControllerSite.make_status clock.to_s
1052
- when 'occupancy'
1053
- values = [-1,0,50,100]
1054
- output = @detector_logics.each_with_index.map {|dl,i| values[i%values.size] }.join(",")
1055
- TrafficControllerSite.make_status output
1056
- end
1057
- end
1058
-
1059
- def handle_s0208 status_code, status_name=nil, options={}
1060
- case status_name
1061
- when 'start'
1062
- TrafficControllerSite.make_status clock.to_s
1063
- when 'P'
1064
- TrafficControllerSite.make_status 0
1065
- when 'PS'
1066
- TrafficControllerSite.make_status 0
1067
- when 'L'
1068
- TrafficControllerSite.make_status 0
1069
- when 'LS'
1070
- TrafficControllerSite.make_status 0
1071
- when 'B'
1072
- TrafficControllerSite.make_status 0
1073
- when 'SP'
1074
- TrafficControllerSite.make_status 0
1075
- when 'MC'
1076
- TrafficControllerSite.make_status 0
1077
- when 'C'
1078
- TrafficControllerSite.make_status 0
1079
- when 'F'
1080
- TrafficControllerSite.make_status 0
108
+ raise InvalidMessage, "unknown status code #{code}"
1081
109
  end
1082
110
  end
1083
111
  end
1084
112
  end
1085
- end
113
+ end