rsmp 0.1.21 → 0.1.32

Sign up to get free protection for your applications and to get access to all the features.
@@ -17,47 +17,57 @@ module RSMP
17
17
  @supervisor_settings['site_id']
18
18
  end
19
19
 
20
- def handle_supervisor_settings options
21
- @supervisor_settings = {
20
+ def handle_supervisor_settings options={}
21
+ defaults = {
22
22
  'port' => 12111,
23
- 'rsmp_versions' => ['3.1.1','3.1.2','3.1.3','3.1.4'],
24
- 'timer_interval' => 0.1,
25
- 'watchdog_interval' => 1,
26
- 'watchdog_timeout' => 2,
27
- 'acknowledgement_timeout' => 2,
28
- 'command_response_timeout' => 1,
29
- 'status_response_timeout' => 1,
30
- 'status_update_timeout' => 1,
31
- 'site_connect_timeout' => 2,
32
- 'site_ready_timeout' => 1,
33
- 'stop_after_first_session' => false,
34
- 'sites' => {
35
- :any => {}
23
+ 'ips' => 'all',
24
+ 'guest' => {
25
+ 'rsmp_versions' => 'all',
26
+ 'sxl' => 'tlc',
27
+ 'intervals' => {
28
+ 'timer' => 1,
29
+ 'watchdog' => 1
30
+ },
31
+ 'timeouts' => {
32
+ 'watchdog' => 2,
33
+ 'acknowledgement' => 2
34
+ }
36
35
  }
37
36
  }
38
-
39
- if options[:supervisor_settings]
40
- converted = options[:supervisor_settings].map { |k,v| [k.to_s,v] }.to_h #convert symbol keys to string keys
41
- converted.compact!
42
- @supervisor_settings.merge! converted
43
- end
44
-
45
- required = [:port, :rsmp_versions, :watchdog_interval, :watchdog_timeout,
46
- :acknowledgement_timeout, :command_response_timeout]
47
- check_required_settings @supervisor_settings, required
48
37
 
38
+ # merge options into defaults
39
+ @supervisor_settings = defaults.deep_merge(options[:supervisor_settings] || {})
49
40
  @rsmp_versions = @supervisor_settings["rsmp_versions"]
41
+ check_site_sxl_types
42
+ end
43
+
44
+ def check_site_sxl_types
45
+ sites = @supervisor_settings['sites'].clone || {}
46
+ sites['guest'] = @supervisor_settings['guest']
47
+ sites.each do |site_id,settings|
48
+ unless settings
49
+ raise RSMP::ConfigurationError.new("Configuration for site '#{site_id}' is empty")
50
+ end
51
+ sxl = settings['sxl']
52
+ sxl = 'tlc' unless sxl # temporary fix until configs are updated
53
+ unless sxl
54
+ raise RSMP::ConfigurationError.new("Configuration error for site '#{site_id}': No SXL specified")
55
+ end
56
+ RSMP::Schemer.find_schemas! sxl if sxl
57
+ rescue RSMP::Schemer::UnknownSchemaError => e
58
+ raise RSMP::ConfigurationError.new("Configuration error for site '#{site_id}': #{e}")
59
+ end
50
60
  end
51
61
 
52
62
  def start_action
53
63
  @endpoint = Async::IO::Endpoint.tcp('0.0.0.0', @supervisor_settings["port"])
54
- @endpoint.accept do |socket|
64
+ @endpoint.accept do |socket| # creates async tasks
55
65
  handle_connection(socket)
66
+ rescue StandardError => e
67
+ notify_error e, level: :internal
56
68
  end
57
- rescue SystemCallError => e # all ERRNO errors
58
- log "Exception: #{e.to_s}", level: :error
59
69
  rescue StandardError => e
60
- log ["Exception: #{e.inspect}",e.backtrace].flatten.join("\n"), level: :error
70
+ notify_error e, level: :internal
61
71
  end
62
72
 
63
73
  def stop
@@ -74,16 +84,18 @@ module RSMP
74
84
  remote_hostname = socket.remote_address.ip_address
75
85
  remote_ip = socket.remote_address.ip_address
76
86
 
77
- info = {ip:remote_ip, port:remote_port, hostname:remote_hostname, now:RSMP.now_string()}
87
+ info = {ip:remote_ip, port:remote_port, hostname:remote_hostname, now:Clock.now}
78
88
  if accept? socket, info
79
89
  connect socket, info
80
90
  else
81
91
  reject socket, info
82
92
  end
83
- rescue SystemCallError => e # all ERRNO errors
84
- log "Exception: #{e.to_s}", level: :error
93
+ rescue ConnectionError => e
94
+ log "Rejected connection from #{remote_ip}:#{remote_port}, #{e.to_s}", level: :warning
95
+ notify_error e
85
96
  rescue StandardError => e
86
- log "Exception: #{e}", exception: e, level: :error
97
+ log "Connection: #{e.to_s}", exception: e, level: :error
98
+ notify_error e, level: :internal
87
99
  ensure
88
100
  close socket, info
89
101
  end
@@ -91,7 +103,7 @@ module RSMP
91
103
  def starting
92
104
  log "Starting supervisor on port #{@supervisor_settings["port"]}",
93
105
  level: :info,
94
- timestamp: RSMP.now_object
106
+ timestamp: @clock.now
95
107
  end
96
108
 
97
109
  def accept? socket, info
@@ -110,18 +122,37 @@ module RSMP
110
122
  end
111
123
  end
112
124
 
125
+ def authorize_ip ip
126
+ return if @supervisor_settings['ips'] == 'all'
127
+ return if @supervisor_settings['ips'].include? ip
128
+ raise ConnectionError.new('guest ip not allowed')
129
+ end
130
+
131
+ def check_max_sites
132
+ max = @supervisor_settings['max_sites']
133
+ if max
134
+ if @proxies.size >= max
135
+ raise ConnectionError.new("maximum of #{max} sites already connected")
136
+ end
137
+ end
138
+ end
139
+
113
140
  def connect socket, info
114
141
  log "Site connected from #{format_ip_and_port(info)}",
115
142
  ip: info[:ip],
116
143
  port: info[:port],
117
144
  level: :info,
118
- timestamp: RSMP.now_object
145
+ timestamp: Clock.now
146
+
147
+ authorize_ip info[:ip]
148
+ check_max_sites
119
149
 
120
- settings = @supervisor_settings['sites'][info[:ip]] || @supervisor_settings['sites'][:any]
121
150
  proxy = build_proxy({
122
151
  supervisor: self,
152
+ ip: info[:ip],
153
+ port: info[:port],
123
154
  task: @task,
124
- settings: settings,
155
+ settings: {'collect'=>@supervisor_settings['collect']},
125
156
  socket: socket,
126
157
  info: info,
127
158
  logger: @logger,
@@ -133,7 +164,7 @@ module RSMP
133
164
  @proxies.delete proxy
134
165
  site_ids_changed
135
166
 
136
- stop if @supervisor_settings['stop_after_first_session']
167
+ stop if @supervisor_settings['one_shot']
137
168
  end
138
169
 
139
170
  def site_ids_changed
@@ -146,9 +177,9 @@ module RSMP
146
177
 
147
178
  def close socket, info
148
179
  if info
149
- log "Connection to #{format_ip_and_port(info)} closed", ip: info[:ip], level: :info, timestamp: RSMP.now_object
180
+ log "Connection to #{format_ip_and_port(info)} closed", ip: info[:ip], level: :info, timestamp: Clock.now
150
181
  else
151
- log "Connection closed", level: :info, timestamp: RSMP.now_object
182
+ log "Connection closed", level: :info, timestamp: Clock.now
152
183
  end
153
184
 
154
185
  socket.close
@@ -170,32 +201,36 @@ module RSMP
170
201
  return site if site
171
202
  wait_for(@site_id_condition,timeout) { find_site site_id }
172
203
  rescue Async::TimeoutError
173
- nil
204
+ raise RSMP::TimeoutError.new "Site '#{site_id}' did not connect within #{timeout}s"
174
205
  end
175
206
 
176
207
  def wait_for_site_disconnect site_id, timeout
177
208
  wait_for(@site_id_condition,timeout) { true unless find_site site_id }
178
209
  rescue Async::TimeoutError
179
- false
210
+ raise RSMP::TimeoutError.new "Site '#{site_id}' did not disconnect within #{timeout}s"
180
211
  end
181
212
 
182
213
  def check_site_id site_id
183
214
  check_site_already_connected site_id
184
- return find_allowed_site_setting site_id
215
+ return site_id_to_site_setting site_id
185
216
  end
186
217
 
187
218
  def check_site_already_connected site_id
188
- raise FatalError.new "Site #{site_id} already connected" if find_site(site_id)
219
+ raise FatalError.new "Site '#{site_id}' already connected" if find_site(site_id)
189
220
  end
190
221
 
191
- def find_allowed_site_setting site_id
222
+ def site_id_to_site_setting site_id
192
223
  return {} unless @supervisor_settings['sites']
193
224
  @supervisor_settings['sites'].each_pair do |id,settings|
194
- if id == :any || id == site_id
225
+ if id == 'guest' || id == site_id
195
226
  return settings
196
227
  end
197
228
  end
198
- raise FatalError.new "site id #{site_id} rejected"
229
+ raise FatalError.new "site id #{site_id} unknown"
230
+ end
231
+
232
+ def ip_to_site_settings ip
233
+ @supervisor_settings['sites'][ip] || @supervisor_settings['sites']['guest']
199
234
  end
200
235
 
201
236
  def aggregated_status_changed site_proxy, component
@@ -32,8 +32,8 @@ module RSMP
32
32
  send_version @site_settings['site_id'], @site_settings["rsmp_versions"]
33
33
  rescue Errno::ECONNREFUSED
34
34
  log "No connection to supervisor at #{@ip}:#{@port}", level: :error
35
- unless @site.site_settings["reconnect_interval"] == :no
36
- log "Will try to reconnect again every #{@site.site_settings["reconnect_interval"]} seconds..", level: :info
35
+ unless @site.site_settings['intervals']['reconnect'] == :no
36
+ log "Will try to reconnect again every #{@site.site_settings['intervals']['reconnect']} seconds..", level: :info
37
37
  @logger.mute @ip, @port
38
38
  end
39
39
  end
@@ -49,12 +49,12 @@ module RSMP
49
49
  @endpoint = Async::IO::Endpoint.tcp(@ip, @port)
50
50
  @socket = @endpoint.connect
51
51
  @stream = Async::IO::Stream.new(@socket)
52
- @protocol = Async::IO::Protocol::Line.new(@stream,RSMP::WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
52
+ @protocol = Async::IO::Protocol::Line.new(@stream,WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
53
53
  end
54
54
 
55
55
  def connection_complete
56
56
  super
57
- log "Connection to supervisor established", level: :info
57
+ log "Connection to supervisor established, using core #{@rsmp_version}, #{sxl} #{sxl_version}", level: :info
58
58
  start_watchdog
59
59
  end
60
60
 
@@ -65,6 +65,8 @@ module RSMP
65
65
  when StatusUpdate
66
66
  when AggregatedStatus
67
67
  will_not_handle message
68
+ when AggregatedStatusRequest
69
+ process_aggregated_status_request message
68
70
  when CommandRequest
69
71
  process_command_request message
70
72
  when CommandResponse
@@ -108,7 +110,7 @@ module RSMP
108
110
  end
109
111
 
110
112
  def reconnect_delay
111
- interval = @site_settings["reconnect_interval"]
113
+ interval = @site_settings['intervals']['reconnect']
112
114
  log "Waiting #{interval} seconds before trying to reconnect", level: :info
113
115
  @task.sleep interval
114
116
  end
@@ -123,7 +125,7 @@ module RSMP
123
125
 
124
126
  def send_aggregated_status component
125
127
  message = AggregatedStatus.new({
126
- "aSTS" => RSMP.now_string,
128
+ "aSTS" => clock.to_s,
127
129
  "cId" => component.c_id,
128
130
  "fP" => 'NormalControl',
129
131
  "fS" => nil,
@@ -161,6 +163,14 @@ module RSMP
161
163
  sorted
162
164
  end
163
165
 
166
+ def process_aggregated_status_request message
167
+ log "Received #{message.type}", message: message, level: :log
168
+ component_id = message.attributes["cId"]
169
+ component = @site.find_component component_id
170
+ acknowledge message
171
+ send_aggregated_status component
172
+ end
173
+
164
174
  def process_command_request message
165
175
  log "Received #{message.type}", message: message, level: :log
166
176
  component_id = message.attributes["cId"]
@@ -177,7 +187,7 @@ module RSMP
177
187
  end
178
188
  response = CommandResponse.new({
179
189
  "cId"=>component_id,
180
- "cTS"=>RSMP.now_string,
190
+ "cTS"=>clock.to_s,
181
191
  "rvs"=>rvs
182
192
  })
183
193
  acknowledge message
@@ -194,7 +204,7 @@ module RSMP
194
204
  end
195
205
  response = StatusResponse.new({
196
206
  "cId"=>component_id,
197
- "sTs"=>RSMP.now_string,
207
+ "sTs"=>clock.to_s,
198
208
  "sS"=>sS,
199
209
  "mId" => options[:m_id]
200
210
  })
@@ -221,7 +231,7 @@ module RSMP
221
231
  update_list[component] ||= {}
222
232
 
223
233
  subs = @status_subscriptions[component]
224
- now = RSMP::now_object
234
+ now = Time.now # internal timestamp
225
235
 
226
236
  message.attributes["sS"].each do |arg|
227
237
  sCI = arg["sCI"]
@@ -310,12 +320,10 @@ module RSMP
310
320
  end
311
321
  end
312
322
  send_status_updates update_list
313
- rescue StandardError => e
314
- log ["Status update exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
315
323
  end
316
324
 
317
325
  def send_status_updates update_list
318
- now = RSMP.now_string
326
+ now = clock.to_s
319
327
  update_list.each_pair do |component_id,by_code|
320
328
  component = @site.find_component component_id
321
329
  sS = []
@@ -343,7 +351,7 @@ module RSMP
343
351
 
344
352
  def send_alarm
345
353
  message = Alarm.new({
346
- "aSTS"=>RSMP.now_string,
354
+ "aSTS"=>clock.to_s,
347
355
  "fP"=>nil,
348
356
  "fS"=>nil,
349
357
  "se"=>@site.aggregated_status_bools
data/lib/rsmp/tlc.rb CHANGED
@@ -13,7 +13,6 @@ module RSMP
13
13
  @cycle_time = cycle_time
14
14
  @num_traffic_situations = 1
15
15
  @num_inputs = 8
16
-
17
16
  reset
18
17
  end
19
18
 
@@ -41,6 +40,11 @@ module RSMP
41
40
  @input_results = '0'*@num_inputs
42
41
  end
43
42
 
43
+ def clock
44
+ node.clock
45
+ end
46
+
47
+
44
48
  def add_signal_group group
45
49
  @signal_groups << group
46
50
  end
@@ -48,8 +52,12 @@ module RSMP
48
52
  def add_detector_logic logic
49
53
  @detector_logics << logic
50
54
  end
55
+
51
56
  def timer now
52
- pos = now.to_i % @cycle_time
57
+ # TODO
58
+ # We should use a monotone timer, to avoid jumps
59
+ # in case the user sets the system time
60
+ pos = Time.now.to_i % @cycle_time
53
61
  if pos != @pos
54
62
  @pos = pos
55
63
  move pos
@@ -80,7 +88,11 @@ module RSMP
80
88
  end
81
89
 
82
90
  def format_signal_group_status
83
- @signal_groups.map { |group| group.state }.join
91
+ if @yellow_flash
92
+ 'c' * @signal_groups.size
93
+ else
94
+ @signal_groups.map { |group| group.state }.join
95
+ end
84
96
  end
85
97
 
86
98
  def handle_command command_code, arg
@@ -202,6 +214,17 @@ module RSMP
202
214
 
203
215
  def handle_m0104 arg
204
216
  @node.verify_security_code 1, arg['securityCode']
217
+ time = Time.new(
218
+ arg['year'],
219
+ arg['month'],
220
+ arg['day'],
221
+ arg['hour'],
222
+ arg['minute'],
223
+ arg['second'],
224
+ 'UTC'
225
+ )
226
+ @node.clock.set time
227
+ log "Clock set to #{time}, (adjustment is #{@node.clock.adjustment}s)", level: :info
205
228
  end
206
229
 
207
230
  def set_input i, value
@@ -518,35 +541,37 @@ module RSMP
518
541
  end
519
542
 
520
543
  def handle_s0096 status_code, status_name=nil
544
+ now = clock.now
521
545
  case status_name
522
546
  when 'year'
523
- RSMP::Tlc.make_status RSMP.now_object.year.to_s.rjust(4, "0")
547
+ RSMP::Tlc.make_status now.year.to_s.rjust(4, "0")
524
548
  when 'month'
525
- RSMP::Tlc.make_status RSMP.now_object.month.to_s.rjust(2, "0")
549
+ RSMP::Tlc.make_status now.month.to_s.rjust(2, "0")
526
550
  when 'day'
527
- RSMP::Tlc.make_status RSMP.now_object.day.to_s.rjust(2, "0")
551
+ RSMP::Tlc.make_status now.day.to_s.rjust(2, "0")
528
552
  when 'hour'
529
- RSMP::Tlc.make_status RSMP.now_object.hour.to_s.rjust(2, "0")
553
+ RSMP::Tlc.make_status now.hour.to_s.rjust(2, "0")
530
554
  when 'minute'
531
- RSMP::Tlc.make_status RSMP.now_object.min.to_s.rjust(2, "0")
555
+ RSMP::Tlc.make_status now.min.to_s.rjust(2, "0")
532
556
  when 'second'
533
- RSMP::Tlc.make_status RSMP.now_object.sec.to_s.rjust(2, "0")
557
+ RSMP::Tlc.make_status now.sec.to_s.rjust(2, "0")
534
558
  end
535
559
  end
536
560
 
537
561
  def handle_s0097 status_code, status_name=nil
538
562
  case status_name
539
- when 'version'
540
- RSMP::Tlc.make_status '1'
541
- when 'hash'
563
+ when 'checksum'
542
564
  RSMP::Tlc.make_status '1'
565
+ when 'timestamp'
566
+ now = @node.clock.to_s
567
+ RSMP::Tlc.make_status now
543
568
  end
544
569
  end
545
570
 
546
571
  def handle_s0205 status_code, status_name=nil
547
572
  case status_name
548
573
  when 'start'
549
- RSMP::Tlc.make_status RSMP.now_string
574
+ RSMP::Tlc.make_status clock.to_s
550
575
  when 'vehicles'
551
576
  RSMP::Tlc.make_status 0
552
577
  end
@@ -555,7 +580,7 @@ module RSMP
555
580
  def handle_s0206 status_code, status_name=nil
556
581
  case status_name
557
582
  when 'start'
558
- RSMP::Tlc.make_status RSMP.now_string
583
+ RSMP::Tlc.make_status clock.to_s
559
584
  when 'speed'
560
585
  RSMP::Tlc.make_status 0
561
586
  end
@@ -564,7 +589,7 @@ module RSMP
564
589
  def handle_s0207 status_code, status_name=nil
565
590
  case status_name
566
591
  when 'start'
567
- RSMP::Tlc.make_status RSMP.now_string
592
+ RSMP::Tlc.make_status clock.to_s
568
593
  when 'occupancy'
569
594
  RSMP::Tlc.make_status 0
570
595
  end
@@ -573,7 +598,7 @@ module RSMP
573
598
  def handle_s0208 status_code, status_name=nil
574
599
  case status_name
575
600
  when 'start'
576
- RSMP::Tlc.make_status RSMP.now_string
601
+ RSMP::Tlc.make_status clock.to_s
577
602
  when 'P'
578
603
  RSMP::Tlc.make_status 0
579
604
  when 'PS'
@@ -653,21 +678,22 @@ module RSMP
653
678
  end
654
679
 
655
680
  def handle_s0025 status_code, status_name=nil
681
+ now = @node.clock.to_s
656
682
  case status_name
657
683
  when 'minToGEstimate'
658
- RSMP::Tlc.make_status RSMP.now_string
684
+ RSMP::Tlc.make_status now
659
685
  when 'maxToGEstimate'
660
- RSMP::Tlc.make_status RSMP.now_string
686
+ RSMP::Tlc.make_status now
661
687
  when 'likelyToGEstimate'
662
- RSMP::Tlc.make_status RSMP.now_string
688
+ RSMP::Tlc.make_status now
663
689
  when 'ToGConfidence'
664
690
  RSMP::Tlc.make_status 0
665
691
  when 'minToREstimate'
666
- RSMP::Tlc.make_status RSMP.now_string
692
+ RSMP::Tlc.make_status now
667
693
  when 'maxToREstimate'
668
- RSMP::Tlc.make_status RSMP.now_string
694
+ RSMP::Tlc.make_status now
669
695
  when 'likelyToREstimate'
670
- RSMP::Tlc.make_status RSMP.now_string
696
+ RSMP::Tlc.make_status now
671
697
  when 'ToRConfidence'
672
698
  RSMP::Tlc.make_status 0
673
699
  end
@@ -695,7 +721,7 @@ module RSMP
695
721
  def handle_s0201 status_code, status_name=nil
696
722
  case status_name
697
723
  when 'starttime'
698
- RSMP::Tlc.make_status RSMP.now_string
724
+ RSMP::Tlc.make_status @node.clock.to_s
699
725
  when 'vehicles'
700
726
  RSMP::Tlc.make_status 0
701
727
  end
@@ -704,7 +730,7 @@ module RSMP
704
730
  def handle_s0202 status_code, status_name=nil
705
731
  case status_name
706
732
  when 'starttime'
707
- RSMP::Tlc.make_status RSMP.now_string
733
+ RSMP::Tlc.make_status @node.clock.to_s
708
734
  when 'speed'
709
735
  RSMP::Tlc.make_status 0
710
736
  end
@@ -713,7 +739,7 @@ module RSMP
713
739
  def handle_s0203 status_code, status_name=nil
714
740
  case status_name
715
741
  when 'starttime'
716
- RSMP::Tlc.make_status RSMP.now_string
742
+ RSMP::Tlc.make_status @node.clock.to_s
717
743
  when 'occupancy'
718
744
  RSMP::Tlc.make_status 0
719
745
  end
@@ -722,7 +748,7 @@ module RSMP
722
748
  def handle_s0204 status_code, status_name=nil
723
749
  case status_name
724
750
  when 'starttime'
725
- RSMP::Tlc.make_status RSMP.now_string
751
+ RSMP::Tlc.make_status @node.clock.to_s
726
752
  when 'P'
727
753
  RSMP::Tlc.make_status 0
728
754
  when 'PS'
@@ -767,11 +793,12 @@ module RSMP
767
793
  end
768
794
 
769
795
  class Tlc < Site
796
+ attr_accessor :main
770
797
  def initialize options={}
771
798
  super options
772
799
  @sxl = 'traffic_light_controller'
773
800
  @security_codes = options[:site_settings]['security_codes']
774
- @interval = options[:site_settings]['interval'] || 1
801
+ @interval = options[:site_settings]['intervals']['timer'] || 1
775
802
  unless @main
776
803
  raise ConfigurationError.new "TLC must have a main component"
777
804
  end
@@ -806,22 +833,21 @@ module RSMP
806
833
  next_time = Time.now.to_f
807
834
  loop do
808
835
  begin
809
- now = RSMP.now_object
810
- timer(now)
836
+ timer(@clock.now)
811
837
  rescue EOFError => e
812
- log "TLC timer: Connection closed: #{e}", level: :warning
838
+ log "Connection closed: #{e}", level: :warning
813
839
  rescue IOError => e
814
- log "TLC timer: IOError", level: :warning
840
+ log "IOError", level: :warning
815
841
  rescue Errno::ECONNRESET
816
- log "TLC timer: Connection reset by peer", level: :warning
842
+ log "Connection reset by peer", level: :warning
817
843
  rescue Errno::EPIPE => e
818
- log "TLC timer: Broken pipe", level: :warning
844
+ log "Broken pipe", level: :warning
819
845
  rescue StandardError => e
820
- log "TLC timer: #{e}", level: :debug
846
+ notify_error e, level: :internal
821
847
  ensure
822
848
  # adjust sleep duration to avoid drift. so wake up always happens on the
823
849
  # same fractional second.
824
- # note that Time.now is not monotonic. If the clock si changed,
850
+ # note that Time.now is not monotonic. If the clock is changed,
825
851
  # either manaully or via NTP, the sleep interval might jump.
826
852
  # an alternative is to use ::Process.clock_gettime(::Process::CLOCK_MONOTONIC),
827
853
  # to get the current time. this ensures a constant interval, but