rsmp 0.8.5 → 0.9.2

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yaml +21 -0
  3. data/Gemfile.lock +9 -3
  4. data/README.md +2 -12
  5. data/bin/console +1 -1
  6. data/cucumber.yml +1 -0
  7. data/documentation/classes_and_modules.md +4 -4
  8. data/documentation/collecting_message.md +2 -2
  9. data/documentation/tasks.md +149 -0
  10. data/lib/rsmp/archive.rb +3 -3
  11. data/lib/rsmp/cli.rb +32 -4
  12. data/lib/rsmp/collect/aggregated_status_collector.rb +1 -1
  13. data/lib/rsmp/collect/command_response_collector.rb +1 -1
  14. data/lib/rsmp/collect/state_collector.rb +1 -1
  15. data/lib/rsmp/collect/status_collector.rb +2 -1
  16. data/lib/rsmp/components.rb +3 -3
  17. data/lib/rsmp/convert/export/json_schema.rb +4 -4
  18. data/lib/rsmp/convert/import/yaml.rb +1 -1
  19. data/lib/rsmp/deep_merge.rb +1 -0
  20. data/lib/rsmp/error.rb +0 -3
  21. data/lib/rsmp/inspect.rb +1 -1
  22. data/lib/rsmp/logger.rb +5 -5
  23. data/lib/rsmp/logging.rb +1 -1
  24. data/lib/rsmp/message.rb +1 -1
  25. data/lib/rsmp/node.rb +10 -45
  26. data/lib/rsmp/proxy.rb +176 -133
  27. data/lib/rsmp/rsmp.rb +1 -1
  28. data/lib/rsmp/site.rb +23 -60
  29. data/lib/rsmp/site_proxy.rb +21 -17
  30. data/lib/rsmp/supervisor.rb +25 -21
  31. data/lib/rsmp/supervisor_proxy.rb +58 -29
  32. data/lib/rsmp/task.rb +84 -0
  33. data/lib/rsmp/tlc/signal_group.rb +7 -5
  34. data/lib/rsmp/tlc/signal_plan.rb +2 -2
  35. data/lib/rsmp/tlc/traffic_controller.rb +146 -53
  36. data/lib/rsmp/tlc/traffic_controller_site.rb +43 -36
  37. data/lib/rsmp/version.rb +1 -1
  38. data/lib/rsmp.rb +1 -1
  39. metadata +6 -5
  40. data/lib/rsmp/site_proxy_wait.rb +0 -0
  41. data/lib/rsmp/wait.rb +0 -16
  42. data/test.rb +0 -27
@@ -6,7 +6,7 @@ module RSMP
6
6
  # not have dedicated components.
7
7
  class TrafficController < Component
8
8
  attr_reader :pos, :cycle_time, :plan, :cycle_counter,
9
- :yellow_flash, :dark_mode,
9
+ :functional_position,
10
10
  :startup_sequence_active, :startup_sequence, :startup_sequence_pos
11
11
 
12
12
  def initialize node:, id:, cycle_time: 10, signal_plans:,
@@ -23,27 +23,35 @@ module RSMP
23
23
  reset
24
24
  end
25
25
 
26
- def reset
27
- @cycle_counter = 0
28
- @plan = 1
29
- @dark_mode = true
30
- @yellow_flash = false
26
+ def reset_modes
27
+ @function_position = 'NormalControl'
28
+ @previous_functional_position = nil
29
+ @functional_position_timeout = nil
30
+
31
31
  @booting = false
32
+ @is_starting = false
32
33
  @control_mode = 'control'
34
+ @manual_control = false
35
+ @fixed_time_control = false
36
+ @isolated_control = false
37
+ @all_red = false
33
38
  @police_key = 0
39
+ end
40
+
41
+ def reset
42
+ reset_modes
43
+
44
+ @cycle_counter = 0
45
+ @plan = 1
34
46
  @intersection = 0
35
- @is_starting = false
36
47
  @emergency_route = false
37
48
  @emergency_route_number = 0
38
49
  @traffic_situation = 0
39
- @manual_control = false
40
- @fixed_time_control = false
41
- @isolated_control = false
42
- @yellow_flash = false
43
- @all_red = false
44
50
 
45
51
  @inputs = '0'*@num_inputs
46
52
  @input_activations = '0'*@num_inputs
53
+ @input_forced = '0'*@num_inputs
54
+ @input_forced_values = '0'*@num_inputs
47
55
  @input_results = '0'*@num_inputs
48
56
 
49
57
  @day_time_table = {}
@@ -53,6 +61,18 @@ module RSMP
53
61
  @time_int = nil
54
62
  end
55
63
 
64
+ def dark?
65
+ @function_position == 'Dark'
66
+ end
67
+
68
+ def yellow_flash?
69
+ @function_position == 'YellowFlash'
70
+ end
71
+
72
+ def normal_control?
73
+ @function_position == 'NormalControl'
74
+ end
75
+
56
76
  def clock
57
77
  node.clock
58
78
  end
@@ -76,12 +96,15 @@ module RSMP
76
96
 
77
97
  def timer now
78
98
  # TODO use monotone timer, to avoid jumps in case the user sets the system time
79
- @signal_groups.each { |group| group.timer }
80
99
  time = Time.now.to_i
81
100
  return if time == @time_int
82
101
  @time_int = time
83
102
  move_cycle_counter
103
+ check_functional_position_timeout
84
104
  move_startup_sequence if @startup_sequence_active
105
+
106
+ @signal_groups.each { |group| group.timer }
107
+
85
108
  output_states
86
109
  end
87
110
 
@@ -90,6 +113,15 @@ module RSMP
90
113
  @cycle_counter = counter
91
114
  end
92
115
 
116
+ def check_functional_position_timeout
117
+ return unless @functional_position_timeout
118
+ if clock.now >= @functional_position_timeout
119
+ switch_functional_position @previous_functional_position, reverting: true
120
+ @functional_position_timeout = nil
121
+ @previous_functional_position = nil
122
+ end
123
+ end
124
+
93
125
  def startup_state
94
126
  return unless @startup_sequence_active
95
127
  return unless @startup_sequence_pos
@@ -98,6 +130,7 @@ module RSMP
98
130
 
99
131
  def initiate_startup_sequence
100
132
  log "Initiating startup sequence", level: :info
133
+ reset_modes
101
134
  @startup_sequence_active = true
102
135
  @startup_sequence_initiated_at = nil
103
136
  @startup_sequence_pos = nil
@@ -107,9 +140,6 @@ module RSMP
107
140
  @startup_sequence_active = false
108
141
  @startup_sequence_initiated_at = nil
109
142
  @startup_sequence_pos = nil
110
-
111
- @yellow_flash = false
112
- @dark_mode = false
113
143
  end
114
144
 
115
145
  def move_startup_sequence
@@ -127,26 +157,45 @@ module RSMP
127
157
 
128
158
  def output_states
129
159
  return unless @live_output
160
+
130
161
  str = @signal_groups.map do |group|
131
- s = "#{group.c_id}:#{group.state}"
132
- if group.state =~ /^[1-9]$/
162
+ state = group.state
163
+ s = "#{group.c_id}:#{state}"
164
+ if state =~ /^[1-9]$/
133
165
  s.colorize(:green)
134
- elsif group.state =~ /^[NOP]$/
166
+ elsif state =~ /^[NOP]$/
135
167
  s.colorize(:yellow)
136
- elsif group.state =~ /^[ae]$/
137
- s.colorize(:black)
138
- elsif group.state =~ /^[f]$/
168
+ elsif state =~ /^[ae]$/
169
+ s.colorize(:light_black)
170
+ elsif state =~ /^[f]$/
139
171
  s.colorize(:yellow)
140
- elsif group.state =~ /^[g]$/
172
+ elsif state =~ /^[g]$/
141
173
  s.colorize(:red)
142
174
  else
143
175
  s.colorize(:red)
144
176
  end
145
177
  end.join ' '
178
+
179
+ modes = '.'*9
180
+ modes[0] = 'N' if @function_position == 'NormalControl'
181
+ modes[1] = 'Y' if @function_position == 'YellowFlash'
182
+ modes[2] = 'D' if @function_position == 'Dark'
183
+ modes[3] = 'B' if @booting
184
+ modes[4] = 'S' if @startup_sequence_active
185
+ modes[5] = 'M' if @manual_control
186
+ modes[6] = 'F' if @fixed_time_control
187
+ modes[7] = 'R' if @all_red
188
+ modes[8] = 'I' if @isolated_control
189
+ modes[9] = 'P' if @police_key != 0
190
+
146
191
  plan = "P#{@plan}"
147
192
 
193
+ # create folders if needed
194
+ FileUtils.mkdir_p File.dirname(@live_output)
195
+
196
+ # append a line with the current state to the file
148
197
  File.open @live_output, 'w' do |file|
149
- file.puts "#{plan.rjust(4)} #{pos.to_s.rjust(4)} #{str}\r"
198
+ file.puts "#{modes} #{plan.rjust(2)} #{@cycle_counter.to_s.rjust(3)} #{str}\r"
150
199
  end
151
200
  end
152
201
 
@@ -169,7 +218,7 @@ module RSMP
169
218
 
170
219
  def handle_m0001 arg
171
220
  @node.verify_security_code 2, arg['securityCode']
172
- switch_mode arg['status']
221
+ switch_functional_position arg['status'], timeout: arg['timeout'].to_i*60
173
222
  end
174
223
 
175
224
  def handle_m0002 arg
@@ -206,18 +255,27 @@ module RSMP
206
255
  end
207
256
  end
208
257
 
258
+ def recompute_input idx
259
+ if @input_forced[idx] == '1'
260
+ @input_results[idx] = @input_forced_values[idx]
261
+ elsif @input_activations[idx]=='1'
262
+ @input_results[idx] = '1'
263
+ else
264
+ @input_results[idx] = bool_to_digit( @inputs[idx]=='1' )
265
+ end
266
+ end
267
+
209
268
  def handle_m0006 arg
210
269
  @node.verify_security_code 2, arg['securityCode']
211
270
  input = arg['input'].to_i
212
271
  idx = input - 1
213
272
  return unless idx>=0 && input<@num_inputs # TODO should NotAck
214
- @input_activations[idx] = (arg['status']=='True' ? '1' : '0')
215
- result = @input_activations[idx]=='1' || @inputs[idx]=='1'
216
- @input_results[idx] = (result ? '1' : '0')
273
+ @input_activations[idx] = bool_string_to_digit arg['status']
274
+ recompute_input idx
217
275
  if @input_activations[idx]
218
- log "Activate input #{idx}", level: :info
276
+ log "Activating input #{idx+1}", level: :info
219
277
  else
220
- log "Deactivate input #{idx}", level: :info
278
+ log "Deactivating input #{idx+1}", level: :info
221
279
  end
222
280
  end
223
281
 
@@ -280,8 +338,39 @@ module RSMP
280
338
  @node.verify_security_code 2, arg['securityCode']
281
339
  end
282
340
 
341
+ def bool_string_to_digit bool
342
+ case bool
343
+ when 'True'
344
+ '1'
345
+ when 'False'
346
+ '0'
347
+ else
348
+ raise RSMP::MessageRejected.new "Invalid boolean '#{bool}', must be 'True' or 'False'"
349
+ end
350
+ end
351
+
352
+ def bool_to_digit bool
353
+ bool ? '1' : '0'
354
+ end
355
+
283
356
  def handle_m0019 arg
284
357
  @node.verify_security_code 2, arg['securityCode']
358
+ input = arg['input'].to_i
359
+ idx = input - 1
360
+ unless idx>=0 && input<@num_inputs # TODO should NotAck
361
+ log "Can't force input #{idx+1}, only have #{@num_inputs} inputs", level: :warning
362
+ return
363
+ end
364
+ @input_forced[idx] = bool_string_to_digit arg['status']
365
+ if @input_forced[idx]
366
+ @input_forced_values[idx] = bool_string_to_digit arg['inputValue']
367
+ end
368
+ recompute_input idx
369
+ if @input_forced[idx]
370
+ log "Forcing input #{idx+1} to #{@input_forced_values[idx]}, #{@input_results}", level: :info
371
+ else
372
+ log "Releasing input #{idx+1}", level: :info
373
+ end
285
374
  end
286
375
 
287
376
  def handle_m0020 arg
@@ -308,15 +397,15 @@ module RSMP
308
397
  arg['second'],
309
398
  'UTC'
310
399
  )
311
- @node.clock.set time
312
- log "Clock set to #{time}, (adjustment is #{@node.clock.adjustment}s)", level: :info
400
+ clock.set time
401
+ log "Clock set to #{time}, (adjustment is #{clock.adjustment}s)", level: :info
313
402
  end
314
403
 
315
404
  def set_input i, value
316
405
  return unless i>=0 && i<@num_inputs
317
- @inputs[i] = (arg['value'] ? '1' : '0')
406
+ @inputs[i] = bool_to_digit arg['value']
318
407
  end
319
-
408
+
320
409
  def set_fixed_time_control status
321
410
  @fixed_time_control = status
322
411
  end
@@ -332,20 +421,24 @@ module RSMP
332
421
  @plan = plan_nr
333
422
  end
334
423
 
335
- def switch_mode mode
336
- log "Switching to mode #{mode}", level: :info
337
- case mode
338
- when 'NormalControl'
339
- initiate_startup_sequence if @yellow_flash || @dark_mode
340
- @yellow_flash = false
341
- @dark_mode = false
342
- when 'YellowFlash'
343
- @yellow_flash = true
344
- @dark_mode = false
345
- when 'Dark'
346
- @yellow_flash = false
347
- @dark_mode = true
424
+ def switch_functional_position mode, timeout: nil, reverting: false
425
+ unless ['NormalControl','YellowFlash','Dark'].include? mode
426
+ raise RSMP::MessageRejected.new "Invalid functional position '#{mode}', must be NormalControl, YellowFlash or Dark'"
348
427
  end
428
+ if reverting
429
+ log "Reverting to functional position #{mode} after timeout", level: :info
430
+ elsif timeout && timeout > 0
431
+ log "Switching to functional position #{mode} with timeout #{timeout}min", level: :info
432
+ @previous_functional_position = @function_position
433
+ now = clock.now
434
+ @functional_position_timeout = now + timeout
435
+ else
436
+ log "Switching to functional position #{mode}", level: :info
437
+ end
438
+ if mode == 'NormalControl'
439
+ initiate_startup_sequence if @function_position != 'NormalControl'
440
+ end
441
+ @function_position = mode
349
442
  mode
350
443
  end
351
444
 
@@ -380,7 +473,7 @@ module RSMP
380
473
  def handle_s0002 status_code, status_name=nil
381
474
  case status_name
382
475
  when 'detectorlogicstatus'
383
- TrafficControllerSite.make_status @detector_logics.map { |dl| dl.value ? '1' : '0' }.join
476
+ TrafficControllerSite.make_status @detector_logics.map { |dl| bool_to_digit(dl.value) }.join
384
477
  end
385
478
  end
386
479
 
@@ -423,7 +516,7 @@ module RSMP
423
516
  when 'intersection'
424
517
  TrafficControllerSite.make_status @intersection
425
518
  when 'status'
426
- TrafficControllerSite.make_status !@dark_mode
519
+ TrafficControllerSite.make_status @function_position != 'Dark'
427
520
  end
428
521
  end
429
522
 
@@ -459,7 +552,7 @@ module RSMP
459
552
  when 'intersection'
460
553
  TrafficControllerSite.make_status @intersection
461
554
  when 'status'
462
- TrafficControllerSite.make_status @yellow_flash
555
+ TrafficControllerSite.make_status TrafficControllerSite.to_rmsp_bool( @function_position == 'YellowFlash' )
463
556
  end
464
557
  end
465
558
 
@@ -535,7 +628,7 @@ module RSMP
535
628
  def handle_s0021 status_code, status_name=nil
536
629
  case status_name
537
630
  when 'detectorlogics'
538
- TrafficControllerSite.make_status @detector_logics.map { |logic| logic.forced=='True' ? '1' : '0'}.join
631
+ TrafficControllerSite.make_status @detector_logics.map { |logic| bool_to_digit(logic.forced)}.join
539
632
  end
540
633
  end
541
634
 
@@ -589,7 +682,7 @@ module RSMP
589
682
  def handle_s0029 status_code, status_name=nil
590
683
  case status_name
591
684
  when 'status'
592
- TrafficControllerSite.make_status ''
685
+ TrafficControllerSite.make_status @input_forced
593
686
  end
594
687
  end
595
688
 
@@ -655,7 +748,7 @@ module RSMP
655
748
  when 'checksum'
656
749
  TrafficControllerSite.make_status '1'
657
750
  when 'timestamp'
658
- now = @node.clock.to_s
751
+ now = clock.to_s
659
752
  TrafficControllerSite.make_status now
660
753
  end
661
754
  end
@@ -18,6 +18,18 @@ module RSMP
18
18
  unless @main
19
19
  raise ConfigurationError.new "TLC must have a main component"
20
20
  end
21
+
22
+ end
23
+
24
+ def start
25
+ super
26
+ start_tlc_timer
27
+ @main.initiate_startup_sequence
28
+ end
29
+
30
+ def stop_subtasks
31
+ stop_tlc_timer
32
+ super
21
33
  end
22
34
 
23
35
  def build_plans signal_plans
@@ -56,49 +68,45 @@ module RSMP
56
68
  end
57
69
  end
58
70
 
59
- def start_action
60
- super
61
- start_timer
62
- @main.initiate_startup_sequence
63
- end
64
-
65
- def start_timer
71
+ def start_tlc_timer
66
72
  task_name = "tlc timer"
67
73
  log "Starting #{task_name} with interval #{@interval} seconds", level: :debug
68
74
 
69
75
  @timer = @task.async do |task|
70
- task.annotate task_name
71
- next_time = Time.now.to_f
72
- loop do
73
- begin
74
- timer(@clock.now)
75
- rescue EOFError => e
76
- log "Connection closed: #{e}", level: :warning
77
- rescue IOError => e
78
- log "IOError", level: :warning
79
- rescue Errno::ECONNRESET
80
- log "Connection reset by peer", level: :warning
81
- rescue Errno::EPIPE => e
82
- log "Broken pipe", level: :warning
83
- rescue StandardError => e
84
- notify_error e, level: :internal
85
- ensure
86
- # adjust sleep duration to avoid drift. so wake up always happens on the
87
- # same fractional second.
88
- # note that Time.now is not monotonic. If the clock is changed,
89
- # either manaully or via NTP, the sleep interval might jump.
90
- # an alternative is to use ::Process.clock_gettime(::Process::CLOCK_MONOTONIC),
91
- # to get the current time. this ensures a constant interval, but
92
- # if the clock is changed, the wake up would then happen on a different
93
- # fractional second
94
- next_time += @interval
95
- duration = next_time - Time.now.to_f
96
- task.sleep duration
97
- end
76
+ task.annotate task_name
77
+ run_tlc_timer task
78
+ end
79
+ end
80
+
81
+ def run_tlc_timer task
82
+ next_time = Time.now.to_f
83
+ loop do
84
+ begin
85
+ timer(@clock.now)
86
+ rescue StandardError => e
87
+ notify_error e, level: :internal
88
+ ensure
89
+ # adjust sleep duration to avoid drift. so wake up always happens on the
90
+ # same fractional second.
91
+ # note that Time.now is not monotonic. If the clock is changed,
92
+ # either manaully or via NTP, the sleep interval might jump.
93
+ # an alternative is to use ::Process.clock_gettime(::Process::CLOCK_MONOTONIC),
94
+ # to get the current time. this ensures a constant interval, but
95
+ # if the clock is changed, the wake up would then happen on a different
96
+ # fractional second
97
+ next_time += @interval
98
+ duration = next_time - Time.now.to_f
99
+ task.sleep duration
98
100
  end
99
101
  end
100
102
  end
101
103
 
104
+ def stop_tlc_timer
105
+ return unless @timer
106
+ @timer.stop
107
+ @timer = nil
108
+ end
109
+
102
110
  def timer now
103
111
  return unless @main
104
112
  @main.timer now
@@ -142,7 +150,6 @@ module RSMP
142
150
  when :restart
143
151
  log "Restarting TLC", level: :info
144
152
  restart
145
- initiate_startup_sequence
146
153
  end
147
154
  end
148
155
  end
data/lib/rsmp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module RSMP
2
- VERSION = "0.8.5"
2
+ VERSION = "0.9.2"
3
3
  end
data/lib/rsmp.rb CHANGED
@@ -10,10 +10,10 @@ require 'json_schemer'
10
10
  require 'async/queue'
11
11
 
12
12
  require 'rsmp/rsmp'
13
+ require 'rsmp/task'
13
14
  require 'rsmp/deep_merge'
14
15
  require 'rsmp/inspect'
15
16
  require 'rsmp/logging'
16
- require 'rsmp/wait'
17
17
  require 'rsmp/node'
18
18
  require 'rsmp/supervisor'
19
19
  require 'rsmp/components'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rsmp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.5
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emil Tin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-26 00:00:00.000000000 Z
11
+ date: 2022-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -186,6 +186,7 @@ executables:
186
186
  extensions: []
187
187
  extra_rdoc_files: []
188
188
  files:
189
+ - ".github/workflows/rspec.yaml"
189
190
  - ".gitignore"
190
191
  - ".gitmodules"
191
192
  - ".rspec"
@@ -200,9 +201,11 @@ files:
200
201
  - bin/setup
201
202
  - config/supervisor.yaml
202
203
  - config/tlc.yaml
204
+ - cucumber.yml
203
205
  - documentation/classes_and_modules.md
204
206
  - documentation/collecting_message.md
205
207
  - documentation/message_distribution.md
208
+ - documentation/tasks.md
206
209
  - exe/rsmp
207
210
  - lib/rsmp.rb
208
211
  - lib/rsmp/archive.rb
@@ -234,18 +237,16 @@ files:
234
237
  - lib/rsmp/rsmp.rb
235
238
  - lib/rsmp/site.rb
236
239
  - lib/rsmp/site_proxy.rb
237
- - lib/rsmp/site_proxy_wait.rb
238
240
  - lib/rsmp/supervisor.rb
239
241
  - lib/rsmp/supervisor_proxy.rb
242
+ - lib/rsmp/task.rb
240
243
  - lib/rsmp/tlc/detector_logic.rb
241
244
  - lib/rsmp/tlc/signal_group.rb
242
245
  - lib/rsmp/tlc/signal_plan.rb
243
246
  - lib/rsmp/tlc/traffic_controller.rb
244
247
  - lib/rsmp/tlc/traffic_controller_site.rb
245
248
  - lib/rsmp/version.rb
246
- - lib/rsmp/wait.rb
247
249
  - rsmp.gemspec
248
- - test.rb
249
250
  homepage: https://github.com/rsmp-nordic/rsmp
250
251
  licenses:
251
252
  - MIT
File without changes
data/lib/rsmp/wait.rb DELETED
@@ -1,16 +0,0 @@
1
- module RSMP
2
- module Wait
3
- # wait for an async condition to signal, then yield to block
4
- # if block returns true we're done. otherwise, wait again
5
- def wait_for condition, timeout, &block
6
- raise RuntimeError.new("Can't wait for condition because task is not running") unless @task.running?
7
- @task.with_timeout(timeout) do
8
- while @task.running? do
9
- value = condition.wait
10
- result = yield value
11
- return result if result # return result of check, if not nil
12
- end
13
- end
14
- end
15
- end
16
- end
data/test.rb DELETED
@@ -1,27 +0,0 @@
1
- class A
2
- def go &block
3
- @block = block # block will be converted automatically to a Proc
4
- indirect
5
- end
6
-
7
- def call
8
- @block.call
9
- end
10
-
11
- def indirect
12
- call
13
- end
14
-
15
- end
16
-
17
- a = A.new
18
-
19
- a.go do
20
- break # this is ok. break causes the block to exit, and the encasing method to return - go() will exit
21
- end
22
-
23
- # this raises an error. the block we passed to go() will be called again, and it tries to break
24
- # but we're not inside a method we can exit from
25
-
26
-
27
- a.indirect