rsmp 0.9.0 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
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