rsmp 0.8.3 → 0.9.0
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 +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:
|