rsmp 0.8.0 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,26 +8,36 @@ module RSMP
8
8
  @want = want
9
9
  @got = nil
10
10
  @message = nil
11
- @done = false
12
11
  end
13
12
 
14
13
  # Are we done, i.e. did the last checked item match?
15
14
  def done?
16
- @done
15
+ @got != nil
17
16
  end
18
17
 
19
18
  # Check an item and set @done to true if it matches
20
19
  # Store the item and corresponding message if there's a positive or negative match
21
- def perform_match item, message
20
+ def perform_match item, message, block
22
21
  matched = match? item
23
22
  if matched != nil
24
- @message = message
25
- @got = item
26
- @done = matched
23
+ if block
24
+ status = block.call(nil,item)
25
+ matched = status if status == true || status == false
26
+ end
27
27
  end
28
28
  matched
29
29
  end
30
30
 
31
+ def keep message, item
32
+ @message = message
33
+ @got = item
34
+ end
35
+
36
+ def forget
37
+ @message = nil
38
+ @got = nil
39
+ end
40
+
31
41
  def match? item
32
42
  end
33
43
  end
@@ -31,8 +31,7 @@ module RSMP
31
31
  # {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>/\d+/} =>
32
32
  # { <StatusResponse message>, {"cCI"=>"M0104", "cO"=>"setDate", "n"=>"month", "v"=>"9"} }
33
33
  # }
34
-
35
- class Matcher < Collector
34
+ class StateCollector < Collector
36
35
  attr_reader :queries
37
36
 
38
37
  # Initialize with a list of wanted statuses
@@ -62,7 +61,7 @@ module RSMP
62
61
 
63
62
  # Get messages from results
64
63
  def messages
65
- @queries.map { |query| query.message }.uniq
64
+ @queries.map { |query| query.message }.uniq.compact
66
65
  end
67
66
 
68
67
  # Return progress as completes queries vs. total number of queries
@@ -91,22 +90,33 @@ module RSMP
91
90
  # Check if a messages matches our criteria.
92
91
  # Match each query against each item in the message
93
92
  def perform_match message
94
- return unless type_match?(message)
93
+ return false if super(message) == false
94
+ return unless collecting?
95
95
  @queries.each do |query| # look through queries
96
96
  get_items(message).each do |item| # look through items in message
97
- matched = query.perform_match(item,message)
98
- if matched == true
99
- matched = @block.call(message,item) if @block
100
- end
97
+ matched = query.perform_match(item,message,@block)
98
+ return unless collecting?
101
99
  if matched != nil
102
100
  type = {true=>'match',false=>'mismatch'}[matched]
103
101
  @notifier.log "#{@title.capitalize} #{message.m_id_short} collect #{type} #{query.want}, item #{item}", level: :debug
104
- break
102
+ if matched == true
103
+ query.keep message, item
104
+ elsif matched == false
105
+ query.forget
106
+ end
105
107
  end
106
108
  end
107
109
  end
108
110
  complete if done?
109
111
  @notifier.log "#{@title.capitalize} collect reached #{summary}", level: :debug
110
112
  end
113
+
114
+ # don't collect anything. Query will collect them instead
115
+ def keep message
116
+ end
117
+
118
+ def describe
119
+ @queries.map {|q| q.want.to_s }
120
+ end
111
121
  end
112
122
  end
@@ -0,0 +1,20 @@
1
+ module RSMP
2
+ # Base class for waiting for status updates or responses
3
+ class StatusCollector < StateCollector
4
+ def initialize proxy, want, options={}
5
+ super proxy, want, options.merge(title: 'status response', type:['MessageNotAck'])
6
+
7
+ @options[:type] << 'StatusUpdate' unless options[:updates] == false
8
+ @options[:type] << 'StatusResponse' unless options[:reponses] == false
9
+ end
10
+
11
+ def build_query want
12
+ RSMP::StatusQuery.new want
13
+ end
14
+
15
+ # Get items, in our case status values
16
+ def get_items message
17
+ message.attributes['sS'] || []
18
+ end
19
+ end
20
+ end
@@ -1,19 +1,4 @@
1
1
  module RSMP
2
- # Match a specific command responses
3
- class CommandQuery < Query
4
- # Match a return value item against a query
5
- def match? item
6
- return nil if @want['cCI'] && @want['cCI'] != item['cCI']
7
- return nil if @want['n'] && @want['n'] != item['n']
8
- if @want['v'].is_a? Regexp
9
- return false if @want['v'] && item['v'] !~ @want['v']
10
- else
11
- return false if @want['v'] && item['v'] != @want['v']
12
- end
13
- true
14
- end
15
- end
16
-
17
2
  # Match a specific status response or update
18
3
  class StatusQuery < Query
19
4
  # Match a status value against a query
data/lib/rsmp/node.rb CHANGED
@@ -15,7 +15,6 @@ module RSMP
15
15
  @clock = Clock.new
16
16
  @error_queue = Async::Queue.new
17
17
  @ignore_errors = []
18
- options[:collector]
19
18
  @collect = options[:collect]
20
19
  end
21
20
 
data/lib/rsmp/proxy.rb CHANGED
@@ -52,12 +52,6 @@ module RSMP
52
52
  node.clock
53
53
  end
54
54
 
55
- def collect task, options, &block
56
- collector = RSMP::Collector.new self, options
57
- collector.collect task, &block
58
- collector
59
- end
60
-
61
55
  def run
62
56
  start
63
57
  @reader.wait if @reader
@@ -579,25 +573,27 @@ module RSMP
579
573
  end
580
574
 
581
575
  def wait_for_acknowledgement parent_task, options={}, m_id
582
- collect(parent_task,options.merge({
583
- type: ['MessageAck','MessageNotAck'],
584
- num: 1
585
- })) do |message|
576
+ collector = Collector.new self, options.merge(task: parent_task, type: ['MessageAck','MessageNotAck'])
577
+ collector.collect do |message|
586
578
  if message.is_a?(MessageNotAck)
587
579
  if message.attribute('oMId') == m_id
588
- # set result to an exception, but don't raise it.
589
- # this will be returned by the task and stored as the task result
590
- # when the parent task call wait() on the task, the exception
591
- # will be raised in the parent task, and caught by rspec.
592
- # rspec will then show the error and record the test as failed
593
580
  m_id_short = RSMP::Message.shorten_m_id m_id, 8
594
- result = RSMP::MessageRejected.new "Aggregated status request #{m_id_short} was rejected with '#{message.attribute('rea')}'"
595
- next true # done, no more messages wanted
581
+ raise RSMP::MessageRejected.new "Aggregated status request #{m_id_short} was rejected with '#{message.attribute('rea')}'"
596
582
  end
597
583
  elsif message.is_a?(MessageAck)
598
- next true if message.attribute('oMId') == m_id
584
+ collector.complete if message.attribute('oMId') == m_id
599
585
  end
600
- false
586
+ end
587
+ end
588
+
589
+ def send_and_optionally_collect message, options, &block
590
+ if options[:collect]
591
+ task = @task.async { |task| yield task }
592
+ send_message message, validate: options[:validate]
593
+ { sent: message, collector: task.wait }
594
+ else
595
+ send_message message, validate: options[:validate]
596
+ return { sent: message }
601
597
  end
602
598
  end
603
599
  end
data/lib/rsmp/site.rb CHANGED
@@ -9,9 +9,9 @@ module RSMP
9
9
  attr_reader :rsmp_versions, :site_settings, :logger, :proxies
10
10
 
11
11
  def initialize options={}
12
+ super options
12
13
  initialize_components
13
14
  handle_site_settings options
14
- super options
15
15
  @proxies = []
16
16
  @sleep_condition = Async::Notification.new
17
17
  @proxies_condition = Async::Notification.new
@@ -95,7 +95,8 @@ module RSMP
95
95
  ip: supervisor_settings['ip'],
96
96
  port: supervisor_settings['port'],
97
97
  logger: @logger,
98
- archive: @archive
98
+ archive: @archive,
99
+ collect: @collect
99
100
  })
100
101
  @proxies << proxy
101
102
  @proxies_condition.signal
@@ -101,8 +101,13 @@ module RSMP
101
101
  "cId" => component,
102
102
  "mId" => m_id
103
103
  })
104
- send_and_collect_if_needed message, options do |task|
105
- collect_aggregated_status task, options[:collect].merge(m_id: m_id, num:1)
104
+ send_and_optionally_collect message, options do |task|
105
+ collector = AggregatedStatusCollector.new(
106
+ self,
107
+ options[:collect].merge(task:@task,m_id: m_id, num:1)
108
+ )
109
+ collector.collect
110
+ collector
106
111
  end
107
112
  end
108
113
 
@@ -174,8 +179,14 @@ module RSMP
174
179
  "sS" => request_list,
175
180
  "mId" => m_id
176
181
  })
177
- send_and_collect_if_needed message, options do |task|
178
- collect_status_responses task, status_list, options[:collect].merge(m_id: m_id)
182
+ send_and_optionally_collect message, options do |task|
183
+ collector = StatusCollector.new(
184
+ self,
185
+ status_list,
186
+ options[:collect].merge(task:@task,m_id: m_id)
187
+ )
188
+ collector.collect
189
+ collector
179
190
  end
180
191
  end
181
192
 
@@ -186,17 +197,6 @@ module RSMP
186
197
  acknowledge message
187
198
  end
188
199
 
189
- def send_and_collect_if_needed message, options, &block
190
- if options[:collect]
191
- task = @task.async { |task| yield task }
192
- send_message message, validate: options[:validate]
193
- { sent: message, collector: task.wait }
194
- else
195
- send_message message, validate: options[:validate]
196
- return { sent: message }
197
- end
198
- end
199
-
200
200
  def subscribe_to_status component_id, status_list, options={}
201
201
  validate_ready 'subscribe to status'
202
202
  m_id = options[:m_id] || RSMP::Message.make_m_id
@@ -215,8 +215,14 @@ module RSMP
215
215
  "sS" => subscribe_list,
216
216
  'mId' => m_id
217
217
  })
218
- send_and_collect_if_needed message, options do |task|
219
- collect_status_updates task, status_list, options[:collect].merge(m_id: m_id)
218
+ send_and_optionally_collect message, options do |task|
219
+ collector = StatusCollector.new(
220
+ self,
221
+ status_list,
222
+ options[:collect].merge(task:@task,m_id: m_id)
223
+ )
224
+ collector.collect
225
+ collector
220
226
  end
221
227
  end
222
228
 
@@ -261,8 +267,14 @@ module RSMP
261
267
  "arg" => command_list,
262
268
  "mId" => m_id
263
269
  })
264
- send_and_collect_if_needed message, options do |task|
265
- collect_command_responses task, command_list, options[:collect].merge(m_id: m_id)
270
+ send_and_optionally_collect message, options do |task|
271
+ collector = CommandResponseCollector.new(
272
+ self,
273
+ command_list,
274
+ options[:collect].merge(task:@task,m_id: m_id)
275
+ )
276
+ collector.collect
277
+ collector
266
278
  end
267
279
  end
268
280
 
@@ -339,37 +351,13 @@ module RSMP
339
351
  distribute_error e, options
340
352
  end
341
353
 
342
- def collect_alarms parent_task, options={}
343
- collect(parent_task,options.merge(type: "Alarm")) do |alarm|
354
+ def collect_alarms options={}
355
+ collect(@task,options.merge(type: "Alarm")) do |alarm|
344
356
  next if options[:aCId] && options[:aCId] != alarm.attribute("aCId")
345
357
  next if options[:aSp] && options[:aSp] != alarm.attribute("aSp")
346
358
  next if options[:aS] && options[:aS] != alarm.attribute("aS")
347
- true
359
+ :keep
348
360
  end
349
361
  end
350
-
351
- def collect_status_updates task, status_list, options
352
- collector = StatusUpdateMatcher.new(self, status_list, options)
353
- collector.collect task
354
- collector
355
- end
356
-
357
- def collect_status_responses task, status_list, options
358
- collector = StatusResponseMatcher.new(self, status_list, options)
359
- collector.collect task
360
- collector
361
- end
362
-
363
- def collect_command_responses task, command_list, options
364
- collector = CommandResponseMatcher.new(self, command_list, options)
365
- collector.collect task
366
- collector
367
- end
368
-
369
- def collect_aggregated_status task, options
370
- collector = AggregatedStatusMatcher.new(self, options)
371
- collector.collect task
372
- collector
373
- end
374
362
  end
375
363
  end
@@ -138,16 +138,8 @@ module RSMP
138
138
  "mId" => m_id,
139
139
  })
140
140
 
141
- if options[:collect]
142
- result = nil
143
- task = @task.async do |task|
144
- wait_for_acknowledgement task, options[:collect], m_id
145
- end
146
- send_message message, validate: options[:validate]
147
- return message, task.wait
148
- else
149
- send_message message, validate: options[:validate]
150
- message
141
+ send_and_optionally_collect message, options do |task|
142
+ wait_for_acknowledgement task, options[:collect], m_id
151
143
  end
152
144
  end
153
145
 
@@ -282,7 +274,7 @@ module RSMP
282
274
  end
283
275
 
284
276
  def fetch_last_sent_status component, code, name
285
- @last_status_sent.dig component, code, name
277
+ @last_status_sent.dig component, code, name if @last_status_sent
286
278
  end
287
279
 
288
280
  def store_last_sent_status message
@@ -6,25 +6,33 @@ module RSMP
6
6
  # plan is a string, with each character representing a signal phase at a particular second in the cycle
7
7
  def initialize node:, id:
8
8
  super node: node, id: id, grouped: false
9
- move 0
10
9
  end
11
10
 
12
- def get_state pos
11
+ def timer
12
+ @state = get_state
13
+ end
14
+
15
+ def get_state
16
+ return 'a' if node.main.dark_mode
17
+ return 'c' if node.main.yellow_flash
18
+
19
+ cycle_counter = node.main.cycle_counter
20
+
21
+ if node.main.startup_sequence_active
22
+ @state = node.main.startup_state || 'a'
23
+ end
24
+
13
25
  default = 'a' # phase a means disabled/dark
14
26
  plan = node.main.current_plan
15
27
  return default unless plan
16
28
  return default unless plan.states
17
29
  states = plan.states[c_id]
18
30
  return default unless states
19
- state =states[pos]
31
+ state = states[cycle_counter]
20
32
  return default unless state =~ /[a-hA-G0-9N-P]/ # valid signal group states
21
33
  state
22
34
  end
23
35
 
24
- def move pos
25
- @state = get_state pos
26
- end
27
-
28
36
  def handle_command command_code, arg
29
37
  case command_code
30
38
  when 'M0010', 'M0011'
@@ -5,9 +5,12 @@ module RSMP
5
5
  # and keeps track of signal plans, detector logics, inputs, etc. which do
6
6
  # not have dedicated components.
7
7
  class TrafficController < Component
8
- attr_reader :pos, :cycle_time, :plan
8
+ attr_reader :pos, :cycle_time, :plan, :cycle_counter,
9
+ :yellow_flash, :dark_mode,
10
+ :startup_sequence_active, :startup_sequence, :startup_sequence_pos
9
11
 
10
- def initialize node:, id:, cycle_time: 10, signal_plans:
12
+ def initialize node:, id:, cycle_time: 10, signal_plans:,
13
+ startup_sequence:, live_output:nil
11
14
  super node: node, id: id, grouped: true
12
15
  @signal_groups = []
13
16
  @detector_logics = []
@@ -15,13 +18,15 @@ module RSMP
15
18
  @cycle_time = cycle_time
16
19
  @num_traffic_situations = 1
17
20
  @num_inputs = 8
21
+ @startup_sequence = startup_sequence
22
+ @live_output = live_output
18
23
  reset
19
24
  end
20
25
 
21
26
  def reset
22
- @pos = 0
23
- @plan = 0
24
- @dark_mode = false
27
+ @cycle_counter = 0
28
+ @plan = 1
29
+ @dark_mode = true
25
30
  @yellow_flash = false
26
31
  @booting = false
27
32
  @control_mode = 'control'
@@ -40,6 +45,12 @@ module RSMP
40
45
  @inputs = '0'*@num_inputs
41
46
  @input_activations = '0'*@num_inputs
42
47
  @input_results = '0'*@num_inputs
48
+
49
+ @day_time_table = {}
50
+ @startup_sequence_active = false
51
+ @startup_sequence_initiated_at = nil
52
+ @startup_sequence_pos = 0
53
+ @time_int = nil
43
54
  end
44
55
 
45
56
  def clock
@@ -64,46 +75,83 @@ module RSMP
64
75
  end
65
76
 
66
77
  def timer now
67
- # TODO
68
- # We should use a monotone timer, to avoid jumps
69
- # in case the user sets the system time
70
- pos = Time.now.to_i % @cycle_time
71
- if pos != @pos
72
- @pos = pos
73
- move pos
74
- end
78
+ # TODO use monotone timer, to avoid jumps in case the user sets the system time
79
+ @signal_groups.each { |group| group.timer }
80
+ time = Time.now.to_i
81
+ return if time == @time_int
82
+ @time_int = time
83
+ move_cycle_counter
84
+ move_startup_sequence if @startup_sequence_active
85
+ output_states
86
+ end
87
+
88
+ def move_cycle_counter
89
+ counter = Time.now.to_i % @cycle_time
90
+ @cycle_counter = counter
91
+ end
92
+
93
+ def startup_state
94
+ return unless @startup_sequence_active
95
+ return unless @startup_sequence_pos
96
+ @startup_sequence[ @startup_sequence_pos ]
75
97
  end
76
98
 
77
- def move pos
78
- @signal_groups.each do |group|
79
- group.move pos
99
+ def initiate_startup_sequence
100
+ log "Initiating startup sequence", level: :info
101
+ @startup_sequence_active = true
102
+ @startup_sequence_initiated_at = nil
103
+ @startup_sequence_pos = nil
104
+ end
105
+
106
+ def end_startup_sequence
107
+ @startup_sequence_active = false
108
+ @startup_sequence_initiated_at = nil
109
+ @startup_sequence_pos = nil
110
+
111
+ @yellow_flash = false
112
+ @dark_mode = false
113
+ end
114
+
115
+ def move_startup_sequence
116
+ was = @startup_sequence_pos
117
+ if @startup_sequence_initiated_at == nil
118
+ @startup_sequence_initiated_at = Time.now.to_i + 1
119
+ @startup_sequence_pos = 0
120
+ else
121
+ @startup_sequence_pos = Time.now.to_i - @startup_sequence_initiated_at
122
+ end
123
+ if @startup_sequence_pos >= @startup_sequence.size
124
+ end_startup_sequence
80
125
  end
81
- #output_states
82
126
  end
83
127
 
84
128
  def output_states
129
+ return unless @live_output
85
130
  str = @signal_groups.map do |group|
86
131
  s = "#{group.c_id}:#{group.state}"
87
132
  if group.state =~ /^[1-9]$/
88
133
  s.colorize(:green)
89
134
  elsif group.state =~ /^[NOP]$/
90
135
  s.colorize(:yellow)
91
- elsif group.state =~ /^[a]$/
92
- s.colorize(color: :black)
136
+ elsif group.state =~ /^[ae]$/
137
+ s.colorize(:black)
138
+ elsif group.state =~ /^[f]$/
139
+ s.colorize(:yellow)
140
+ elsif group.state =~ /^[g]$/
141
+ s.colorize(:red)
93
142
  else
94
143
  s.colorize(:red)
95
144
  end
96
145
  end.join ' '
97
146
  plan = "P#{@plan}"
98
- print "#{plan.rjust(4)} #{pos.to_s.rjust(4)} #{str}\r"
147
+
148
+ File.open @live_output, 'w' do |file|
149
+ file.puts "#{plan.rjust(4)} #{pos.to_s.rjust(4)} #{str}\r"
150
+ end
99
151
  end
100
152
 
101
153
  def format_signal_group_status
102
- if @yellow_flash
103
- 'c' * @signal_groups.size
104
- else
105
- @signal_groups.map { |group| group.state }.join
106
- end
154
+ @signal_groups.map { |group| group.state }.join
107
155
  end
108
156
 
109
157
  def handle_command command_code, arg
@@ -214,6 +262,18 @@ module RSMP
214
262
 
215
263
  def handle_m0017 arg
216
264
  @node.verify_security_code 2, arg['securityCode']
265
+ arg['status'].split(',').each do |item|
266
+ elems = item.split('-')
267
+ nr = elems[0].to_i
268
+ plan = elems[1].to_i
269
+ hour = elems[2].to_i
270
+ min = elems[3].to_i
271
+ if nr<0 || nr>12
272
+ raise InvalidMessage.new "time table id must be between 0 and 12, got #{nr}"
273
+ end
274
+ #p "nr: #{nr}, plan #{plan} at #{hour}:#{min}"
275
+ @day_time_table[nr] = {plan: plan, hour: hour, min:min}
276
+ end
217
277
  end
218
278
 
219
279
  def handle_m0018 arg
@@ -276,6 +336,7 @@ module RSMP
276
336
  log "Switching to mode #{mode}", level: :info
277
337
  case mode
278
338
  when 'NormalControl'
339
+ initiate_startup_sequence if @yellow_flash || @dark_mode
279
340
  @yellow_flash = false
280
341
  @dark_mode = false
281
342
  when 'YellowFlash'
@@ -308,9 +369,9 @@ module RSMP
308
369
  when 'signalgroupstatus'
309
370
  TrafficControllerSite.make_status format_signal_group_status
310
371
  when 'cyclecounter'
311
- TrafficControllerSite.make_status @pos.to_s
372
+ TrafficControllerSite.make_status @cycle_counter.to_s
312
373
  when 'basecyclecounter'
313
- TrafficControllerSite.make_status @pos.to_s
374
+ TrafficControllerSite.make_status @cycle_counter.to_s
314
375
  when 'stage'
315
376
  TrafficControllerSite.make_status 0.to_s
316
377
  end
@@ -511,7 +572,10 @@ module RSMP
511
572
  def handle_s0027 status_code, status_name=nil
512
573
  case status_name
513
574
  when 'status'
514
- TrafficControllerSite.make_status '00-00-00-00'
575
+ status = @day_time_table.map do |i,item|
576
+ "#{i}-#{item[:plan]}-#{item[:hour]}-#{item[:min]}"
577
+ end.join(',')
578
+ TrafficControllerSite.make_status status
515
579
  end
516
580
  end
517
581
 
@@ -5,11 +5,16 @@ module RSMP
5
5
  attr_accessor :main, :signal_plans
6
6
 
7
7
  def initialize options={}
8
+ # setup options before calling super initializer,
9
+ # since build of components depend on options
8
10
  @sxl = 'traffic_light_controller'
9
11
  @security_codes = options[:site_settings]['security_codes']
10
12
  @interval = options[:site_settings].dig('intervals','timer') || 1
13
+ @startup_sequence = options[:site_settings]['startup_sequence'] || 'efg'
11
14
  build_plans options[:site_settings].dig('signal_plans')
15
+
12
16
  super options
17
+
13
18
  unless @main
14
19
  raise ConfigurationError.new "TLC must have a main component"
15
20
  end
@@ -37,7 +42,9 @@ module RSMP
37
42
  when 'main'
38
43
  @main = TrafficController.new node: self, id: id,
39
44
  cycle_time: settings['cycle_time'],
40
- signal_plans: @signal_plans
45
+ startup_sequence: @startup_sequence,
46
+ signal_plans: @signal_plans,
47
+ live_output: @site_settings['live_output']
41
48
  when 'signal_group'
42
49
  group = SignalGroup.new node: self, id: id
43
50
  @main.add_signal_group group
@@ -52,6 +59,7 @@ module RSMP
52
59
  def start_action
53
60
  super
54
61
  start_timer
62
+ @main.initiate_startup_sequence
55
63
  end
56
64
 
57
65
  def start_timer
@@ -134,6 +142,7 @@ module RSMP
134
142
  when :restart
135
143
  log "Restarting TLC", level: :info
136
144
  restart
145
+ initiate_startup_sequence
137
146
  end
138
147
  end
139
148
  end
data/lib/rsmp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module RSMP
2
- VERSION = "0.8.0"
2
+ VERSION = "0.8.4"
3
3
  end