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