rsmp 0.1.10 → 0.1.19

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