rsmp 0.1.21 → 0.1.32

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.
@@ -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