rsmp 0.37.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/devcontainer.json +22 -0
  3. data/.github/workflows/rubocop.yaml +17 -0
  4. data/.gitignore +5 -6
  5. data/.rubocop.yml +80 -0
  6. data/Gemfile +13 -1
  7. data/Gemfile.lock +34 -1
  8. data/Rakefile +3 -3
  9. data/lib/rsmp/cli.rb +147 -124
  10. data/lib/rsmp/collect/ack_collector.rb +8 -7
  11. data/lib/rsmp/collect/aggregated_status_collector.rb +4 -4
  12. data/lib/rsmp/collect/alarm_collector.rb +31 -23
  13. data/lib/rsmp/collect/alarm_matcher.rb +3 -3
  14. data/lib/rsmp/collect/collector/logging.rb +17 -0
  15. data/lib/rsmp/collect/collector/reporting.rb +44 -0
  16. data/lib/rsmp/collect/collector/status.rb +34 -0
  17. data/lib/rsmp/collect/collector.rb +69 -150
  18. data/lib/rsmp/collect/command_matcher.rb +19 -6
  19. data/lib/rsmp/collect/command_response_collector.rb +7 -7
  20. data/lib/rsmp/collect/distributor.rb +14 -11
  21. data/lib/rsmp/collect/filter.rb +31 -15
  22. data/lib/rsmp/collect/matcher.rb +7 -11
  23. data/lib/rsmp/collect/queue.rb +4 -4
  24. data/lib/rsmp/collect/receiver.rb +10 -12
  25. data/lib/rsmp/collect/state_collector.rb +116 -77
  26. data/lib/rsmp/collect/status_collector.rb +6 -6
  27. data/lib/rsmp/collect/status_matcher.rb +17 -7
  28. data/lib/rsmp/{alarm_state.rb → component/alarm_state.rb} +76 -37
  29. data/lib/rsmp/{component.rb → component/component.rb} +15 -15
  30. data/lib/rsmp/component/component_base.rb +89 -0
  31. data/lib/rsmp/component/component_proxy.rb +75 -0
  32. data/lib/rsmp/component/components.rb +63 -0
  33. data/lib/rsmp/convert/export/json_schema.rb +116 -110
  34. data/lib/rsmp/convert/import/yaml.rb +21 -18
  35. data/lib/rsmp/{rsmp.rb → helpers/clock.rb} +5 -6
  36. data/lib/rsmp/{deep_merge.rb → helpers/deep_merge.rb} +2 -1
  37. data/lib/rsmp/helpers/error.rb +71 -0
  38. data/lib/rsmp/{inspect.rb → helpers/inspect.rb} +6 -10
  39. data/lib/rsmp/log/archive.rb +98 -0
  40. data/lib/rsmp/log/colorization.rb +41 -0
  41. data/lib/rsmp/log/filtering.rb +54 -0
  42. data/lib/rsmp/log/logger.rb +206 -0
  43. data/lib/rsmp/{logging.rb → log/logging.rb} +5 -7
  44. data/lib/rsmp/message.rb +159 -148
  45. data/lib/rsmp/{node.rb → node/node.rb} +19 -17
  46. data/lib/rsmp/{protocol.rb → node/protocol.rb} +5 -3
  47. data/lib/rsmp/node/site/site.rb +195 -0
  48. data/lib/rsmp/node/supervisor/modules/configuration.rb +59 -0
  49. data/lib/rsmp/node/supervisor/modules/connection.rb +140 -0
  50. data/lib/rsmp/node/supervisor/modules/sites.rb +64 -0
  51. data/lib/rsmp/node/supervisor/supervisor.rb +72 -0
  52. data/lib/rsmp/{task.rb → node/task.rb} +12 -14
  53. data/lib/rsmp/proxy/modules/acknowledgements.rb +144 -0
  54. data/lib/rsmp/proxy/modules/receive.rb +119 -0
  55. data/lib/rsmp/proxy/modules/send.rb +76 -0
  56. data/lib/rsmp/proxy/modules/state.rb +25 -0
  57. data/lib/rsmp/proxy/modules/tasks.rb +105 -0
  58. data/lib/rsmp/proxy/modules/versions.rb +69 -0
  59. data/lib/rsmp/proxy/modules/watchdogs.rb +66 -0
  60. data/lib/rsmp/proxy/proxy.rb +199 -0
  61. data/lib/rsmp/proxy/site/modules/aggregated_status.rb +52 -0
  62. data/lib/rsmp/proxy/site/modules/alarms.rb +27 -0
  63. data/lib/rsmp/proxy/site/modules/commands.rb +31 -0
  64. data/lib/rsmp/proxy/site/modules/status.rb +110 -0
  65. data/lib/rsmp/proxy/site/site_proxy.rb +205 -0
  66. data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +47 -0
  67. data/lib/rsmp/proxy/supervisor/modules/alarms.rb +73 -0
  68. data/lib/rsmp/proxy/supervisor/modules/commands.rb +53 -0
  69. data/lib/rsmp/proxy/supervisor/modules/status.rb +204 -0
  70. data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +178 -0
  71. data/lib/rsmp/tlc/detector_logic.rb +18 -34
  72. data/lib/rsmp/tlc/input_states.rb +126 -0
  73. data/lib/rsmp/tlc/modules/detector_logics.rb +50 -0
  74. data/lib/rsmp/tlc/modules/display.rb +78 -0
  75. data/lib/rsmp/tlc/modules/helpers.rb +41 -0
  76. data/lib/rsmp/tlc/modules/inputs.rb +173 -0
  77. data/lib/rsmp/tlc/modules/modes.rb +253 -0
  78. data/lib/rsmp/tlc/modules/outputs.rb +30 -0
  79. data/lib/rsmp/tlc/modules/plans.rb +218 -0
  80. data/lib/rsmp/tlc/modules/signal_groups.rb +109 -0
  81. data/lib/rsmp/tlc/modules/startup_sequence.rb +22 -0
  82. data/lib/rsmp/tlc/modules/system.rb +140 -0
  83. data/lib/rsmp/tlc/modules/traffic_data.rb +49 -0
  84. data/lib/rsmp/tlc/signal_group.rb +37 -41
  85. data/lib/rsmp/tlc/signal_plan.rb +14 -11
  86. data/lib/rsmp/tlc/signal_priority.rb +39 -35
  87. data/lib/rsmp/tlc/startup_sequence.rb +59 -0
  88. data/lib/rsmp/tlc/traffic_controller.rb +38 -1010
  89. data/lib/rsmp/tlc/traffic_controller_site.rb +58 -57
  90. data/lib/rsmp/version.rb +1 -1
  91. data/lib/rsmp.rb +82 -48
  92. data/rsmp.gemspec +24 -31
  93. metadata +79 -139
  94. data/lib/rsmp/archive.rb +0 -76
  95. data/lib/rsmp/collect/message_matchers.rb +0 -0
  96. data/lib/rsmp/component_base.rb +0 -87
  97. data/lib/rsmp/component_proxy.rb +0 -57
  98. data/lib/rsmp/components.rb +0 -65
  99. data/lib/rsmp/error.rb +0 -71
  100. data/lib/rsmp/logger.rb +0 -216
  101. data/lib/rsmp/proxy.rb +0 -693
  102. data/lib/rsmp/site.rb +0 -188
  103. data/lib/rsmp/site_proxy.rb +0 -389
  104. data/lib/rsmp/supervisor.rb +0 -302
  105. data/lib/rsmp/supervisor_proxy.rb +0 -510
  106. data/lib/rsmp/tlc/inputs.rb +0 -134
@@ -0,0 +1,140 @@
1
+ module RSMP
2
+ module TLC
3
+ module Modules
4
+ # System-level commands and status for traffic controllers
5
+ # Handles restart, emergency routes, security, clock, version, and configuration
6
+ module System
7
+ def clock
8
+ node.clock
9
+ end
10
+
11
+ # M0004 - Restart traffic light controller
12
+ def handle_m0004(arg, _options = {})
13
+ @node.verify_security_code 2, arg['securityCode']
14
+ # don't restart immeediately, since we need to first send command response
15
+ # instead, defer an action, which will be handled by the TLC site
16
+ log 'Sheduling restart of TLC', level: :info
17
+ @node.defer :restart
18
+ end
19
+
20
+ # M0103 - Set security code
21
+ def handle_m0103(arg, _options = {})
22
+ level = { 'Level1' => 1, 'Level2' => 2 }[arg['status']]
23
+ @node.change_security_code level, arg['oldSecurityCode'], arg['newSecurityCode']
24
+ end
25
+
26
+ # M0104 - Set clock
27
+ def handle_m0104(arg, _options = {})
28
+ @node.verify_security_code 1, arg['securityCode']
29
+ time = Time.new(
30
+ arg['year'],
31
+ arg['month'],
32
+ arg['day'],
33
+ arg['hour'],
34
+ arg['minute'],
35
+ arg['second'],
36
+ 'UTC'
37
+ )
38
+ clock.set time
39
+ log "Clock set to #{time}, (adjustment is #{clock.adjustment}s)", level: :info
40
+ end
41
+
42
+ # S0005 - Traffic controller starting
43
+ def handle_s0005(_status_code, status_name = nil, _options = {})
44
+ case status_name
45
+ when 'status'
46
+ TrafficControllerSite.make_status @is_starting
47
+ when 'statusByIntersection' # from sxl 1.2.0
48
+ TrafficControllerSite.make_status([
49
+ {
50
+ 'intersection' => '1',
51
+ 'startup' => TrafficControllerSite.to_rmsp_bool(@is_starting)
52
+ }
53
+ ])
54
+ end
55
+ end
56
+
57
+ # S0095 - Version information
58
+ def handle_s0095(_status_code, status_name = nil, _options = {})
59
+ case status_name
60
+ when 'status'
61
+ TrafficControllerSite.make_status RSMP::VERSION
62
+ end
63
+ end
64
+
65
+ # S0091 - Operator logged in/out OP-panel
66
+ def handle_s0091(_status_code, status_name = nil, _options = {})
67
+ case status_name
68
+ when 'user'
69
+ TrafficControllerSite.make_status 0
70
+ when 'username'
71
+ TrafficControllerSite.make_status ''
72
+ end
73
+ end
74
+
75
+ # S0092 - Operator logged in/out web-interface
76
+ def handle_s0092(_status_code, status_name = nil, _options = {})
77
+ case status_name
78
+ when 'user'
79
+ TrafficControllerSite.make_status 0
80
+ when 'username'
81
+ TrafficControllerSite.make_status ''
82
+ end
83
+ end
84
+
85
+ def datetime_components
86
+ {
87
+ 'year' => [4, :year],
88
+ 'month' => [2, :month],
89
+ 'day' => [2, :day],
90
+ 'hour' => [2, :hour],
91
+ 'minute' => [2, :min],
92
+ 'second' => [2, :sec]
93
+ }
94
+ end
95
+
96
+ # S0096 - Current date and time
97
+ def handle_s0096(_status_code, status_name = nil, _options = {})
98
+ component = datetime_components[status_name]
99
+ return nil unless component
100
+
101
+ width, method = component
102
+ now = clock.now
103
+ TrafficControllerSite.make_status format_datetime_component(now.send(method), width)
104
+ end
105
+
106
+ private
107
+
108
+ def format_datetime_component(value, width)
109
+ value.to_s.rjust(width, '0')
110
+ end
111
+
112
+ # S0097 - Configuration checksum
113
+ def handle_s0097(_status_code, status_name = nil, _options = {})
114
+ case status_name
115
+ when 'checksum'
116
+ TrafficControllerSite.make_status '1'
117
+ when 'timestamp'
118
+ now = clock.to_s
119
+ TrafficControllerSite.make_status now
120
+ end
121
+ end
122
+
123
+ # S0098 - Configuration data
124
+ def handle_s0098(_status_code, status_name = nil, _options = {})
125
+ settings = node.site_settings.slice('components', 'signal_plans', 'inputs', 'startup_sequence')
126
+ json = JSON.generate(settings)
127
+ case status_name
128
+ when 'config'
129
+ TrafficControllerSite.make_status json
130
+ when 'timestamp'
131
+ now = clock.to_s
132
+ TrafficControllerSite.make_status now
133
+ when 'version'
134
+ TrafficControllerSite.make_status Digest::MD5.hexdigest(json)
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,49 @@
1
+ module RSMP
2
+ module TLC
3
+ module Modules
4
+ # Traffic counting data collection for TLC
5
+ # Handles aggregate traffic counting status for all detectors
6
+ module TrafficData
7
+ # S0205 - Traffic Counting: Number of vehicles (aggregate)
8
+ def handle_s0205(_status_code, status_name = nil, _options = {})
9
+ case status_name
10
+ when 'start'
11
+ TrafficControllerSite.make_status clock.to_s
12
+ when 'vehicles'
13
+ TrafficControllerSite.make_status 0
14
+ end
15
+ end
16
+
17
+ # S0206 - Traffic Counting: Vehicle speed (aggregate)
18
+ def handle_s0206(_status_code, status_name = nil, _options = {})
19
+ case status_name
20
+ when 'start'
21
+ TrafficControllerSite.make_status clock.to_s
22
+ when 'speed'
23
+ TrafficControllerSite.make_status 0
24
+ end
25
+ end
26
+
27
+ # S0207 - Traffic Counting: Occupancy (aggregate)
28
+ def handle_s0207(_status_code, status_name = nil, _options = {})
29
+ case status_name
30
+ when 'start'
31
+ TrafficControllerSite.make_status clock.to_s
32
+ when 'occupancy'
33
+ TrafficControllerSite.make_status 0
34
+ end
35
+ end
36
+
37
+ # S0208 - Traffic Counting: Number of vehicles by classification (aggregate)
38
+ def handle_s0208(_status_code, status_name = nil, _options = {})
39
+ case status_name
40
+ when 'start'
41
+ TrafficControllerSite.make_status clock.to_s
42
+ when 'P', 'PS', 'L', 'LS', 'B', 'SP', 'MC', 'C', 'F'
43
+ TrafficControllerSite.make_status 0
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -4,8 +4,8 @@ module RSMP
4
4
  attr_reader :plan, :state
5
5
 
6
6
  # plan is a string, with each character representing a signal phase at a particular second in the cycle
7
- def initialize node:, id:
8
- super node: node, id: id, grouped: false
7
+ def initialize(node:, id:)
8
+ super(node: node, id: id, grouped: false)
9
9
  end
10
10
 
11
11
  def timer
@@ -15,82 +15,78 @@ module RSMP
15
15
  def compute_state
16
16
  return 'a' if node.main.dark?
17
17
  return 'c' if node.main.yellow_flash?
18
+ return startup_state if node.main.startup_sequence.active?
18
19
 
19
- cycle_counter = node.main.cycle_counter
20
+ compute_plan_state
21
+ end
20
22
 
21
- if node.main.startup_sequence_active
22
- return node.main.startup_state || 'a'
23
- end
23
+ def startup_state
24
+ node.main.startup_state || 'a'
25
+ end
24
26
 
25
- default = 'a' # phase a means disabled/dark
27
+ def compute_plan_state
28
+ default = 'a' # phase a means disabled/dark
26
29
  plan = node.main.current_plan
27
- return default unless plan
28
- return default unless plan.states
30
+ return default unless plan&.states
29
31
 
30
32
  states = plan.states[c_id]
31
33
  return default unless states
32
34
 
33
- counter = [cycle_counter, states.length-1].min
35
+ state_at_cycle_position(states, node.main.cycle_counter, default)
36
+ end
37
+
38
+ def state_at_cycle_position(states, cycle_counter, default)
39
+ counter = [cycle_counter, states.length - 1].min
34
40
  state = states[counter]
35
- return default unless state =~ /[a-hA-G0-9N-P]/ # valid signal group states
41
+ return default unless state =~ /[a-hA-G0-9N-P]/ # valid signal group states
42
+
36
43
  state
37
44
  end
38
45
 
39
- def handle_command command_code, arg, options={}
46
+ def handle_command(command_code, arg, options = {})
40
47
  case command_code
41
48
  when 'M0010', 'M0011'
42
- return send("handle_#{command_code.downcase}", arg, options)
49
+ send("handle_#{command_code.downcase}", arg, options)
43
50
  else
44
- raise UnknownCommand.new "Unknown command #{command_code}"
51
+ raise UnknownCommand, "Unknown command #{command_code}"
45
52
  end
46
53
  end
47
54
 
48
55
  # Start of signal group. Orders a signal group to green
49
- def handle_m0010 arg, options={}
56
+ def handle_m0010(arg, _options = {})
50
57
  @node.verify_security_code 2, arg['securityCode']
51
- if TrafficControllerSite.from_rsmp_bool arg['status']
52
- log "Start signal group #{c_id}, go to green", level: :info
53
- end
58
+ return unless TrafficControllerSite.from_rsmp_bool? arg['status']
59
+
60
+ log "Start signal group #{c_id}, go to green", level: :info
54
61
  end
55
62
 
56
63
  # Stop of signal group. Orders a signal group to red
57
- def handle_m0011 arg, options={}
64
+ def handle_m0011(arg, _options = {})
58
65
  @node.verify_security_code 2, arg['securityCode']
59
- if TrafficControllerSite.from_rsmp_bool arg['status']
60
- log "Stop signal group #{c_id}, go to red", level: :info
61
- end
66
+ return unless TrafficControllerSite.from_rsmp_bool? arg['status']
67
+
68
+ log "Stop signal group #{c_id}, go to red", level: :info
62
69
  end
63
70
 
64
- def get_status code, name=nil, options={}
71
+ def get_status(code, name = nil, _options = {})
65
72
  case code
66
73
  when 'S0025'
67
- return send("handle_#{code.downcase}", code, name)
74
+ send("handle_#{code.downcase}", code, name)
68
75
  else
69
- raise InvalidMessage.new "unknown status code #{code}"
76
+ raise InvalidMessage, "unknown status code #{code}"
70
77
  end
71
78
  end
72
79
 
73
- def handle_s0025 status_code, status_name=nil, options={}
80
+ def handle_s0025(_status_code, status_name = nil, _options = {})
74
81
  now = @node.clock.to_s
75
82
  case status_name
76
- when 'minToGEstimate'
77
- TrafficControllerSite.make_status now
78
- when 'maxToGEstimate'
79
- TrafficControllerSite.make_status now
80
- when 'likelyToGEstimate'
81
- TrafficControllerSite.make_status now
82
- when 'ToGConfidence'
83
- TrafficControllerSite.make_status 0
84
- when 'minToREstimate'
85
- TrafficControllerSite.make_status now
86
- when 'maxToREstimate'
87
- TrafficControllerSite.make_status now
88
- when 'likelyToREstimate'
83
+ when 'minToGEstimate', 'maxToGEstimate', 'likelyToGEstimate',
84
+ 'minToREstimate', 'maxToREstimate', 'likelyToREstimate'
89
85
  TrafficControllerSite.make_status now
90
- when 'ToRConfidence'
86
+ when 'ToGConfidence', 'ToRConfidence'
91
87
  TrafficControllerSite.make_status 0
92
88
  end
93
89
  end
94
90
  end
95
91
  end
96
- end
92
+ end
@@ -4,32 +4,35 @@ module RSMP
4
4
  # A signal plan is a description of how all signal groups should change
5
5
  # state over time.
6
6
  class SignalPlan
7
- attr_reader :nr, :states, :dynamic_bands, :cycle_time
8
- def initialize nr:, cycle_time:, states:, dynamic_bands:
9
- @nr = nr
7
+ attr_reader :number, :states, :dynamic_bands, :cycle_time
8
+
9
+ def initialize(number:, cycle_time:, states:, dynamic_bands:)
10
+ @number = number
10
11
  @states = states
11
12
  @dynamic_bands = dynamic_bands || {}
12
13
  @cycle_time = cycle_time
13
14
  end
14
15
 
15
16
  def dynamic_bands_string
16
- str = @dynamic_bands.map { |band,value| "#{nr}-#{band}-#{value}" }.join(',')
17
+ str = @dynamic_bands.map { |band, value| "#{@number}-#{band}-#{value}" }.join(',')
17
18
  return nil if str == ''
19
+
18
20
  str
19
21
  end
20
22
 
21
- def set_band band, value
22
- @dynamic_bands[ band.to_i ] = value.to_i
23
+ def set_band(band, value)
24
+ @dynamic_bands[band.to_i] = value.to_i
23
25
  end
24
26
 
25
- def get_band band
26
- @dynamic_bands[ band.to_i ]
27
+ def get_band(band)
28
+ @dynamic_bands[band.to_i]
27
29
  end
28
30
 
29
- def set_cycle_time cycle_time
30
- raise ArgumentError if cycle_time < 0
31
+ def cycle_time=(cycle_time)
32
+ raise ArgumentError if cycle_time.negative?
33
+
31
34
  @cycle_time = cycle_time
32
35
  end
33
36
  end
34
37
  end
35
- end
38
+ end
@@ -1,44 +1,48 @@
1
- class RSMP::TLC::SignalPriority
2
- attr_reader :state, :node, :id, :level, :eta, :vehicleType, :age, :updated
1
+ module RSMP
2
+ module TLC
3
+ class SignalPriority
4
+ attr_reader :state, :node, :id, :level, :eta, :vehicle_type, :age, :updated
3
5
 
4
- def initialize node:, id:, level:, eta:, vehicleType:
5
- @node = node
6
- @id = id
7
- @level = level
8
- @eta = eta
9
- @vehicleType = vehicleType
10
- set_state 'received'
11
- end
6
+ def initialize(node:, id:, level:, eta:, vehicle_type:)
7
+ @node = node
8
+ @id = id
9
+ @level = level
10
+ @eta = eta
11
+ @vehicle_type = vehicle_type
12
+ self.state = 'received'
13
+ end
12
14
 
13
- def prune?
14
- @state == 'stale' || @state == 'completed'
15
- end
15
+ def prune?
16
+ @state == 'stale' || @state == 'completed'
17
+ end
16
18
 
17
- def cancel
18
- if @state == 'activated'
19
- set_state 'completed'
20
- end
21
- end
19
+ def cancel
20
+ return unless @state == 'activated'
22
21
 
23
- def set_state state
24
- @state = state
25
- @updated = node.clock.now
26
- @node.signal_priority_changed self, @state
27
- end
22
+ self.state = 'completed'
23
+ end
28
24
 
29
- def timer
30
- @age = @node.clock.now - @updated
31
- case @state
32
- when 'received'
33
- if @age >= 0.5
34
- @node.log "Priority request #{@id} activated.", level: :info
35
- set_state 'activated'
25
+ def state=(state)
26
+ @state = state
27
+ @updated = node.clock.now
28
+ @node.signal_priority_changed self, @state
36
29
  end
37
- when 'activated'
38
- if @age >= 1
39
- @node.log "Priority request #{@id} became stale.", level: :info
40
- set_state 'stale'
30
+
31
+ def timer
32
+ @age = @node.clock.now - @updated
33
+ case @state
34
+ when 'received'
35
+ if @age >= 0.5
36
+ @node.log "Priority request #{@id} activated.", level: :info
37
+ self.state = 'activated'
38
+ end
39
+ when 'activated'
40
+ if @age >= 1
41
+ @node.log "Priority request #{@id} became stale.", level: :info
42
+ self.state = 'stale'
43
+ end
44
+ end
41
45
  end
42
46
  end
43
47
  end
44
- end
48
+ end
@@ -0,0 +1,59 @@
1
+ module RSMP
2
+ module TLC
3
+ # Manages startup sequence state machine for traffic controllers
4
+ class StartupSequence
5
+ attr_reader :sequence, :position, :initiated_at
6
+
7
+ def initialize(sequence)
8
+ @sequence = sequence || []
9
+ @active = false
10
+ @initiated_at = nil
11
+ @position = nil
12
+ end
13
+
14
+ def start
15
+ @active = true
16
+ @initiated_at = nil
17
+ @position = nil
18
+ end
19
+
20
+ def stop
21
+ @active = false
22
+ @initiated_at = nil
23
+ @position = nil
24
+ end
25
+
26
+ def active?
27
+ @active
28
+ end
29
+
30
+ def complete?
31
+ return false unless @active
32
+ return false if @position.nil?
33
+
34
+ @position >= @sequence.size
35
+ end
36
+
37
+ def current_state
38
+ return nil unless @active
39
+ return nil if @position.nil?
40
+ return nil if @position >= @sequence.size
41
+
42
+ @sequence[@position]
43
+ end
44
+
45
+ def advance
46
+ return unless @active
47
+
48
+ if @initiated_at.nil?
49
+ @initiated_at = Time.now.to_i + 1
50
+ @position = 0
51
+ else
52
+ @position = Time.now.to_i - @initiated_at
53
+ end
54
+
55
+ stop if complete?
56
+ end
57
+ end
58
+ end
59
+ end