rsmp 0.1.17 → 0.1.19

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: 1b583271d38fc35e2badabf94b0a8a764f07a8e36025c2902922d926adbf320b
4
- data.tar.gz: 9e37dd8939d68a19e1cf16580c558c813e8a5d6214a733cc8dce08fe4957be59
3
+ metadata.gz: bfbb10fc77baf015a50f7ce94b2ee3b0830a09f8c083b9f0dfbb86fcd4285893
4
+ data.tar.gz: 8756ab6010d199fdc2e22973ab960156b66f000c7a8feacad201f5be5bb0ff08
5
5
  SHA512:
6
- metadata.gz: 1af3ecb44fc6c9e9fa0735b524c1561eafd91ef05775c0be325c8093635354e535d9d8f024d05cbc256859319a13795dfff34233165b8b57c53ac82fb76dbbd5
7
- data.tar.gz: aeaa068f63614bcd8e3af873400e5af54810c51e7a1941d62834c1bd2d94a3ff4ae62e59d89ad3242749fff54126aa3ccabf3bf3ac672f29c9cac9611ebddd47
6
+ metadata.gz: af59d9680e6717f8d0e89cae43e1a95aa17ad322c54024c4dae91bb8e58216d7fc57c727c3e1d56ac9c6770131f2197f6708cceba890b0b83867a493b410111e
7
+ data.tar.gz: 20597a90bc6c9abaf196ffba19d233fa76716c2febee7af2c0c057337953d40d77ac965a671716ed5e645fb93734265231def5104efc7a39a6e92fade86a5ce7
data/.gitignore CHANGED
@@ -12,5 +12,6 @@ log
12
12
  /spec/reports/
13
13
  /tmp/
14
14
  /lib/rsmp_schema/
15
+ /config/private/
15
16
 
16
17
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rsmp (0.1.17)
4
+ rsmp (0.1.19)
5
5
  async (~> 1.23.0)
6
6
  async-io (~> 1.27.4)
7
7
  colorize (~> 0.8.1)
@@ -22,13 +22,14 @@ GEM
22
22
  console (~> 1.0)
23
23
  nio4r (~> 2.3)
24
24
  timers (~> 4.1)
25
- async-io (1.27.5)
25
+ async-io (1.27.7)
26
26
  async (~> 1.14)
27
27
  backports (3.17.0)
28
28
  builder (3.2.4)
29
29
  childprocess (3.0.0)
30
30
  colorize (0.8.1)
31
- console (1.8.2)
31
+ console (1.10.1)
32
+ fiber-local
32
33
  contracts (0.16.0)
33
34
  cucumber (3.1.2)
34
35
  builder (>= 2.1.2)
@@ -47,21 +48,22 @@ GEM
47
48
  cucumber-tag_expressions (1.1.1)
48
49
  cucumber-wire (0.0.1)
49
50
  diff-lcs (1.3)
50
- ecma-re-validator (0.2.0)
51
- regexp_parser (~> 1.2)
51
+ ecma-re-validator (0.3.0)
52
+ regexp_parser (~> 2.0)
52
53
  ffi (1.12.2)
54
+ fiber-local (1.0.0)
53
55
  gherkin (5.1.0)
54
- hana (1.3.5)
55
- json_schemer (0.2.11)
56
- ecma-re-validator (~> 0.2)
56
+ hana (1.3.7)
57
+ json_schemer (0.2.17)
58
+ ecma-re-validator (~> 0.3)
57
59
  hana (~> 1.3)
58
- regexp_parser (~> 1.5)
60
+ regexp_parser (~> 2.0)
59
61
  uri_template (~> 0.7)
60
62
  multi_json (1.14.1)
61
63
  multi_test (0.1.2)
62
- nio4r (2.5.2)
64
+ nio4r (2.5.4)
63
65
  rake (13.0.1)
64
- regexp_parser (1.7.0)
66
+ regexp_parser (2.0.0)
65
67
  rspec (3.9.0)
66
68
  rspec-core (~> 3.9.0)
67
69
  rspec-expectations (~> 3.9.0)
@@ -77,7 +79,7 @@ GEM
77
79
  rspec-support (3.9.2)
78
80
  thor (1.0.1)
79
81
  timecop (0.9.1)
80
- timers (4.3.0)
82
+ timers (4.3.2)
81
83
  uri_template (0.7.0)
82
84
 
83
85
  PLATFORMS
data/README.md CHANGED
@@ -178,7 +178,7 @@ Use ```--config <path>``` to point to a .yaml config file, controlling things li
178
178
  ### RSpec
179
179
  RSpec tests are located in spec/. The tests will start supervisor and sites to test communication, but will do so on port 13111, rather than the usual port 12111, to avoid inferference with other RMSP processes running locally.
180
180
 
181
- Note that these tests are NOT intented for testing external equipment or systems. The tests are for validating the code in this repository. To test external equipment or systems.
181
+ Note that these tests are NOT intented for testing external equipment or systems. The tests are for validating the code in this repository. To test external equipment or systems use the rsmp_validator tool.
182
182
 
183
183
  ```console
184
184
  $ rspec
@@ -18,14 +18,15 @@ components:
18
18
  plan: 'BBB1NB'
19
19
  detector_logic:
20
20
  DL1:
21
- watchdog_interval: 1
22
- watchdog_timeout: 2
23
- acknowledgement_timeout: 1
24
- command_response_timeout: 1
25
- status_response_timeout: 1
26
- status_update_timeout: 1
21
+ watchdog_interval: 0.1
22
+ watchdog_timeout: 0.2
23
+ acknowledgement_timeout: 0.2
27
24
  reconnect_interval: 0.1
28
25
 
26
+ security_codes:
27
+ 1: '1111'
28
+ 2: '2222'
29
+
29
30
  log:
30
31
  active: true
31
32
  color: true
@@ -0,0 +1,62 @@
1
+ # Classes
2
+
3
+ ## Class tree:
4
+ ```
5
+
6
+ .------ Base -------.
7
+ / \
8
+ / \
9
+ / SiteBase module \
10
+ Node / \ Proxy
11
+ / \ / \ / \
12
+ Super Site SiteProxy SupervisorProxy
13
+
14
+ ```
15
+
16
+
17
+ ## Base
18
+ The Base class handle logging.
19
+
20
+ Node and Site inherit from Base.
21
+
22
+ ## Node
23
+ A Node has an async task. A node can be started and stopped.
24
+
25
+ Node has two child classes: Site and Supervisor.
26
+
27
+ ## Site
28
+ A Site represents an RSMP site, typically a traffic light, variable message sign, or other type of field equipment. An RSMP site can connect to one or more supervisors.
29
+
30
+ A Site has one or more SupervisorProxies (connections to supervisor).
31
+
32
+ A site has one of more components.
33
+
34
+ ## Supervisor
35
+ A Supervisor represents an RSMP supervisor, typically a central supervisor system. An RSMP supervisor can handle connections one or more sites.
36
+
37
+ A Supervisor has one or more SiteProxies (connections to sites).
38
+
39
+ ## Proxy
40
+ A Proxy represents a connection to a remove Site or Supervisor and handles the RSMP interface.
41
+
42
+ A proxy has an async task listening for messages on an TCP/IP socket. Incoming RSMP messages are parsing and appropriate handles are called.
43
+
44
+ A proxy also has a repaating async timer task for handling watchdog and acknowledgement timeouts.
45
+
46
+ Proxy has to child classes: SiteProxy and SupervisorProxy.
47
+
48
+ ## SiteProxy
49
+ A connection to a remote Site.
50
+
51
+ Handles RSMP messaging specific to a supervisor, including methods for requesting status, sending commands, etc.
52
+
53
+ A SiteProxy has one or more components, representing the components in the remote site.
54
+
55
+ ## Supervisor Proxy
56
+ A connection to a remote Site. Handles RSMP messaging specific to a site, including sending aggregated status, handling status requests, status subscription and command requests.
57
+
58
+ Status and command requests are delegated to the appropriate components.
59
+
60
+ ## SiteBase
61
+ Things shared between Site and SiteProxy, mainly handling components.
62
+
@@ -6,6 +6,8 @@ module RSMP
6
6
  attr_reader :items
7
7
  attr_accessor :probes
8
8
 
9
+ @@index = 0
10
+
9
11
  def initialize
10
12
  @items = []
11
13
  @probes = ProbeCollection.new
@@ -27,6 +29,14 @@ module RSMP
27
29
  cleaned
28
30
  end
29
31
 
32
+ def self.increase_index
33
+ @@index += 1
34
+ end
35
+
36
+ def self.current_index
37
+ @@index
38
+ end
39
+
30
40
  def by_level levels
31
41
  items.select { |item| levels.include? item[:level] }
32
42
  end
@@ -35,12 +45,8 @@ module RSMP
35
45
  items.map { |item| item[:str] }
36
46
  end
37
47
 
38
- def current_index
39
- @items.size
40
- end
41
-
42
48
  def add item
43
- item[:index] = @items.size
49
+ item[:index] = RSMP::Archive.increase_index
44
50
  @items << item
45
51
  probe item
46
52
  end
@@ -23,6 +23,7 @@ module RSMP
23
23
  def clear_aggregated_status
24
24
  @aggregated_status = []
25
25
  @aggregated_status_bools = Array.new(8,false)
26
+ @aggregated_status_bools[5] = true
26
27
  end
27
28
 
28
29
  def set_aggregated_status status
@@ -23,7 +23,7 @@ module RSMP
23
23
  class MissingWatchdog < Error
24
24
  end
25
25
 
26
- class MissingAcknowledgment < Error
26
+ class MessageRejected < Error
27
27
  end
28
28
 
29
29
  class MissingAttribute < InvalidMessage
@@ -27,6 +27,9 @@ module RSMP
27
27
 
28
28
  @@schemas = load_schemas
29
29
 
30
+ def self.make_m_id
31
+ SecureRandom.uuid()
32
+ end
30
33
 
31
34
  def self.parse_attributes json
32
35
  raise ArgumentError unless json
@@ -80,8 +83,12 @@ module RSMP
80
83
  @attributes["mId"]
81
84
  end
82
85
 
86
+ def self.shorten_m_id m_id, length=4
87
+ m_id[0..length-1]
88
+ end
89
+
83
90
  def m_id_short
84
- @attributes["mId"][0..3]
91
+ Message.shorten_m_id @attributes["mId"]
85
92
  end
86
93
 
87
94
  def attribute key
@@ -126,7 +133,7 @@ module RSMP
126
133
 
127
134
  def ensure_message_id
128
135
  # if message id is empty, generate a new one
129
- @attributes["mId"] ||= SecureRandom.uuid()
136
+ @attributes["mId"] ||= Message.make_m_id
130
137
  end
131
138
 
132
139
  def validate sxl=nil
@@ -197,6 +204,22 @@ module RSMP
197
204
  end
198
205
  end
199
206
 
207
+ class AlarmRequest < Message
208
+ def initialize attributes = {}
209
+ super({
210
+ "type" => "Alarm",
211
+ }.merge attributes)
212
+ end
213
+ end
214
+
215
+ class AlarmAcknowledged < Message
216
+ def initialize attributes = {}
217
+ super({
218
+ "type" => "Alarm",
219
+ }.merge attributes)
220
+ end
221
+ end
222
+
200
223
  class Watchdog < Message
201
224
  def initialize attributes = {}
202
225
  super({
@@ -5,19 +5,46 @@
5
5
 
6
6
  module RSMP
7
7
  class Node < Base
8
- attr_reader :archive, :logger, :task
8
+ include Wait
9
+
10
+ attr_reader :archive, :logger, :task, :deferred
9
11
 
10
12
  def initialize options
11
13
  super options
14
+ @task = options[:task]
15
+ @deferred = []
16
+ end
17
+
18
+ def defer item
19
+ @deferred << item
20
+ end
21
+
22
+ def process_deferred
23
+ cloned = @deferred.clone # clone in case do_deferred restarts the current task
24
+ @deferred.clear
25
+ cloned.each do |item|
26
+ do_deferred item
27
+ end
28
+ end
29
+
30
+ def do_deferred item
31
+ end
32
+
33
+ def do_start task
34
+ task.annotate self.class.to_s
35
+ @task = task
36
+ start_action
37
+ idle
12
38
  end
13
39
 
14
40
  def start
15
41
  starting
16
- Async do |task|
17
- task.annotate self.class
18
- @task = task
19
- start_action
20
- idle
42
+ if @task
43
+ do_start @task
44
+ else
45
+ Async do |task|
46
+ do_start task
47
+ end
21
48
  end
22
49
  rescue Errno::EADDRINUSE => e
23
50
  log "Cannot start: #{e.to_s}", level: :error
@@ -6,22 +6,6 @@ module RSMP
6
6
  class Probe
7
7
  attr_reader :condition, :items, :done
8
8
 
9
- # block should send a message and return message just sent
10
- def self.collect_response proxy, options={}, &block
11
- from = proxy.archive.current_index
12
- sent = yield
13
- raise RuntimeError unless sent && sent[:message].is_a?(RSMP::Message)
14
- item = proxy.archive.capture(options.merge(from: from+1, num: 1, with_message: true)) do |item|
15
- ["CommandResponse","StatusResponse","MessageNotAck"].include?(item[:message].type)
16
- end
17
- if item
18
- item[:message]
19
- else
20
- nil
21
- end
22
- end
23
-
24
-
25
9
  def initialize archive
26
10
  raise ArgumentError.new("Archive expected") unless archive.is_a? Archive
27
11
  @archive = archive
@@ -30,6 +14,7 @@ module RSMP
30
14
  end
31
15
 
32
16
  def capture task, options={}, &block
17
+ raise ArgumentError.new("timeout option is missing") unless options[:timeout]
33
18
  @options = options
34
19
  @block = block
35
20
  @num = options[:num]
@@ -37,8 +22,6 @@ module RSMP
37
22
  if options[:earliest]
38
23
  from = find_timestamp_index options[:earliest]
39
24
  backscan from
40
- elsif options[:from]
41
- backscan options[:from]
42
25
  end
43
26
 
44
27
  # if backscan didn't find enough items, then
@@ -62,6 +45,7 @@ module RSMP
62
45
  end
63
46
 
64
47
  def find_timestamp_index earliest
48
+ return 0 if earliest == :start
65
49
  (0..@archive.items.size).bsearch do |i| # use binary search to find item index
66
50
  @archive.items[i][:timestamp] >= earliest
67
51
  end
@@ -105,6 +89,9 @@ module RSMP
105
89
  end
106
90
  return if @options[:level] && item[:level] != @options[:level]
107
91
  return false if @options[:with_message] && !(item[:direction] && item[:message])
92
+ if @options[:component]
93
+ return false if item[:message].attributes['cId'] && item[:message].attributes['cId'] != @options[:component]
94
+ end
108
95
  if @block
109
96
  return false if @block.call(item) == false
110
97
  end
@@ -2,7 +2,9 @@
2
2
 
3
3
  module RSMP
4
4
  class Proxy < Base
5
- attr_reader :state, :archive, :connection_info, :sxl
5
+ include Wait
6
+
7
+ attr_reader :state, :archive, :connection_info, :sxl, :task
6
8
 
7
9
  def initialize options
8
10
  super options
@@ -18,7 +20,7 @@ module RSMP
18
20
  def run
19
21
  start
20
22
  @reader.wait if @reader
21
- stop
23
+ stop unless [:stopped, :stopping].include? @state
22
24
  end
23
25
 
24
26
  def ready?
@@ -120,10 +122,22 @@ module RSMP
120
122
  task.annotate "timer"
121
123
  next_time = Time.now.to_f
122
124
  loop do
123
- now = RSMP.now_object
124
- break if timer(now) == false
125
- rescue StandardError => e
126
- log ["#{name} exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
125
+ begin
126
+ now = RSMP.now_object
127
+ timer(now)
128
+ rescue EOFError => e
129
+ log "Timer: Connection closed: #{e}", level: :warning
130
+ rescue IOError => e
131
+ log "Timer: IOError", level: :warning
132
+ rescue Errno::ECONNRESET
133
+ log "Timer: Connection reset by peer", level: :warning
134
+ rescue Errno::EPIPE => e
135
+ log "Timer: Broken pipe", level: :warning
136
+ rescue StandardError => e
137
+ log "Error: #{e}", level: :debug
138
+ #rescue StandardError => e
139
+ # log ["Timer error: #{e}",e.backtrace].flatten.join("\n"), level: :error
140
+ end
127
141
  ensure
128
142
  next_time += interval
129
143
  duration = next_time - Time.now.to_f
@@ -133,12 +147,12 @@ module RSMP
133
147
  end
134
148
 
135
149
  def timer now
136
- check_watchdog_send_time now
137
- return false if check_ack_timeout now
138
- return false if check_watchdog_timeout now
150
+ watchdog_send_timer now
151
+ check_ack_timeout now
152
+ check_watchdog_timeout now
139
153
  end
140
154
 
141
- def check_watchdog_send_time now
155
+ def watchdog_send_timer now
142
156
  return unless @watchdog_started
143
157
  return if @settings["watchdog_interval"] == :never
144
158
 
@@ -152,8 +166,6 @@ module RSMP
152
166
  send_watchdog now
153
167
  end
154
168
  end
155
- rescue StandardError => e
156
- log ["Watchdog error: #{e}",e.backtrace].flatten.join("\n"), level: :error
157
169
  end
158
170
 
159
171
  def send_watchdog now=nil
@@ -171,24 +183,18 @@ module RSMP
171
183
  if now > latest
172
184
  log "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds", level: :error
173
185
  stop
174
- return true
175
186
  end
176
187
  end
177
- false
178
188
  end
179
189
 
180
190
  def check_watchdog_timeout now
181
-
182
191
  timeout = @settings["watchdog_timeout"]
183
192
  latest = @latest_watchdog_received + timeout
184
193
  left = latest - now
185
- #log "Check watchdog, time:#{timeout}, last:#{@latest_watchdog_received}, now: #{now}, latest:#{latest}, left #{left}, fail:#{left<0}", level: :debug
186
194
  if left < 0
187
195
  log "No Watchdog within #{timeout} seconds", level: :error
188
196
  stop
189
- return true
190
197
  end
191
- false
192
198
  end
193
199
 
194
200
  def stop_tasks
@@ -216,7 +222,7 @@ module RSMP
216
222
 
217
223
  def buffer_message message
218
224
  # TODO
219
- log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
225
+ #log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
220
226
  end
221
227
 
222
228
  def log_send message, reason=nil
@@ -239,12 +245,13 @@ module RSMP
239
245
  message.validate sxl
240
246
  expect_version_message(message) unless @version_determined
241
247
  process_message message
248
+ process_deferred
242
249
  message
243
250
  rescue InvalidPacket => e
244
- warning "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}"
251
+ log "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}", level: :warning
245
252
  nil
246
253
  rescue MalformedMessage => e
247
- warning "Received malformed message, #{e.message}", Malformed.new(attributes)
254
+ log "Received malformed message, #{e.message}", message: Malformed.new(attributes), level: :warning
248
255
  # cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
249
256
  nil
250
257
  rescue SchemaError => e
@@ -335,7 +342,7 @@ module RSMP
335
342
  def wait_for_state state, timeout
336
343
  states = [state].flatten
337
344
  return if states.include?(@state)
338
- RSMP::Wait.wait_for(@task,@state_condition,timeout) do |s|
345
+ wait_for(@state_condition,timeout) do |s|
339
346
  states.include?(@state)
340
347
  end
341
348
  @state
@@ -445,27 +452,16 @@ module RSMP
445
452
  def version_acknowledged
446
453
  end
447
454
 
448
- def wait_for_acknowledgement original, timeout, options={}
449
- raise ArgumentError unless original
450
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
451
- message.is_a?(MessageAck) &&
452
- message.attributes["oMId"] == original.m_id
453
- end
454
- end
455
-
456
- def wait_for_not_acknowledged original, timeout
455
+ def wait_for_acknowledgement original, timeout
457
456
  raise ArgumentError unless original
458
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
459
- message.is_a?(MessageNotAck) &&
460
- message.attributes["oMId"] == original.m_id
461
- end
462
- end
463
-
464
- def wait_for_acknowledgements timeout
465
- return if @awaiting_acknowledgement.empty?
466
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
467
- @awaiting_acknowledgement.empty?
457
+ wait_for(@acknowledgement_condition,timeout) do |message|
458
+ if message.is_a?(MessageNotAck) && message.attributes["oMId"] == original.m_id
459
+ raise RSMP::MessageRejected.new(message.attributes['rea'])
460
+ end
461
+ message.is_a?(MessageAck) && message.attributes["oMId"] == original.m_id
468
462
  end
463
+ rescue Async::TimeoutError
464
+ raise RSMP::TimeoutError.new("Acknowledgement for #{original.type} #{original.m_id} not received within #{timeout}s")
469
465
  end
470
466
 
471
467
  def node