rsmp 0.37.0 → 0.38.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/.devcontainer/devcontainer.json +22 -0
- data/.github/workflows/rubocop.yaml +17 -0
- data/.gitignore +5 -6
- data/.rubocop.yml +80 -0
- data/Gemfile +13 -1
- data/Gemfile.lock +34 -1
- data/Rakefile +3 -3
- data/lib/rsmp/cli.rb +147 -124
- data/lib/rsmp/collect/ack_collector.rb +8 -7
- data/lib/rsmp/collect/aggregated_status_collector.rb +4 -4
- data/lib/rsmp/collect/alarm_collector.rb +31 -23
- data/lib/rsmp/collect/alarm_matcher.rb +3 -3
- data/lib/rsmp/collect/collector/logging.rb +17 -0
- data/lib/rsmp/collect/collector/reporting.rb +44 -0
- data/lib/rsmp/collect/collector/status.rb +34 -0
- data/lib/rsmp/collect/collector.rb +69 -150
- data/lib/rsmp/collect/command_matcher.rb +19 -6
- data/lib/rsmp/collect/command_response_collector.rb +7 -7
- data/lib/rsmp/collect/distributor.rb +14 -11
- data/lib/rsmp/collect/filter.rb +31 -15
- data/lib/rsmp/collect/matcher.rb +7 -11
- data/lib/rsmp/collect/queue.rb +4 -4
- data/lib/rsmp/collect/receiver.rb +10 -12
- data/lib/rsmp/collect/state_collector.rb +116 -77
- data/lib/rsmp/collect/status_collector.rb +6 -6
- data/lib/rsmp/collect/status_matcher.rb +17 -7
- data/lib/rsmp/{alarm_state.rb → component/alarm_state.rb} +76 -37
- data/lib/rsmp/{component.rb → component/component.rb} +15 -15
- data/lib/rsmp/component/component_base.rb +89 -0
- data/lib/rsmp/component/component_proxy.rb +75 -0
- data/lib/rsmp/component/components.rb +63 -0
- data/lib/rsmp/convert/export/json_schema.rb +116 -110
- data/lib/rsmp/convert/import/yaml.rb +21 -18
- data/lib/rsmp/{rsmp.rb → helpers/clock.rb} +5 -6
- data/lib/rsmp/{deep_merge.rb → helpers/deep_merge.rb} +2 -1
- data/lib/rsmp/helpers/error.rb +71 -0
- data/lib/rsmp/{inspect.rb → helpers/inspect.rb} +6 -10
- data/lib/rsmp/log/archive.rb +98 -0
- data/lib/rsmp/log/colorization.rb +41 -0
- data/lib/rsmp/log/filtering.rb +54 -0
- data/lib/rsmp/log/logger.rb +206 -0
- data/lib/rsmp/{logging.rb → log/logging.rb} +5 -7
- data/lib/rsmp/message.rb +159 -148
- data/lib/rsmp/{node.rb → node/node.rb} +19 -17
- data/lib/rsmp/{protocol.rb → node/protocol.rb} +5 -3
- data/lib/rsmp/node/site/site.rb +195 -0
- data/lib/rsmp/node/supervisor/modules/configuration.rb +59 -0
- data/lib/rsmp/node/supervisor/modules/connection.rb +140 -0
- data/lib/rsmp/node/supervisor/modules/sites.rb +64 -0
- data/lib/rsmp/node/supervisor/supervisor.rb +72 -0
- data/lib/rsmp/{task.rb → node/task.rb} +12 -14
- data/lib/rsmp/proxy/modules/acknowledgements.rb +144 -0
- data/lib/rsmp/proxy/modules/receive.rb +119 -0
- data/lib/rsmp/proxy/modules/send.rb +76 -0
- data/lib/rsmp/proxy/modules/state.rb +25 -0
- data/lib/rsmp/proxy/modules/tasks.rb +105 -0
- data/lib/rsmp/proxy/modules/versions.rb +69 -0
- data/lib/rsmp/proxy/modules/watchdogs.rb +66 -0
- data/lib/rsmp/proxy/proxy.rb +199 -0
- data/lib/rsmp/proxy/site/modules/aggregated_status.rb +52 -0
- data/lib/rsmp/proxy/site/modules/alarms.rb +27 -0
- data/lib/rsmp/proxy/site/modules/commands.rb +31 -0
- data/lib/rsmp/proxy/site/modules/status.rb +110 -0
- data/lib/rsmp/proxy/site/site_proxy.rb +205 -0
- data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +47 -0
- data/lib/rsmp/proxy/supervisor/modules/alarms.rb +73 -0
- data/lib/rsmp/proxy/supervisor/modules/commands.rb +53 -0
- data/lib/rsmp/proxy/supervisor/modules/status.rb +204 -0
- data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +178 -0
- data/lib/rsmp/tlc/detector_logic.rb +18 -34
- data/lib/rsmp/tlc/input_states.rb +126 -0
- data/lib/rsmp/tlc/modules/detector_logics.rb +50 -0
- data/lib/rsmp/tlc/modules/display.rb +78 -0
- data/lib/rsmp/tlc/modules/helpers.rb +41 -0
- data/lib/rsmp/tlc/modules/inputs.rb +173 -0
- data/lib/rsmp/tlc/modules/modes.rb +253 -0
- data/lib/rsmp/tlc/modules/outputs.rb +30 -0
- data/lib/rsmp/tlc/modules/plans.rb +218 -0
- data/lib/rsmp/tlc/modules/signal_groups.rb +109 -0
- data/lib/rsmp/tlc/modules/startup_sequence.rb +22 -0
- data/lib/rsmp/tlc/modules/system.rb +140 -0
- data/lib/rsmp/tlc/modules/traffic_data.rb +49 -0
- data/lib/rsmp/tlc/signal_group.rb +37 -41
- data/lib/rsmp/tlc/signal_plan.rb +14 -11
- data/lib/rsmp/tlc/signal_priority.rb +39 -35
- data/lib/rsmp/tlc/startup_sequence.rb +59 -0
- data/lib/rsmp/tlc/traffic_controller.rb +38 -1010
- data/lib/rsmp/tlc/traffic_controller_site.rb +58 -57
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +82 -48
- data/rsmp.gemspec +24 -31
- metadata +79 -139
- data/lib/rsmp/archive.rb +0 -76
- data/lib/rsmp/collect/message_matchers.rb +0 -0
- data/lib/rsmp/component_base.rb +0 -87
- data/lib/rsmp/component_proxy.rb +0 -57
- data/lib/rsmp/components.rb +0 -65
- data/lib/rsmp/error.rb +0 -71
- data/lib/rsmp/logger.rb +0 -216
- data/lib/rsmp/proxy.rb +0 -693
- data/lib/rsmp/site.rb +0 -188
- data/lib/rsmp/site_proxy.rb +0 -389
- data/lib/rsmp/supervisor.rb +0 -302
- data/lib/rsmp/supervisor_proxy.rb +0 -510
- data/lib/rsmp/tlc/inputs.rb +0 -134
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# A connection to a remote site or supervisor.
|
|
2
|
+
# Uses the Task module to handle asyncronous work, but adds
|
|
3
|
+
# the concept of a connection that can be connected or disconnected.
|
|
4
|
+
|
|
5
|
+
require 'rubygems'
|
|
6
|
+
|
|
7
|
+
module RSMP
|
|
8
|
+
class Proxy
|
|
9
|
+
WRAPPING_DELIMITER = "\f".freeze
|
|
10
|
+
|
|
11
|
+
include Logging
|
|
12
|
+
include Distributor
|
|
13
|
+
include Inspect
|
|
14
|
+
include Task
|
|
15
|
+
include Modules::State
|
|
16
|
+
include Modules::Watchdogs
|
|
17
|
+
include Modules::Acknowledgements
|
|
18
|
+
include Modules::Send
|
|
19
|
+
include Modules::Receive
|
|
20
|
+
include Modules::Versions
|
|
21
|
+
include Modules::Tasks
|
|
22
|
+
|
|
23
|
+
attr_reader :state, :archive, :connection_info, :sxl, :collector, :ip, :port, :node, :core_version
|
|
24
|
+
|
|
25
|
+
def initialize(options)
|
|
26
|
+
@node = options[:node]
|
|
27
|
+
initialize_logging options
|
|
28
|
+
initialize_distributor
|
|
29
|
+
initialize_task
|
|
30
|
+
setup options
|
|
31
|
+
clear
|
|
32
|
+
@state = :disconnected
|
|
33
|
+
@state_condition = Async::Notification.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def now
|
|
37
|
+
node.now
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Connection lifecycle methods
|
|
41
|
+
|
|
42
|
+
def disconnect; end
|
|
43
|
+
|
|
44
|
+
# wait for the reader task to complete,
|
|
45
|
+
# which is not expected to happen before the connection is closed
|
|
46
|
+
def wait_for_reader
|
|
47
|
+
@reader&.wait
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# close connection, but keep our main task running so we can reconnect
|
|
51
|
+
def close
|
|
52
|
+
log 'Closing connection', level: :warning
|
|
53
|
+
close_stream
|
|
54
|
+
close_socket
|
|
55
|
+
stop_reader
|
|
56
|
+
self.state = :disconnected
|
|
57
|
+
distribute_error DisconnectError.new('Connection was closed')
|
|
58
|
+
|
|
59
|
+
# stop timer
|
|
60
|
+
# as we're running inside the timer, code after stop_timer() will not be called,
|
|
61
|
+
# unless it's in the ensure block
|
|
62
|
+
stop_timer
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def stop_subtasks
|
|
66
|
+
stop_timer
|
|
67
|
+
stop_reader
|
|
68
|
+
clear
|
|
69
|
+
super
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def stop_timer
|
|
73
|
+
@timer&.stop
|
|
74
|
+
ensure
|
|
75
|
+
@timer = nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def stop_reader
|
|
79
|
+
@reader&.stop
|
|
80
|
+
ensure
|
|
81
|
+
@reader = nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def close_stream
|
|
85
|
+
@stream&.close
|
|
86
|
+
ensure
|
|
87
|
+
@stream = nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def close_socket
|
|
91
|
+
@socket&.close
|
|
92
|
+
ensure
|
|
93
|
+
@socket = nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def stop_task
|
|
97
|
+
close
|
|
98
|
+
super
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# State management methods
|
|
102
|
+
|
|
103
|
+
def ready?
|
|
104
|
+
@state == :ready
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def connected?
|
|
108
|
+
@state == :connected || @state == :ready
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def disconnected?
|
|
112
|
+
@state == :disconnected
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# change our state
|
|
116
|
+
def state=(state)
|
|
117
|
+
return if state == @state
|
|
118
|
+
|
|
119
|
+
@state = state
|
|
120
|
+
state_changed
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# the state changed
|
|
124
|
+
# override to to things like notifications
|
|
125
|
+
def state_changed
|
|
126
|
+
@state_condition.signal @state
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def clear
|
|
130
|
+
@awaiting_acknowledgement = {}
|
|
131
|
+
@latest_watchdog_received = nil
|
|
132
|
+
@watchdog_started = false
|
|
133
|
+
@version_determined = false
|
|
134
|
+
@ingoing_acknowledged = {}
|
|
135
|
+
@outgoing_acknowledged = {}
|
|
136
|
+
@latest_watchdog_send_at = nil
|
|
137
|
+
|
|
138
|
+
@acknowledgements = {}
|
|
139
|
+
@acknowledgement_condition = Async::Notification.new
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# revive after a reconnect
|
|
143
|
+
def revive(options)
|
|
144
|
+
setup options
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def setup(options)
|
|
148
|
+
@settings = options[:settings]
|
|
149
|
+
@socket = options[:socket]
|
|
150
|
+
@stream = options[:stream]
|
|
151
|
+
@protocol = options[:protocol]
|
|
152
|
+
@ip = options[:ip]
|
|
153
|
+
@port = options[:port]
|
|
154
|
+
@connection_info = options[:info]
|
|
155
|
+
@sxl = nil
|
|
156
|
+
@site_settings = nil # can't pick until we know the site id
|
|
157
|
+
return unless options[:collect]
|
|
158
|
+
|
|
159
|
+
@collector = RSMP::Collector.new self, options[:collect]
|
|
160
|
+
@collector.start
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def inspect
|
|
164
|
+
"#<#{self.class.name}:#{object_id}, #{inspector(
|
|
165
|
+
:@acknowledgements, :@settings, :@site_settings
|
|
166
|
+
)}>"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def clock
|
|
170
|
+
@node.clock
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def receive_error(error, options = {})
|
|
174
|
+
@node.receive_error error, options
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def log(str, options = {})
|
|
178
|
+
super(str, options.merge(ip: @ip, port: @port, site_id: @site_id))
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def schemas
|
|
182
|
+
schemas = { core: RSMP::Schema.latest_core_version } # use latest core
|
|
183
|
+
schemas[:core] = core_version if core_version
|
|
184
|
+
schemas[sxl] = RSMP::Schema.sanitize_version(sxl_version.to_s) if sxl && sxl_version
|
|
185
|
+
schemas
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def author
|
|
189
|
+
@node.site_id
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Use Gem class to check version requirement
|
|
193
|
+
# Requirement must be a string like '1.1', '>=1.0.3' or '<2.1.4',
|
|
194
|
+
# or list of strings, like ['<=1.4','<1.5']
|
|
195
|
+
def self.version_meets_requirement?(version, requirement)
|
|
196
|
+
Modules::Versions.version_meets_requirement?(version, requirement)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class SiteProxy < Proxy
|
|
3
|
+
module Modules
|
|
4
|
+
# Handles aggregated status requests and responses
|
|
5
|
+
module AggregatedStatus
|
|
6
|
+
def request_aggregated_status(component, options = {})
|
|
7
|
+
validate_ready 'request aggregated status'
|
|
8
|
+
m_id = options[:m_id] || RSMP::Message.make_m_id
|
|
9
|
+
message = RSMP::AggregatedStatusRequest.new({
|
|
10
|
+
'cId' => component,
|
|
11
|
+
'mId' => m_id
|
|
12
|
+
})
|
|
13
|
+
apply_nts_message_attributes message
|
|
14
|
+
send_and_optionally_collect message, options do |collect_options|
|
|
15
|
+
AggregatedStatusCollector.new(
|
|
16
|
+
self,
|
|
17
|
+
collect_options.merge(task: @task, m_id: m_id, num: 1)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def validate_aggregated_status(message, status_elements)
|
|
23
|
+
return if status_elements.is_a?(Array) && status_elements.size == 8
|
|
24
|
+
|
|
25
|
+
dont_acknowledge message, 'Received', reaons
|
|
26
|
+
raise InvalidMessage
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def process_aggregated_status(message)
|
|
30
|
+
status_elements = message.attribute('se')
|
|
31
|
+
validate_aggregated_status(message, status_elements)
|
|
32
|
+
c_id = message.attributes['cId']
|
|
33
|
+
component = find_component c_id
|
|
34
|
+
unless component
|
|
35
|
+
reason = "component #{c_id} not found"
|
|
36
|
+
dont_acknowledge message, "Ignoring #{message.type}:", reason
|
|
37
|
+
return
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
component.aggregated_status_bools = status_elements
|
|
41
|
+
log "Received #{message.type} status for component #{c_id} [#{component.aggregated_status.join(', ')}]",
|
|
42
|
+
message: message
|
|
43
|
+
acknowledge message
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def aggregated_status_changed(component, _options = {})
|
|
47
|
+
@supervisor.aggregated_status_changed self, component
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class SiteProxy < Proxy
|
|
3
|
+
module Modules
|
|
4
|
+
# Handles alarm messages
|
|
5
|
+
module Alarms
|
|
6
|
+
def process_alarm(message)
|
|
7
|
+
component = find_component message.attribute('cId')
|
|
8
|
+
status = %w[ack aS sS].map { |key| message.attribute(key) }.join(',')
|
|
9
|
+
component.handle_alarm message
|
|
10
|
+
alarm_code = message.attribute('aCId')
|
|
11
|
+
asp = message.attribute('aSp')
|
|
12
|
+
log "Received #{message.type}, #{alarm_code} #{asp} [#{status}]", message: message, level: :log
|
|
13
|
+
acknowledge message
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def send_alarm_acknowledgement(component, alarm_code, options = {})
|
|
17
|
+
message = RSMP::AlarmAcknowledged.new({
|
|
18
|
+
'cId' => component,
|
|
19
|
+
'aCId' => alarm_code
|
|
20
|
+
})
|
|
21
|
+
send_message message, validate: options[:validate]
|
|
22
|
+
message
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class SiteProxy < Proxy
|
|
3
|
+
module Modules
|
|
4
|
+
# Handles command requests and responses
|
|
5
|
+
module Commands
|
|
6
|
+
def send_command(component, command_list, options = {})
|
|
7
|
+
validate_ready 'send command'
|
|
8
|
+
m_id = options[:m_id] || RSMP::Message.make_m_id
|
|
9
|
+
message = RSMP::CommandRequest.new({
|
|
10
|
+
'cId' => component,
|
|
11
|
+
'arg' => command_list,
|
|
12
|
+
'mId' => m_id
|
|
13
|
+
})
|
|
14
|
+
apply_nts_message_attributes message
|
|
15
|
+
send_and_optionally_collect message, options do |collect_options|
|
|
16
|
+
CommandResponseCollector.new(
|
|
17
|
+
self,
|
|
18
|
+
command_list,
|
|
19
|
+
collect_options.merge(task: @task, m_id: m_id)
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def process_command_response(message)
|
|
25
|
+
log "Received #{message.type}", message: message, level: :log
|
|
26
|
+
acknowledge message
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class SiteProxy < Proxy
|
|
3
|
+
module Modules
|
|
4
|
+
# Handles status requests, responses, subscriptions and updates
|
|
5
|
+
module Status
|
|
6
|
+
def request_status(component, status_list, options = {})
|
|
7
|
+
validate_ready 'request status'
|
|
8
|
+
m_id = options[:m_id] || RSMP::Message.make_m_id
|
|
9
|
+
|
|
10
|
+
# additional items can be used when verifying the response,
|
|
11
|
+
# but must be removed from the request
|
|
12
|
+
request_list = status_list.map { |item| item.slice('sCI', 'n') }
|
|
13
|
+
|
|
14
|
+
message = RSMP::StatusRequest.new({
|
|
15
|
+
'cId' => component,
|
|
16
|
+
'sS' => request_list,
|
|
17
|
+
'mId' => m_id
|
|
18
|
+
})
|
|
19
|
+
apply_nts_message_attributes message
|
|
20
|
+
send_and_optionally_collect message, options do |collect_options|
|
|
21
|
+
StatusCollector.new(
|
|
22
|
+
self,
|
|
23
|
+
status_list,
|
|
24
|
+
collect_options.merge(task: @task, m_id: m_id)
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def process_status_response(message)
|
|
30
|
+
component = find_component message.attribute('cId')
|
|
31
|
+
component.store_status message
|
|
32
|
+
log "Received #{message.type}", message: message, level: :log
|
|
33
|
+
acknowledge message
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ensure_subscription_path(component_id, code, name)
|
|
37
|
+
@status_subscriptions[component_id] ||= {}
|
|
38
|
+
@status_subscriptions[component_id][code] ||= {}
|
|
39
|
+
@status_subscriptions[component_id][code][name] ||= {}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def update_subscription(component_id, subscribe_list)
|
|
43
|
+
subscribe_list.each do |item|
|
|
44
|
+
code = item['sCI']
|
|
45
|
+
name = item['n']
|
|
46
|
+
sub = ensure_subscription_path(component_id, code, name)
|
|
47
|
+
sub['uRt'] = item['uRt']
|
|
48
|
+
sub['sOc'] = item['sOc']
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def subscribe_to_status(component_id, status_list, options = {})
|
|
53
|
+
validate_ready 'subscribe to status'
|
|
54
|
+
m_id = options[:m_id] || RSMP::Message.make_m_id
|
|
55
|
+
subscribe_list = status_list.map { |item| item.slice('sCI', 'n', 'uRt', 'sOc') }
|
|
56
|
+
|
|
57
|
+
update_subscription(component_id, subscribe_list)
|
|
58
|
+
find_component component_id
|
|
59
|
+
|
|
60
|
+
message = RSMP::StatusSubscribe.new({
|
|
61
|
+
'cId' => component_id,
|
|
62
|
+
'sS' => subscribe_list,
|
|
63
|
+
'mId' => m_id
|
|
64
|
+
})
|
|
65
|
+
apply_nts_message_attributes message
|
|
66
|
+
|
|
67
|
+
send_and_optionally_collect message, options do |collect_options|
|
|
68
|
+
StatusCollector.new(
|
|
69
|
+
self,
|
|
70
|
+
status_list,
|
|
71
|
+
collect_options.merge(task: @task, m_id: m_id)
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def remove_subscription_item(component_id, code, name)
|
|
77
|
+
return unless @status_subscriptions.dig(component_id, code, name)
|
|
78
|
+
|
|
79
|
+
@status_subscriptions[component_id][code].delete name
|
|
80
|
+
@status_subscriptions[component_id].delete(code) if @status_subscriptions[component_id][code].empty?
|
|
81
|
+
@status_subscriptions.delete(component_id) if @status_subscriptions[component_id].empty?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def unsubscribe_to_status(component_id, status_list, options = {})
|
|
85
|
+
validate_ready 'unsubscribe to status'
|
|
86
|
+
|
|
87
|
+
status_list.each do |item|
|
|
88
|
+
remove_subscription_item(component_id, item['sCI'], item['n'])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
message = RSMP::StatusUnsubscribe.new({
|
|
92
|
+
'cId' => component_id,
|
|
93
|
+
'sS' => status_list
|
|
94
|
+
})
|
|
95
|
+
apply_nts_message_attributes message
|
|
96
|
+
send_message message, validate: options[:validate]
|
|
97
|
+
message
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def process_status_update(message)
|
|
101
|
+
component = find_component message.attribute('cId')
|
|
102
|
+
component.check_repeat_values message, @status_subscriptions
|
|
103
|
+
component.store_status message
|
|
104
|
+
log "Received #{message.type}", message: message, level: :log
|
|
105
|
+
acknowledge message
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Handles a supervisor connection to a remote client
|
|
2
|
+
|
|
3
|
+
module RSMP
|
|
4
|
+
class SiteProxy < Proxy
|
|
5
|
+
include Components
|
|
6
|
+
include Modules::Status
|
|
7
|
+
include Modules::AggregatedStatus
|
|
8
|
+
include Modules::Alarms
|
|
9
|
+
include Modules::Commands
|
|
10
|
+
|
|
11
|
+
attr_reader :supervisor, :site_id
|
|
12
|
+
|
|
13
|
+
def initialize(options)
|
|
14
|
+
super(options.merge(node: options[:supervisor]))
|
|
15
|
+
initialize_components
|
|
16
|
+
@supervisor = options[:supervisor]
|
|
17
|
+
@settings = @supervisor.supervisor_settings.clone
|
|
18
|
+
@site_id = options[:site_id]
|
|
19
|
+
@status_subscriptions = {}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# handle communication
|
|
23
|
+
# when we're created, the socket is already open
|
|
24
|
+
def run
|
|
25
|
+
self.state = :connected
|
|
26
|
+
start_reader
|
|
27
|
+
wait_for_reader # run until disconnected
|
|
28
|
+
rescue RSMP::ConnectionError => e
|
|
29
|
+
log e, level: :error
|
|
30
|
+
rescue StandardError => e
|
|
31
|
+
distribute_error e, level: :internal
|
|
32
|
+
ensure
|
|
33
|
+
close
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def revive(options)
|
|
37
|
+
super
|
|
38
|
+
@supervisor = options[:supervisor]
|
|
39
|
+
@settings = @supervisor.supervisor_settings.clone
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def inspect
|
|
43
|
+
"#<#{self.class.name}:#{object_id}, #{inspector(
|
|
44
|
+
:@acknowledgements, :@settings, :@site_settings, :@components
|
|
45
|
+
)}>"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def node
|
|
49
|
+
supervisor
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def handshake_complete
|
|
53
|
+
super
|
|
54
|
+
sanitized_sxl_version = RSMP::Schema.sanitize_version(@site_sxl_version)
|
|
55
|
+
log "Connection to site #{@site_id} established, using core #{@core_version}, #{@sxl} #{sanitized_sxl_version}",
|
|
56
|
+
level: :info
|
|
57
|
+
start_watchdog
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def process_message(message)
|
|
61
|
+
return super if handled_by_parent?(message)
|
|
62
|
+
|
|
63
|
+
case message
|
|
64
|
+
when StatusUnsubscribe, AggregatedStatusRequest
|
|
65
|
+
will_not_handle message
|
|
66
|
+
when AggregatedStatus
|
|
67
|
+
process_aggregated_status message
|
|
68
|
+
when AlarmIssue, AlarmSuspended, AlarmResumed, AlarmAcknowledged
|
|
69
|
+
process_alarm message
|
|
70
|
+
when CommandResponse
|
|
71
|
+
process_command_response message
|
|
72
|
+
when StatusResponse
|
|
73
|
+
process_status_response message
|
|
74
|
+
when StatusUpdate
|
|
75
|
+
process_status_update message
|
|
76
|
+
else
|
|
77
|
+
super
|
|
78
|
+
end
|
|
79
|
+
rescue RSMP::RepeatedAlarmError, RSMP::RepeatedStatusError, RSMP::TimestampError => e
|
|
80
|
+
str = "Rejected #{message.type} message,"
|
|
81
|
+
dont_acknowledge message, str, e.to_s
|
|
82
|
+
distribute_error e.exception("#{str}#{e.message} #{message.json}")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def handled_by_parent?(message)
|
|
86
|
+
message.is_a?(CommandRequest) || message.is_a?(StatusRequest) || message.is_a?(StatusSubscribe)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def version_accepted(message)
|
|
90
|
+
log "Received Version message for site #{@site_id}", message: message, level: :log
|
|
91
|
+
start_timer
|
|
92
|
+
acknowledge message
|
|
93
|
+
send_version @site_id, core_versions
|
|
94
|
+
@version_determined = true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def acknowledged_first_ingoing(message)
|
|
98
|
+
case message.type
|
|
99
|
+
when 'Watchdog'
|
|
100
|
+
send_watchdog
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def acknowledged_first_outgoing(message)
|
|
105
|
+
case message.type
|
|
106
|
+
when 'Watchdog'
|
|
107
|
+
handshake_complete
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def validate_ready(action)
|
|
112
|
+
raise NotReady, "Can't #{action} because connection is not ready. (Currently #{@state})" unless ready?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def version_acknowledged; end
|
|
116
|
+
|
|
117
|
+
def site_ids_changed
|
|
118
|
+
@supervisor.site_ids_changed
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def watchdog_interval=(interval)
|
|
122
|
+
@settings['intervals']['watchdog'] = interval
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def check_sxl_version(message)
|
|
126
|
+
# check that we have a schema for specified sxl type and version
|
|
127
|
+
# note that the type comes from the site config, while the version
|
|
128
|
+
# comes from the Version message send by the site
|
|
129
|
+
type = @site_settings['sxl']
|
|
130
|
+
version = message.attribute 'SXL'
|
|
131
|
+
RSMP::Schema.find_schema! type, version, lenient: true
|
|
132
|
+
|
|
133
|
+
# store sxl version requested by site
|
|
134
|
+
# TODO should check agaist site settings
|
|
135
|
+
@site_sxl_version = message.attribute 'SXL'
|
|
136
|
+
rescue RSMP::Schema::UnknownSchemaError => e
|
|
137
|
+
dont_acknowledge message, "Rejected #{message.type} message,", e.to_s
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def sxl_version
|
|
141
|
+
# a supervisor does not maintain it's own sxl version
|
|
142
|
+
# instead we use what the site requests
|
|
143
|
+
@site_sxl_version
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def process_version(message)
|
|
147
|
+
return extraneous_version message if @version_determined
|
|
148
|
+
|
|
149
|
+
check_site_ids message
|
|
150
|
+
check_sxl_version message
|
|
151
|
+
version_accepted message
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def check_site_ids(message)
|
|
155
|
+
# RSMP support multiple site ids. we don't support this yet. instead we use the first id only
|
|
156
|
+
site_id = message.attribute('siteId').map { |item| item['sId'] }.first
|
|
157
|
+
@supervisor.check_site_id site_id
|
|
158
|
+
site_ids_changed
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def find_site_settings(_site_id)
|
|
162
|
+
if @settings['sites'] && @settings['sites'][@site_id]
|
|
163
|
+
log "Using site settings for site id #{@site_id}", level: :debug
|
|
164
|
+
return @settings['sites'][@site_id]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
@settings['guest']
|
|
168
|
+
if @settings['guest']
|
|
169
|
+
log 'Using site settings for guest', level: :debug
|
|
170
|
+
return @settings['guest']
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
nil
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def setup_site_settings
|
|
177
|
+
@site_settings = find_site_settings @site_id
|
|
178
|
+
if @site_settings
|
|
179
|
+
@sxl = @site_settings['sxl']
|
|
180
|
+
setup_components @site_settings['components']
|
|
181
|
+
else
|
|
182
|
+
dont_acknowledge message, 'Rejected', "No config found for site #{@site_id}"
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def receive_error(error, options = {})
|
|
187
|
+
@supervisor&.receive_error error, options
|
|
188
|
+
distribute_error error, options
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def build_component(id:, type:, settings: {})
|
|
192
|
+
settings ||= {}
|
|
193
|
+
if type == 'main'
|
|
194
|
+
ComponentProxy.new id: id, node: self, grouped: true,
|
|
195
|
+
ntsoid: settings['ntsOId'], xnid: settings['xNId']
|
|
196
|
+
else
|
|
197
|
+
ComponentProxy.new id: id, node: self, grouped: false
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def infer_component_type(_component_id)
|
|
202
|
+
ComponentProxy
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class SupervisorProxy < Proxy
|
|
3
|
+
module Modules
|
|
4
|
+
# Handles aggregated status messages
|
|
5
|
+
module AggregatedStatus
|
|
6
|
+
def send_all_aggregated_status
|
|
7
|
+
@site.components.each_pair do |_c_id, component|
|
|
8
|
+
send_aggregated_status component if component.grouped
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Send aggregated status for a component
|
|
13
|
+
def send_aggregated_status(component, options = {})
|
|
14
|
+
m_id = options[:m_id] || RSMP::Message.make_m_id
|
|
15
|
+
|
|
16
|
+
se = if Proxy.version_meets_requirement?(core_version, '<=3.1.2')
|
|
17
|
+
component.aggregated_status_bools.map { |bool| bool ? 'true' : 'false' }
|
|
18
|
+
else
|
|
19
|
+
component.aggregated_status_bools
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
message = RSMP::AggregatedStatus.new({
|
|
23
|
+
'aSTS' => clock.to_s,
|
|
24
|
+
'cId' => component.c_id,
|
|
25
|
+
'fP' => nil,
|
|
26
|
+
'fS' => nil,
|
|
27
|
+
'se' => se,
|
|
28
|
+
'mId' => m_id
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
apply_nts_message_attributes message
|
|
32
|
+
send_and_optionally_collect message, options do |collect_options|
|
|
33
|
+
Collector.new self, collect_options.merge(task: @task, type: 'MessageAck')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def process_aggregated_status_request(message)
|
|
38
|
+
log "Received #{message.type}", message: message, level: :log
|
|
39
|
+
component_id = message.attributes['cId']
|
|
40
|
+
component = @site.find_component component_id
|
|
41
|
+
acknowledge message
|
|
42
|
+
send_aggregated_status component
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|