rsmp 0.1.11 → 0.1.21

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