rsmp 0.9.0 → 0.9.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dde978c380def64dfcad00d0a7c9845a7c57476828623664dcf2ace7f3f6b669
4
- data.tar.gz: 6dadc564b21089271b9921f876b95c899ad42c5bbc27b9250d34b47648447937
3
+ metadata.gz: b4eb81e9be58fe5645d41ffe88fad923aac8ba5db14382ce0532d17721b18b0c
4
+ data.tar.gz: 806810e243027d1349631b1b0153c452557e9b7ad01470ff51376c6a181a3810
5
5
  SHA512:
6
- metadata.gz: 7bc25b5125c599a5c46db4c919e8bf6891ddad0631de6264dfe5df97eac5cdf85768f099df8f41bff26203fb6fb15d95f4b09c8e2d4b32e80b24b3065be88d1e
7
- data.tar.gz: 8b58da0f830cc4e4fa7f0906a2d691a3d1548b364c21e320c0f597ce4da873dd16b2db0d5e196c70f21fa9126c24a4e3f4c951586768cad0f99b78047ed7eb18
6
+ metadata.gz: cfa09b0e9692c42999c97866166631e5e805c16741af046b21670a5cdaad862afa9fd532d266877da1c623b0ebb42f4d23854bac9dcb1884e095dabf6027b97a
7
+ data.tar.gz: 493156ab24d418536dd57a5d0a68d008320b9c896e99b33e9ed6023c061f597467569865645895b106f3e13423c686f5c3c89c2177cafbd28a1707c387a5a00c
@@ -1,14 +1,21 @@
1
1
  # This workflow runs RSpec tests
2
2
 
3
3
  name: RSpec
4
- on: [push, pull_request]
4
+ on: [push]
5
5
  jobs:
6
6
  test:
7
- runs-on: ubuntu-latest
7
+ timeout-minutes: 10
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ os: [ubuntu-latest, macos-latest, windows-latest, windows-2022]
12
+ # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
13
+ ruby: ['3.0', '3.1']
14
+ runs-on: ${{ matrix.os }}
8
15
  steps:
9
16
  - uses: actions/checkout@v2
10
17
  - uses: ruby/setup-ruby@v1
11
18
  with:
12
- # ruby-version is not needed because we have a .ruby-version file
19
+ ruby-version: ${{ matrix.ruby }}
13
20
  bundler-cache: true # runs 'bundle install' and caches installed gems automatically
14
- - run: bundle exec rspec
21
+ - run: bundle exec rspec -f d
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rsmp (0.9.0)
4
+ rsmp (0.9.3)
5
5
  async (~> 1.29.1)
6
6
  async-io (~> 1.32.2)
7
7
  colorize (~> 0.8.1)
@@ -64,9 +64,11 @@ GEM
64
64
  ecma-re-validator (0.4.0)
65
65
  regexp_parser (~> 2.2)
66
66
  ffi (1.15.5)
67
+ ffi (1.15.5-x64-mingw-ucrt)
68
+ ffi (1.15.5-x64-mingw32)
67
69
  fiber-local (1.0.0)
68
70
  hana (1.3.7)
69
- json_schemer (0.2.18)
71
+ json_schemer (0.2.19)
70
72
  ecma-re-validator (~> 0.3)
71
73
  hana (~> 1.3)
72
74
  regexp_parser (~> 2.0)
@@ -77,7 +79,7 @@ GEM
77
79
  multi_test (0.1.2)
78
80
  nio4r (2.5.8)
79
81
  rake (13.0.6)
80
- regexp_parser (2.2.0)
82
+ regexp_parser (2.2.1)
81
83
  rsmp_schemer (0.3.2)
82
84
  json_schemer (~> 0.2.18)
83
85
  rspec (3.10.0)
@@ -101,6 +103,9 @@ GEM
101
103
  uri_template (0.7.0)
102
104
 
103
105
  PLATFORMS
106
+ x64-mingw-ucrt
107
+ x64-mingw32
108
+ x86_64-darwin-19
104
109
  x86_64-darwin-20
105
110
  x86_64-darwin-21
106
111
  x86_64-linux
data/README.md CHANGED
@@ -13,16 +13,6 @@ $ gem install bundler
13
13
  $ bundle
14
14
  ```
15
15
 
16
- Install git submodules:
17
- The JSON Schema is is included as a git submodule. To install it:
18
-
19
- ```console
20
- $ git submodule init # initialize local submodule config
21
- $ git submodule update # fetch submodules
22
- ```
23
-
24
- Alternatively, you can pass --recurse-submodules to the git clone command, and it will automatically initialize and update each submodule in the repository.
25
-
26
16
  ## Usage
27
17
  ### Site and Supervisor
28
18
  The RSMP::Site and RSMP::Supervisor classes can be used to run a RSMP site.
data/cucumber.yml ADDED
@@ -0,0 +1 @@
1
+ default: --publish-quiet
data/lib/rsmp/cli.rb CHANGED
@@ -161,5 +161,10 @@ module RSMP
161
161
  RSMP::Convert::Export::JSONSchema.write sxl, options[:out]
162
162
  end
163
163
 
164
+ # avoid Thor returnin 0 on failures, see
165
+ # https://github.com/coinbase/salus/pull/380/files
166
+ def self.exit_on_failure?
167
+ true
168
+ end
164
169
  end
165
170
  end
@@ -0,0 +1,20 @@
1
+ module RSMP
2
+ # Class for waiting for specific command responses
3
+ class AlarmCollector < Collector
4
+ def initialize proxy, want, options={}
5
+ @want = want
6
+ super proxy, options.merge(
7
+ type: 'Alarm',
8
+ title:'alarm'
9
+ )
10
+ end
11
+
12
+ def type_match? message
13
+ return false if super(message) == false
14
+ [:aCId, :aSp, :ack, :aS, :sS, :cat, :pri].each do |key|
15
+ return false if @want[key] && @want[key] != message.attribute(key.to_s)
16
+ end
17
+ true
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ module RSMP
2
+ # Match a specific alarm
3
+ class AlarmQuery < Query
4
+ # Match an alarm value against a query
5
+ def match? item
6
+ return false if @want['n'] && @want['n'] != item['n']
7
+ if @want['v'].is_a? Regexp
8
+ return false if item['v'] !~ @want['v']
9
+ elsif @want['v']
10
+ return false if item['v'] != @want['v']
11
+ end
12
+ true
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,6 @@
1
1
  class Hash
2
2
  def deep_merge(other_hash)
3
+ return self unless other_hash
3
4
  self.merge(other_hash) do |key, old, fresh|
4
5
  if old.is_a?(Hash) && fresh.is_a?(Hash)
5
6
  old.deep_merge(fresh)
@@ -72,7 +72,7 @@ module RSMP
72
72
  else
73
73
  super message
74
74
  end
75
- rescue RSMP::RepeatedAlarmError, RSMP::RepeatedStatusError
75
+ rescue RSMP::RepeatedAlarmError, RSMP::RepeatedStatusError => e
76
76
  str = "Rejected #{message.type} message,"
77
77
  dont_acknowledge message, str, "#{e}"
78
78
  notify_error e.exception("#{str}#{e.message} #{message.json}")
@@ -346,14 +346,5 @@ module RSMP
346
346
  @supervisor.notify_error e, options if @supervisor
347
347
  distribute_error e, options
348
348
  end
349
-
350
- def collect_alarms options={}
351
- collect(@task,options.merge(type: "Alarm")) do |alarm|
352
- next if options[:aCId] && options[:aCId] != alarm.attribute("aCId")
353
- next if options[:aSp] && options[:aSp] != alarm.attribute("aSp")
354
- next if options[:aS] && options[:aS] != alarm.attribute("aS")
355
- :keep
356
- end
357
- end
358
349
  end
359
350
  end
@@ -71,10 +71,13 @@ module RSMP
71
71
  def connect_tcp
72
72
  @endpoint = Async::IO::Endpoint.tcp(@ip, @port)
73
73
 
74
- # Async::IO::Endpoint#connect renames the current task. run in a subtask to avoid this
74
+ # Async::IO::Endpoint#connect renames the current task. run in a subtask to avoid this see issue #22
75
75
  @task.async do |task|
76
76
  task.annotate 'socket task'
77
- @socket = @endpoint.connect
77
+ # this timeout is a workaround for #connect hanging on windows if the other side is not present yet
78
+ task.with_timeout 1.1 do
79
+ @socket = @endpoint.connect
80
+ end
78
81
  end.wait
79
82
 
80
83
  @stream = Async::IO::Stream.new(@socket)
data/lib/rsmp/task.rb CHANGED
@@ -29,7 +29,7 @@ module RSMP
29
29
  end
30
30
 
31
31
  # get the status of our task, or nil of no task
32
- def status
32
+ def task_status
33
33
  @task.status if @task
34
34
  end
35
35
 
@@ -13,8 +13,8 @@ module RSMP
13
13
  end
14
14
 
15
15
  def compute_state
16
- return 'a' if node.main.dark_mode
17
- return 'c' if node.main.yellow_flash
16
+ return 'a' if node.main.dark?
17
+ return 'c' if node.main.yellow_flash?
18
18
 
19
19
  cycle_counter = node.main.cycle_counter
20
20
 
@@ -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:,
@@ -24,15 +24,16 @@ module RSMP
24
24
  end
25
25
 
26
26
  def reset_modes
27
- @dark_mode = true
28
- @yellow_flash = false
27
+ @function_position = 'NormalControl'
28
+ @previous_functional_position = nil
29
+ @functional_position_timeout = nil
30
+
29
31
  @booting = false
30
32
  @is_starting = false
31
33
  @control_mode = 'control'
32
34
  @manual_control = false
33
35
  @fixed_time_control = false
34
36
  @isolated_control = false
35
- @yellow_flash = false
36
37
  @all_red = false
37
38
  @police_key = 0
38
39
  end
@@ -49,6 +50,8 @@ module RSMP
49
50
 
50
51
  @inputs = '0'*@num_inputs
51
52
  @input_activations = '0'*@num_inputs
53
+ @input_forced = '0'*@num_inputs
54
+ @input_forced_values = '0'*@num_inputs
52
55
  @input_results = '0'*@num_inputs
53
56
 
54
57
  @day_time_table = {}
@@ -58,6 +61,18 @@ module RSMP
58
61
  @time_int = nil
59
62
  end
60
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
+
61
76
  def clock
62
77
  node.clock
63
78
  end
@@ -85,6 +100,7 @@ module RSMP
85
100
  return if time == @time_int
86
101
  @time_int = time
87
102
  move_cycle_counter
103
+ check_functional_position_timeout
88
104
  move_startup_sequence if @startup_sequence_active
89
105
 
90
106
  @signal_groups.each { |group| group.timer }
@@ -97,6 +113,15 @@ module RSMP
97
113
  @cycle_counter = counter
98
114
  end
99
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
+
100
125
  def startup_state
101
126
  return unless @startup_sequence_active
102
127
  return unless @startup_sequence_pos
@@ -106,7 +131,6 @@ module RSMP
106
131
  def initiate_startup_sequence
107
132
  log "Initiating startup sequence", level: :info
108
133
  reset_modes
109
- @dark_mode = false
110
134
  @startup_sequence_active = true
111
135
  @startup_sequence_initiated_at = nil
112
136
  @startup_sequence_pos = nil
@@ -116,8 +140,6 @@ module RSMP
116
140
  @startup_sequence_active = false
117
141
  @startup_sequence_initiated_at = nil
118
142
  @startup_sequence_pos = nil
119
- @yellow_flash = false
120
- @dark_mode = false
121
143
  end
122
144
 
123
145
  def move_startup_sequence
@@ -155,18 +177,23 @@ module RSMP
155
177
  end.join ' '
156
178
 
157
179
  modes = '.'*9
158
- modes[0] = 'B' if @booting
159
- modes[1] = 'S' if @startup_sequence_active
160
- modes[2] = 'D' if @dark_mode
161
- modes[3] = 'Y' if @yellow_flash
162
- modes[4] = 'M' if @manual_control
163
- modes[5] = 'F' if @fixed_time_control
164
- modes[6] = 'R' if @all_red
165
- modes[7] = 'I' if @isolated_control
166
- modes[8] = 'P' if @police_key != 0
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
167
190
 
168
191
  plan = "P#{@plan}"
169
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
170
197
  File.open @live_output, 'w' do |file|
171
198
  file.puts "#{modes} #{plan.rjust(2)} #{@cycle_counter.to_s.rjust(3)} #{str}\r"
172
199
  end
@@ -191,7 +218,7 @@ module RSMP
191
218
 
192
219
  def handle_m0001 arg
193
220
  @node.verify_security_code 2, arg['securityCode']
194
- switch_mode arg['status']
221
+ switch_functional_position arg['status'], timeout: arg['timeout'].to_i*60
195
222
  end
196
223
 
197
224
  def handle_m0002 arg
@@ -228,18 +255,27 @@ module RSMP
228
255
  end
229
256
  end
230
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
+
231
268
  def handle_m0006 arg
232
269
  @node.verify_security_code 2, arg['securityCode']
233
270
  input = arg['input'].to_i
234
271
  idx = input - 1
235
272
  return unless idx>=0 && input<@num_inputs # TODO should NotAck
236
- @input_activations[idx] = (arg['status']=='True' ? '1' : '0')
237
- result = @input_activations[idx]=='1' || @inputs[idx]=='1'
238
- @input_results[idx] = (result ? '1' : '0')
273
+ @input_activations[idx] = bool_string_to_digit arg['status']
274
+ recompute_input idx
239
275
  if @input_activations[idx]
240
- log "Activate input #{idx}", level: :info
276
+ log "Activating input #{idx+1}", level: :info
241
277
  else
242
- log "Deactivate input #{idx}", level: :info
278
+ log "Deactivating input #{idx+1}", level: :info
243
279
  end
244
280
  end
245
281
 
@@ -302,8 +338,39 @@ module RSMP
302
338
  @node.verify_security_code 2, arg['securityCode']
303
339
  end
304
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
+
305
356
  def handle_m0019 arg
306
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
307
374
  end
308
375
 
309
376
  def handle_m0020 arg
@@ -330,13 +397,13 @@ module RSMP
330
397
  arg['second'],
331
398
  'UTC'
332
399
  )
333
- @node.clock.set time
334
- 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
335
402
  end
336
403
 
337
404
  def set_input i, value
338
405
  return unless i>=0 && i<@num_inputs
339
- @inputs[i] = (arg['value'] ? '1' : '0')
406
+ @inputs[i] = bool_to_digit arg['value']
340
407
  end
341
408
 
342
409
  def set_fixed_time_control status
@@ -354,20 +421,24 @@ module RSMP
354
421
  @plan = plan_nr
355
422
  end
356
423
 
357
- def switch_mode mode
358
- log "Switching to mode #{mode}", level: :info
359
- case mode
360
- when 'NormalControl'
361
- initiate_startup_sequence if @yellow_flash || @dark_mode
362
- @yellow_flash = false
363
- @dark_mode = false
364
- when 'YellowFlash'
365
- @yellow_flash = true
366
- @dark_mode = false
367
- when 'Dark'
368
- @yellow_flash = false
369
- @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'"
370
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
371
442
  mode
372
443
  end
373
444
 
@@ -402,7 +473,7 @@ module RSMP
402
473
  def handle_s0002 status_code, status_name=nil
403
474
  case status_name
404
475
  when 'detectorlogicstatus'
405
- 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
406
477
  end
407
478
  end
408
479
 
@@ -445,7 +516,7 @@ module RSMP
445
516
  when 'intersection'
446
517
  TrafficControllerSite.make_status @intersection
447
518
  when 'status'
448
- TrafficControllerSite.make_status !@dark_mode
519
+ TrafficControllerSite.make_status @function_position != 'Dark'
449
520
  end
450
521
  end
451
522
 
@@ -481,7 +552,7 @@ module RSMP
481
552
  when 'intersection'
482
553
  TrafficControllerSite.make_status @intersection
483
554
  when 'status'
484
- TrafficControllerSite.make_status @yellow_flash
555
+ TrafficControllerSite.make_status TrafficControllerSite.to_rmsp_bool( @function_position == 'YellowFlash' )
485
556
  end
486
557
  end
487
558
 
@@ -557,7 +628,7 @@ module RSMP
557
628
  def handle_s0021 status_code, status_name=nil
558
629
  case status_name
559
630
  when 'detectorlogics'
560
- 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
561
632
  end
562
633
  end
563
634
 
@@ -611,7 +682,7 @@ module RSMP
611
682
  def handle_s0029 status_code, status_name=nil
612
683
  case status_name
613
684
  when 'status'
614
- TrafficControllerSite.make_status ''
685
+ TrafficControllerSite.make_status @input_forced
615
686
  end
616
687
  end
617
688
 
@@ -677,7 +748,7 @@ module RSMP
677
748
  when 'checksum'
678
749
  TrafficControllerSite.make_status '1'
679
750
  when 'timestamp'
680
- now = @node.clock.to_s
751
+ now = clock.to_s
681
752
  TrafficControllerSite.make_status now
682
753
  end
683
754
  end
data/lib/rsmp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module RSMP
2
- VERSION = "0.9.0"
2
+ VERSION = "0.9.3"
3
3
  end
data/lib/rsmp.rb CHANGED
@@ -25,9 +25,11 @@ require 'rsmp/collect/filter'
25
25
  require 'rsmp/collect/query'
26
26
  require 'rsmp/collect/status_query'
27
27
  require 'rsmp/collect/command_query'
28
+ require 'rsmp/collect/alarm_query'
28
29
  require 'rsmp/collect/status_collector'
29
30
  require 'rsmp/collect/command_response_collector'
30
31
  require 'rsmp/collect/aggregated_status_collector'
32
+ require 'rsmp/collect/alarm_collector'
31
33
  require 'rsmp/component'
32
34
  require 'rsmp/site'
33
35
  require 'rsmp/proxy'
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.9.0
4
+ version: 0.9.3
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-31 00:00:00.000000000 Z
11
+ date: 2022-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -201,6 +201,7 @@ files:
201
201
  - bin/setup
202
202
  - config/supervisor.yaml
203
203
  - config/tlc.yaml
204
+ - cucumber.yml
204
205
  - documentation/classes_and_modules.md
205
206
  - documentation/collecting_message.md
206
207
  - documentation/message_distribution.md
@@ -210,6 +211,8 @@ files:
210
211
  - lib/rsmp/archive.rb
211
212
  - lib/rsmp/cli.rb
212
213
  - lib/rsmp/collect/aggregated_status_collector.rb
214
+ - lib/rsmp/collect/alarm_collector.rb
215
+ - lib/rsmp/collect/alarm_query.rb
213
216
  - lib/rsmp/collect/collector.rb
214
217
  - lib/rsmp/collect/command_query.rb
215
218
  - lib/rsmp/collect/command_response_collector.rb