rsmp 0.8.3 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rspec.yaml +14 -0
- data/.ruby-version +1 -1
- data/Gemfile.lock +46 -67
- data/README.md +2 -2
- data/bin/console +1 -1
- data/config/tlc.yaml +8 -6
- data/documentation/classes_and_modules.md +4 -4
- data/documentation/collecting_message.md +2 -2
- data/documentation/tasks.md +149 -0
- data/lib/rsmp/archive.rb +3 -3
- data/lib/rsmp/cli.rb +27 -4
- data/lib/rsmp/collect/aggregated_status_collector.rb +1 -1
- data/lib/rsmp/collect/collector.rb +13 -6
- data/lib/rsmp/collect/command_response_collector.rb +1 -1
- data/lib/rsmp/collect/state_collector.rb +1 -1
- data/lib/rsmp/collect/status_collector.rb +2 -1
- data/lib/rsmp/components.rb +3 -3
- data/lib/rsmp/convert/export/json_schema.rb +4 -4
- data/lib/rsmp/convert/import/yaml.rb +1 -1
- data/lib/rsmp/error.rb +0 -3
- data/lib/rsmp/inspect.rb +1 -1
- data/lib/rsmp/logger.rb +5 -5
- data/lib/rsmp/logging.rb +1 -1
- data/lib/rsmp/message.rb +1 -1
- data/lib/rsmp/node.rb +10 -45
- data/lib/rsmp/proxy.rb +184 -134
- data/lib/rsmp/rsmp.rb +1 -1
- data/lib/rsmp/site.rb +24 -61
- data/lib/rsmp/site_proxy.rb +33 -37
- data/lib/rsmp/supervisor.rb +25 -21
- data/lib/rsmp/supervisor_proxy.rb +55 -29
- data/lib/rsmp/task.rb +84 -0
- data/lib/rsmp/tlc/signal_group.rb +17 -7
- data/lib/rsmp/tlc/signal_plan.rb +2 -2
- data/lib/rsmp/tlc/traffic_controller.rb +125 -39
- data/lib/rsmp/tlc/traffic_controller_site.rb +51 -35
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +1 -1
- data/rsmp.gemspec +7 -7
- metadata +20 -20
- data/lib/rsmp/site_proxy_wait.rb +0 -0
- data/lib/rsmp/wait.rb +0 -16
- data/test.rb +0 -27
data/lib/rsmp/site.rb
CHANGED
@@ -9,12 +9,14 @@ 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
|
18
|
+
|
19
|
+
build_proxies
|
18
20
|
end
|
19
21
|
|
20
22
|
def site_id
|
@@ -62,25 +64,29 @@ module RSMP
|
|
62
64
|
RSMP::Schemer::find_schema! sxl, version, lenient: true
|
63
65
|
end
|
64
66
|
|
65
|
-
def
|
66
|
-
@
|
67
|
+
def run
|
68
|
+
log "Starting site #{@site_settings["site_id"]}",
|
69
|
+
level: :info,
|
70
|
+
timestamp: @clock.now
|
71
|
+
@proxies.each { |proxy| proxy.start }
|
72
|
+
@proxies.each { |proxy| proxy.wait }
|
67
73
|
end
|
68
74
|
|
69
|
-
def
|
75
|
+
def build_proxies
|
70
76
|
@site_settings["supervisors"].each do |supervisor_settings|
|
71
|
-
@
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
+
@proxies << SupervisorProxy.new({
|
78
|
+
site: self,
|
79
|
+
task: @task,
|
80
|
+
settings: @site_settings,
|
81
|
+
ip: supervisor_settings['ip'],
|
82
|
+
port: supervisor_settings['port'],
|
83
|
+
logger: @logger,
|
84
|
+
archive: @archive,
|
85
|
+
collect: @collect
|
86
|
+
})
|
77
87
|
end
|
78
88
|
end
|
79
89
|
|
80
|
-
def build_proxy settings
|
81
|
-
SupervisorProxy.new settings
|
82
|
-
end
|
83
|
-
|
84
90
|
def aggregated_status_changed component, options={}
|
85
91
|
@proxies.each do |proxy|
|
86
92
|
proxy.send_aggregated_status component, options if proxy.ready?
|
@@ -91,7 +97,7 @@ module RSMP
|
|
91
97
|
proxy = build_proxy({
|
92
98
|
site: self,
|
93
99
|
task: @task,
|
94
|
-
settings: @site_settings,
|
100
|
+
settings: @site_settings,
|
95
101
|
ip: supervisor_settings['ip'],
|
96
102
|
port: supervisor_settings['port'],
|
97
103
|
logger: @logger,
|
@@ -99,63 +105,20 @@ module RSMP
|
|
99
105
|
collect: @collect
|
100
106
|
})
|
101
107
|
@proxies << proxy
|
108
|
+
proxy.start
|
102
109
|
@proxies_condition.signal
|
103
|
-
run_site_proxy task, proxy
|
104
|
-
ensure
|
105
|
-
@proxies.delete proxy
|
106
|
-
@proxies_condition.signal
|
107
|
-
end
|
108
|
-
|
109
|
-
def run_site_proxy task, proxy
|
110
|
-
loop do
|
111
|
-
proxy.run # run until disconnected
|
112
|
-
rescue IOError => e
|
113
|
-
log "Stream error: #{e}", level: :warning
|
114
|
-
rescue StandardError => e
|
115
|
-
notify_error e, level: :internal
|
116
|
-
ensure
|
117
|
-
begin
|
118
|
-
if @site_settings['intervals']['watchdog'] != :no
|
119
|
-
# sleep until waken by reconnect() or the reconnect interval passed
|
120
|
-
proxy.set_state :wait_for_reconnect
|
121
|
-
task.with_timeout(@site_settings['intervals']['watchdog']) do
|
122
|
-
@sleep_condition.wait
|
123
|
-
end
|
124
|
-
else
|
125
|
-
proxy.set_state :cannot_connect
|
126
|
-
break
|
127
|
-
end
|
128
|
-
rescue Async::TimeoutError
|
129
|
-
# ignore
|
130
|
-
end
|
131
|
-
end
|
132
110
|
end
|
133
111
|
|
112
|
+
# stop
|
134
113
|
def stop
|
135
114
|
log "Stopping site #{@site_settings["site_id"]}", level: :info
|
136
|
-
@proxies.each do |proxy|
|
137
|
-
proxy.stop
|
138
|
-
end
|
139
|
-
@proxies.clear
|
140
115
|
super
|
141
116
|
end
|
142
|
-
|
143
|
-
def starting
|
144
|
-
log "Starting site #{@site_settings["site_id"]}",
|
145
|
-
level: :info,
|
146
|
-
timestamp: @clock.now
|
147
|
-
end
|
148
|
-
|
149
|
-
def alarm
|
150
|
-
@proxies.each do |proxy|
|
151
|
-
proxy.stop
|
152
|
-
end
|
153
|
-
end
|
154
117
|
|
155
118
|
def wait_for_supervisor ip, timeout
|
156
119
|
supervisor = find_supervisor ip
|
157
120
|
return supervisor if supervisor
|
158
|
-
|
121
|
+
wait_for_condition(@proxy_condition,timeout:timeout) { find_supervisor ip }
|
159
122
|
rescue Async::TimeoutError
|
160
123
|
raise RSMP::TimeoutError.new "Supervisor '#{ip}' did not connect within #{timeout}s"
|
161
124
|
end
|
data/lib/rsmp/site_proxy.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Handles a supervisor connection to a remote client
|
2
2
|
|
3
|
-
module RSMP
|
3
|
+
module RSMP
|
4
4
|
class SiteProxy < Proxy
|
5
5
|
include Components
|
6
6
|
|
@@ -14,33 +14,37 @@ module RSMP
|
|
14
14
|
@site_id = options[:site_id]
|
15
15
|
end
|
16
16
|
|
17
|
+
# handle communication
|
18
|
+
# when we're created, the socket is already open
|
19
|
+
def run
|
20
|
+
set_state :connected
|
21
|
+
start_reader
|
22
|
+
wait_for_reader # run until disconnected
|
23
|
+
rescue RSMP::ConnectionError => e
|
24
|
+
log e, level: :error
|
25
|
+
rescue StandardError => e
|
26
|
+
notify_error e, level: :internal
|
27
|
+
ensure
|
28
|
+
close
|
29
|
+
end
|
30
|
+
|
17
31
|
def revive options
|
18
32
|
super options
|
19
33
|
@supervisor = options[:supervisor]
|
20
34
|
@settings = @supervisor.supervisor_settings.clone
|
21
35
|
end
|
22
36
|
|
23
|
-
|
24
37
|
def inspect
|
25
38
|
"#<#{self.class.name}:#{self.object_id}, #{inspector(
|
26
39
|
:@acknowledgements,:@settings,:@site_settings,:@components
|
27
40
|
)}>"
|
28
41
|
end
|
42
|
+
|
29
43
|
def node
|
30
44
|
supervisor
|
31
45
|
end
|
32
46
|
|
33
|
-
def
|
34
|
-
super
|
35
|
-
start_reader
|
36
|
-
end
|
37
|
-
|
38
|
-
def stop
|
39
|
-
log "Closing connection to site", level: :info
|
40
|
-
super
|
41
|
-
end
|
42
|
-
|
43
|
-
def connection_complete
|
47
|
+
def handshake_complete
|
44
48
|
super
|
45
49
|
sanitized_sxl_version = RSMP::Schemer.sanitize_version(@site_sxl_version)
|
46
50
|
log "Connection to site #{@site_id} established, using core #{@rsmp_version}, #{@sxl} #{sanitized_sxl_version}", level: :info
|
@@ -68,7 +72,7 @@ module RSMP
|
|
68
72
|
else
|
69
73
|
super message
|
70
74
|
end
|
71
|
-
rescue RSMP::RepeatedAlarmError, RSMP::RepeatedStatusError
|
75
|
+
rescue RSMP::RepeatedAlarmError, RSMP::RepeatedStatusError
|
72
76
|
str = "Rejected #{message.type} message,"
|
73
77
|
dont_acknowledge message, str, "#{e}"
|
74
78
|
notify_error e.exception("#{str}#{e.message} #{message.json}")
|
@@ -101,13 +105,11 @@ module RSMP
|
|
101
105
|
"cId" => component,
|
102
106
|
"mId" => m_id
|
103
107
|
})
|
104
|
-
send_and_optionally_collect message, options do |
|
105
|
-
|
108
|
+
send_and_optionally_collect message, options do |collect_options|
|
109
|
+
AggregatedStatusCollector.new(
|
106
110
|
self,
|
107
|
-
|
111
|
+
collect_options.merge(task:@task,m_id: m_id, num:1)
|
108
112
|
)
|
109
|
-
collector.collect
|
110
|
-
collector
|
111
113
|
end
|
112
114
|
end
|
113
115
|
|
@@ -150,7 +152,7 @@ module RSMP
|
|
150
152
|
end
|
151
153
|
|
152
154
|
def version_acknowledged
|
153
|
-
|
155
|
+
handshake_complete
|
154
156
|
end
|
155
157
|
|
156
158
|
def process_watchdog message
|
@@ -179,14 +181,12 @@ module RSMP
|
|
179
181
|
"sS" => request_list,
|
180
182
|
"mId" => m_id
|
181
183
|
})
|
182
|
-
send_and_optionally_collect message, options do |
|
183
|
-
|
184
|
+
send_and_optionally_collect message, options do |collect_options|
|
185
|
+
StatusCollector.new(
|
184
186
|
self,
|
185
187
|
status_list,
|
186
|
-
|
188
|
+
collect_options.merge(task:@task,m_id: m_id)
|
187
189
|
)
|
188
|
-
collector.collect
|
189
|
-
collector
|
190
190
|
end
|
191
191
|
end
|
192
192
|
|
@@ -200,7 +200,7 @@ module RSMP
|
|
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
|
203
|
-
|
203
|
+
|
204
204
|
# additional items can be used when verifying the response,
|
205
205
|
# but must to remove from the subscribe message
|
206
206
|
subscribe_list = status_list.map { |item| item.slice('sCI','n','uRt') }
|
@@ -215,14 +215,12 @@ module RSMP
|
|
215
215
|
"sS" => subscribe_list,
|
216
216
|
'mId' => m_id
|
217
217
|
})
|
218
|
-
send_and_optionally_collect message, options do |
|
219
|
-
|
218
|
+
send_and_optionally_collect message, options do |collect_options|
|
219
|
+
StatusCollector.new(
|
220
220
|
self,
|
221
221
|
status_list,
|
222
|
-
|
222
|
+
collect_options.merge(task:@task,m_id: m_id)
|
223
223
|
)
|
224
|
-
collector.collect
|
225
|
-
collector
|
226
224
|
end
|
227
225
|
end
|
228
226
|
|
@@ -267,14 +265,12 @@ module RSMP
|
|
267
265
|
"arg" => command_list,
|
268
266
|
"mId" => m_id
|
269
267
|
})
|
270
|
-
send_and_optionally_collect message, options do |
|
271
|
-
|
268
|
+
send_and_optionally_collect message, options do |collect_options|
|
269
|
+
CommandResponseCollector.new(
|
272
270
|
self,
|
273
271
|
command_list,
|
274
|
-
|
272
|
+
collect_options.merge(task:@task,m_id: m_id)
|
275
273
|
)
|
276
|
-
collector.collect
|
277
|
-
collector
|
278
274
|
end
|
279
275
|
end
|
280
276
|
|
@@ -332,7 +328,7 @@ module RSMP
|
|
332
328
|
log "Using site settings for guest", level: :debug
|
333
329
|
return @settings['guest']
|
334
330
|
end
|
335
|
-
|
331
|
+
|
336
332
|
nil
|
337
333
|
end
|
338
334
|
|
data/lib/rsmp/supervisor.rb
CHANGED
@@ -58,26 +58,31 @@ module RSMP
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
|
61
|
+
# listen for connections
|
62
|
+
# Async::IO::Endpoint#accept createa an async task that we will wait for
|
63
|
+
def run
|
64
|
+
log "Starting supervisor on port #{@supervisor_settings["port"]}",
|
65
|
+
level: :info,
|
66
|
+
timestamp: @clock.now
|
67
|
+
|
62
68
|
@endpoint = Async::IO::Endpoint.tcp('0.0.0.0', @supervisor_settings["port"])
|
63
|
-
@endpoint.accept do |socket| # creates async tasks
|
69
|
+
tasks = @endpoint.accept do |socket| # creates async tasks
|
64
70
|
handle_connection(socket)
|
65
71
|
rescue StandardError => e
|
66
72
|
notify_error e, level: :internal
|
67
73
|
end
|
74
|
+
tasks.each { |task| task.wait }
|
68
75
|
rescue StandardError => e
|
69
76
|
notify_error e, level: :internal
|
70
77
|
end
|
71
78
|
|
79
|
+
# stop
|
72
80
|
def stop
|
73
81
|
log "Stopping supervisor #{@supervisor_settings["site_id"]}", level: :info
|
74
|
-
@proxies.each { |proxy| proxy.stop }
|
75
|
-
@proxies.clear
|
76
82
|
super
|
77
|
-
@tcp_server.close if @tcp_server
|
78
|
-
@tcp_server = nil
|
79
83
|
end
|
80
84
|
|
85
|
+
# handle an incoming connction by either accepting of rejecting it
|
81
86
|
def handle_connection socket
|
82
87
|
remote_port = socket.remote_address.ip_port
|
83
88
|
remote_hostname = socket.remote_address.ip_address
|
@@ -85,9 +90,9 @@ module RSMP
|
|
85
90
|
|
86
91
|
info = {ip:remote_ip, port:remote_port, hostname:remote_hostname, now:Clock.now}
|
87
92
|
if accept? socket, info
|
88
|
-
|
93
|
+
accept_connection socket, info
|
89
94
|
else
|
90
|
-
|
95
|
+
reject_connection socket, info
|
91
96
|
end
|
92
97
|
rescue ConnectionError => e
|
93
98
|
log "Rejected connection from #{remote_ip}:#{remote_port}, #{e.to_s}", level: :warning
|
@@ -99,12 +104,6 @@ module RSMP
|
|
99
104
|
close socket, info
|
100
105
|
end
|
101
106
|
|
102
|
-
def starting
|
103
|
-
log "Starting supervisor on port #{@supervisor_settings["port"]}",
|
104
|
-
level: :info,
|
105
|
-
timestamp: @clock.now
|
106
|
-
end
|
107
|
-
|
108
107
|
def accept? socket, info
|
109
108
|
true
|
110
109
|
end
|
@@ -143,7 +142,8 @@ module RSMP
|
|
143
142
|
message.attribute('siteId').first['sId']
|
144
143
|
end
|
145
144
|
|
146
|
-
|
145
|
+
# accept an incoming connecting by creating and starting a proxy
|
146
|
+
def accept_connection socket, info
|
147
147
|
log "Site connected from #{format_ip_and_port(info)}",
|
148
148
|
ip: info[:ip],
|
149
149
|
port: info[:port],
|
@@ -182,7 +182,8 @@ module RSMP
|
|
182
182
|
proxy = build_proxy settings.merge(site_id:id) # keep the id learned by peeking above
|
183
183
|
@proxies.push proxy
|
184
184
|
end
|
185
|
-
proxy.
|
185
|
+
proxy.start # will run until the site disconnects
|
186
|
+
proxy.wait
|
186
187
|
ensure
|
187
188
|
site_ids_changed
|
188
189
|
stop if @supervisor_settings['one_shot']
|
@@ -192,7 +193,7 @@ module RSMP
|
|
192
193
|
@site_id_condition.signal
|
193
194
|
end
|
194
195
|
|
195
|
-
def
|
196
|
+
def reject_connection socket, info
|
196
197
|
log "Site rejected", ip: info[:ip], level: :info
|
197
198
|
end
|
198
199
|
|
@@ -224,10 +225,13 @@ module RSMP
|
|
224
225
|
nil
|
225
226
|
end
|
226
227
|
|
227
|
-
def wait_for_site site_id, timeout
|
228
|
+
def wait_for_site site_id, timeout:
|
228
229
|
site = find_site site_id
|
229
230
|
return site if site
|
230
|
-
|
231
|
+
wait_for_condition(@site_id_condition,timeout:timeout) do
|
232
|
+
find_site site_id
|
233
|
+
end
|
234
|
+
|
231
235
|
rescue Async::TimeoutError
|
232
236
|
if site_id == :any
|
233
237
|
str = "No site connected"
|
@@ -237,8 +241,8 @@ module RSMP
|
|
237
241
|
raise RSMP::TimeoutError.new "#{str} within #{timeout}s"
|
238
242
|
end
|
239
243
|
|
240
|
-
def wait_for_site_disconnect site_id, timeout
|
241
|
-
|
244
|
+
def wait_for_site_disconnect site_id, timeout:
|
245
|
+
wait_for_condition(@site_id_condition,timeout:timeout) { true unless find_site site_id }
|
242
246
|
rescue Async::TimeoutError
|
243
247
|
raise RSMP::TimeoutError.new "Site '#{site_id}' did not disconnect within #{timeout}s"
|
244
248
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'digest'
|
4
4
|
|
5
|
-
module RSMP
|
5
|
+
module RSMP
|
6
6
|
class SupervisorProxy < Proxy
|
7
7
|
|
8
8
|
attr_reader :supervisor_id, :site
|
@@ -22,44 +22,67 @@ module RSMP
|
|
22
22
|
site
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
# handle communication
|
26
|
+
# if disconnected, then try to reconnect
|
27
|
+
def run
|
28
|
+
loop do
|
29
|
+
connect
|
30
|
+
start_reader
|
31
|
+
start_handshake
|
32
|
+
wait_for_reader # run until disconnected
|
33
|
+
break if reconnect_delay == false
|
34
|
+
rescue Restart
|
35
|
+
@logger.mute @ip, @port
|
36
|
+
raise
|
37
|
+
rescue RSMP::ConnectionError => e
|
38
|
+
log e, level: :error
|
39
|
+
break if reconnect_delay == false
|
40
|
+
rescue StandardError => e
|
41
|
+
notify_error e, level: :internal
|
42
|
+
break if reconnect_delay == false
|
43
|
+
ensure
|
44
|
+
close
|
45
|
+
stop_subtasks
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def start_handshake
|
50
|
+
send_version @site_settings['site_id'], @site_settings["rsmp_versions"]
|
51
|
+
end
|
52
|
+
|
53
|
+
# connect to the supervisor and initiate handshake supervisor
|
54
|
+
def connect
|
26
55
|
log "Connecting to supervisor at #{@ip}:#{@port}", level: :info
|
27
|
-
|
28
|
-
|
56
|
+
set_state :connecting
|
57
|
+
connect_tcp
|
29
58
|
@logger.unmute @ip, @port
|
30
59
|
log "Connected to supervisor at #{@ip}:#{@port}", level: :info
|
31
|
-
start_reader
|
32
|
-
send_version @site_settings['site_id'], @site_settings["rsmp_versions"]
|
33
60
|
rescue SystemCallError => e
|
34
|
-
|
35
|
-
retry_notice
|
61
|
+
raise ConnectionError.new "Could not connect to supervisor at #{@ip}:#{@port}: Errno #{e.errno} #{e}"
|
36
62
|
rescue StandardError => e
|
37
|
-
|
38
|
-
retry_notice
|
39
|
-
end
|
40
|
-
|
41
|
-
def retry_notice
|
42
|
-
unless @site.site_settings['intervals']['reconnect'] == :no
|
43
|
-
log "Will try to reconnect again every #{@site.site_settings['intervals']['reconnect']} seconds..", level: :info
|
44
|
-
@logger.mute @ip, @port
|
45
|
-
end
|
63
|
+
raise ConnectionError.new "Error while connecting to supervisor at #{@ip}:#{@port}: #{e}"
|
46
64
|
end
|
47
65
|
|
48
|
-
def
|
49
|
-
log "Closing connection to supervisor", level: :info
|
66
|
+
def stop_task
|
50
67
|
super
|
51
68
|
@last_status_sent = nil
|
52
69
|
end
|
53
70
|
|
54
|
-
def
|
55
|
-
return if @socket
|
71
|
+
def connect_tcp
|
56
72
|
@endpoint = Async::IO::Endpoint.tcp(@ip, @port)
|
57
|
-
|
73
|
+
|
74
|
+
# Async::IO::Endpoint#connect renames the current task. run in a subtask to avoid this
|
75
|
+
@task.async do |task|
|
76
|
+
task.annotate 'socket task'
|
77
|
+
@socket = @endpoint.connect
|
78
|
+
end.wait
|
79
|
+
|
58
80
|
@stream = Async::IO::Stream.new(@socket)
|
59
81
|
@protocol = Async::IO::Protocol::Line.new(@stream,WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
|
82
|
+
set_state :connected
|
60
83
|
end
|
61
84
|
|
62
|
-
def
|
85
|
+
def handshake_complete
|
63
86
|
super
|
64
87
|
sanitized_sxl_version = RSMP::Schemer.sanitize_version(sxl_version)
|
65
88
|
log "Connection to supervisor established, using core #{@rsmp_version}, #{sxl} #{sanitized_sxl_version}", level: :info
|
@@ -114,16 +137,19 @@ module RSMP
|
|
114
137
|
end
|
115
138
|
|
116
139
|
def reconnect_delay
|
140
|
+
return false if @site_settings['intervals']['reconnect'] == :no
|
117
141
|
interval = @site_settings['intervals']['reconnect']
|
118
|
-
log "
|
142
|
+
log "Will try to reconnect again every #{interval} seconds...", level: :info
|
143
|
+
@logger.mute @ip, @port
|
119
144
|
@task.sleep interval
|
145
|
+
true
|
120
146
|
end
|
121
147
|
|
122
148
|
def version_accepted message
|
123
149
|
log "Received Version message, using RSMP #{@rsmp_version}", message: message, level: :log
|
124
150
|
start_timer
|
125
151
|
acknowledge message
|
126
|
-
|
152
|
+
handshake_complete
|
127
153
|
@version_determined = true
|
128
154
|
end
|
129
155
|
|
@@ -138,8 +164,8 @@ module RSMP
|
|
138
164
|
"mId" => m_id,
|
139
165
|
})
|
140
166
|
|
141
|
-
send_and_optionally_collect message, options do |
|
142
|
-
|
167
|
+
send_and_optionally_collect message, options do |collect_options|
|
168
|
+
Collector.new self, collect_options.merge(task:@task, type: 'MessageAck')
|
143
169
|
end
|
144
170
|
end
|
145
171
|
|
@@ -234,7 +260,7 @@ module RSMP
|
|
234
260
|
update_list = {}
|
235
261
|
component = message.attributes["cId"]
|
236
262
|
@status_subscriptions[component] ||= {}
|
237
|
-
update_list[component] ||= {}
|
263
|
+
update_list[component] ||= {}
|
238
264
|
now = Time.now # internal timestamp
|
239
265
|
subs = @status_subscriptions[component]
|
240
266
|
|
@@ -299,7 +325,7 @@ module RSMP
|
|
299
325
|
by_name.each_pair do |name,subscription|
|
300
326
|
current = nil
|
301
327
|
should_send = false
|
302
|
-
if subscription[:interval] == 0
|
328
|
+
if subscription[:interval] == 0
|
303
329
|
# send as soon as the data changes
|
304
330
|
if component_object
|
305
331
|
current, age = *(component_object.get_status code, name)
|
data/lib/rsmp/task.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
module RSMP
|
2
|
+
class Restart < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
module Task
|
6
|
+
attr_reader :task
|
7
|
+
|
8
|
+
def initialize_task
|
9
|
+
@task = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
# start our async tasks and return immediately
|
13
|
+
# run() will be called inside the task to perform actual long-running work
|
14
|
+
def start
|
15
|
+
return if @task
|
16
|
+
Async do |task|
|
17
|
+
task.annotate "#{self.class.name} main task"
|
18
|
+
@task = task
|
19
|
+
run
|
20
|
+
stop_subtasks
|
21
|
+
@task = nil
|
22
|
+
end
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
# initiate restart by raising a Restart exception
|
27
|
+
def restart
|
28
|
+
raise Restart.new "restart initiated by #{self.class.name}:#{object_id}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# get the status of our task, or nil of no task
|
32
|
+
def status
|
33
|
+
@task.status if @task
|
34
|
+
end
|
35
|
+
|
36
|
+
# perform any long-running work
|
37
|
+
# the method will be called from an async task, and should not return
|
38
|
+
# if subtasks are needed, the method should call wait() on each of them
|
39
|
+
# once running, ready() must be called
|
40
|
+
def run
|
41
|
+
start_subtasks
|
42
|
+
end
|
43
|
+
|
44
|
+
# wait for our task to complete
|
45
|
+
def wait
|
46
|
+
@task.wait if @task
|
47
|
+
end
|
48
|
+
|
49
|
+
# stop our task
|
50
|
+
def stop
|
51
|
+
stop_subtasks
|
52
|
+
stop_task if @task
|
53
|
+
end
|
54
|
+
|
55
|
+
def stop_subtasks
|
56
|
+
end
|
57
|
+
|
58
|
+
# stop our task and any subtask
|
59
|
+
def stop_task
|
60
|
+
@task.stop
|
61
|
+
@task = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
# wait for an async condition to signal, then yield to block
|
65
|
+
# if block returns true we're done. otherwise, wait again
|
66
|
+
def wait_for_condition condition, timeout:, task:Async::Task.current, &block
|
67
|
+
unless task
|
68
|
+
raise RuntimeError.new("Can't wait without a task")
|
69
|
+
end
|
70
|
+
task.with_timeout(timeout) do
|
71
|
+
while task.running?
|
72
|
+
value = condition.wait
|
73
|
+
return value unless block
|
74
|
+
result = yield value
|
75
|
+
return result if result
|
76
|
+
end
|
77
|
+
raise RuntimeError.new("Can't wait for condition because task #{task.object_id} #{task.annotation} is not running")
|
78
|
+
end
|
79
|
+
rescue Async::TimeoutError
|
80
|
+
raise RSMP::TimeoutError.new
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -6,25 +6,35 @@ 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
|
11
|
+
def timer
|
12
|
+
@state = compute_state
|
13
|
+
end
|
14
|
+
|
15
|
+
def compute_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
|
+
return 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
|
29
|
+
|
17
30
|
states = plan.states[c_id]
|
18
31
|
return default unless states
|
19
|
-
|
32
|
+
|
33
|
+
state = states[cycle_counter]
|
20
34
|
return default unless state =~ /[a-hA-G0-9N-P]/ # valid signal group states
|
21
35
|
state
|
22
36
|
end
|
23
37
|
|
24
|
-
def move pos
|
25
|
-
@state = get_state pos
|
26
|
-
end
|
27
|
-
|
28
38
|
def handle_command command_code, arg
|
29
39
|
case command_code
|
30
40
|
when 'M0010', 'M0011'
|
data/lib/rsmp/tlc/signal_plan.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module RSMP
|
2
2
|
module TLC
|
3
3
|
# A Traffic Light Controller Signal Plan.
|
4
|
-
# A signal plan is a description of how all signal groups should change
|
5
|
-
# state over time.
|
4
|
+
# A signal plan is a description of how all signal groups should change
|
5
|
+
# state over time.
|
6
6
|
class SignalPlan
|
7
7
|
attr_reader :nr, :states, :dynamic_bands
|
8
8
|
def initialize nr:, states:, dynamic_bands:
|