rsmp 0.52.0 → 0.53.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/CHANGELOG.md +3 -0
- data/Gemfile.lock +1 -1
- data/documentation/configuration.md +53 -2
- data/lib/rsmp/component/component_base.rb +10 -1
- data/lib/rsmp/component/component_proxy.rb +1 -1
- data/lib/rsmp/component/components.rb +24 -2
- data/lib/rsmp/node/site/site.rb +21 -7
- data/lib/rsmp/node/site/status.rb +39 -0
- data/lib/rsmp/node/supervisor/modules/connection.rb +20 -4
- data/lib/rsmp/node/supervisor/modules/sites.rb +2 -0
- data/lib/rsmp/options/schemas/required_settings.json +20 -12
- data/lib/rsmp/options/schemas/supervisor_site.json +1 -1
- data/lib/rsmp/options/site_options.rb +1 -0
- data/lib/rsmp/proxy/modules/acknowledgements.rb +2 -0
- data/lib/rsmp/proxy/modules/receive.rb +32 -2
- data/lib/rsmp/proxy/modules/send.rb +23 -3
- data/lib/rsmp/proxy/modules/version_validation.rb +76 -0
- data/lib/rsmp/proxy/modules/versions.rb +21 -22
- data/lib/rsmp/proxy/proxy.rb +21 -3
- data/lib/rsmp/proxy/site/modules/aggregated_status.rb +22 -2
- data/lib/rsmp/proxy/site/modules/commands.rb +1 -1
- data/lib/rsmp/proxy/site/site_proxy.rb +22 -13
- data/lib/rsmp/proxy/site/sxl_selection.rb +13 -22
- data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +56 -1
- data/lib/rsmp/proxy/supervisor/modules/alarms.rb +11 -4
- data/lib/rsmp/proxy/supervisor/modules/commands.rb +4 -3
- data/lib/rsmp/proxy/supervisor/modules/message_buffer.rb +6 -1
- data/lib/rsmp/proxy/supervisor/modules/status.rb +46 -1
- data/lib/rsmp/proxy/supervisor/modules/status_updates.rb +17 -1
- data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +12 -16
- data/lib/rsmp/schema/validation.rb +2 -1
- data/lib/rsmp/version.rb +1 -1
- data/schemas/core/3.1.2/definitions.json +5 -0
- data/schemas/core/3.1.2/status_subscribe.json +1 -1
- data/schemas/core/3.1.3/definitions.json +5 -0
- data/schemas/core/3.1.3/status_subscribe.json +1 -1
- data/schemas/core/3.1.4/definitions.json +5 -0
- data/schemas/core/3.1.4/status_subscribe.json +1 -1
- data/schemas/core/3.1.5/definitions.json +5 -0
- data/schemas/core/3.1.5/status_subscribe.json +1 -1
- data/schemas/core/3.2.0/definitions.json +5 -0
- data/schemas/core/3.2.0/status_subscribe.json +1 -1
- data/schemas/core/3.2.1/definitions.json +5 -0
- data/schemas/core/3.2.1/status_subscribe.json +1 -1
- data/schemas/core/3.2.2/definitions.json +5 -0
- data/schemas/core/3.2.2/status_subscribe.json +1 -1
- data/schemas/core/3.3.0/aggregated_status.json +17 -11
- data/schemas/core/3.3.0/aggregated_status_request.json +4 -4
- data/schemas/core/3.3.0/component_list.json +1 -1
- data/schemas/core/3.3.0/definitions.json +23 -2
- data/schemas/core/3.3.0/status_subscribe.json +14 -4
- data/schemas/core/3.3.0/version.json +46 -26
- data/schemas/tlc/1.0.10/defs/definitions.json +5 -0
- data/schemas/tlc/1.0.13/defs/definitions.json +5 -0
- data/schemas/tlc/1.0.14/defs/definitions.json +5 -0
- data/schemas/tlc/1.0.15/defs/definitions.json +5 -0
- data/schemas/tlc/1.0.7/defs/definitions.json +5 -0
- data/schemas/tlc/1.0.8/defs/definitions.json +5 -0
- data/schemas/tlc/1.0.9/defs/definitions.json +5 -0
- data/schemas/tlc/1.1.0/defs/definitions.json +5 -0
- data/schemas/tlc/1.2.0/defs/definitions.json +5 -0
- data/schemas/tlc/1.2.1/defs/definitions.json +5 -0
- data/schemas/tlc/1.3.0/defs/definitions.json +7 -0
- metadata +3 -1
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
require_relative 'version_validation'
|
|
2
|
+
|
|
1
3
|
module RSMP
|
|
2
4
|
class Proxy
|
|
3
5
|
module Modules
|
|
4
6
|
# Version negotiation and handling
|
|
5
7
|
# Manages RSMP version handshake between sites and supervisors
|
|
6
8
|
module Versions
|
|
9
|
+
include VersionValidation
|
|
10
|
+
|
|
7
11
|
def core_versions
|
|
8
12
|
version = @site_settings['core_version']
|
|
9
13
|
if version == 'latest'
|
|
@@ -25,8 +29,10 @@ module RSMP
|
|
|
25
29
|
versions.uniq
|
|
26
30
|
end
|
|
27
31
|
|
|
28
|
-
def
|
|
29
|
-
|
|
32
|
+
def core_3_3_0_or_newer?
|
|
33
|
+
return false unless core_version
|
|
34
|
+
|
|
35
|
+
version_meets_requirement?(core_version, '>=3.3.0')
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
def configured_sxls
|
|
@@ -48,7 +54,8 @@ module RSMP
|
|
|
48
54
|
end
|
|
49
55
|
|
|
50
56
|
def check_core_version(message)
|
|
51
|
-
|
|
57
|
+
validate_version_array!(message)
|
|
58
|
+
versions = @offered_core_versions || core_versions
|
|
52
59
|
candidates = message.versions.filter_map do |wire_version|
|
|
53
60
|
normalized = RSMP::Schema.normalize_core_version(wire_version)
|
|
54
61
|
[wire_version, normalized] if normalized && versions.include?(normalized)
|
|
@@ -60,7 +67,6 @@ module RSMP
|
|
|
60
67
|
else
|
|
61
68
|
reason = "RSMP versions [#{message.versions.join(', ')}] requested, " \
|
|
62
69
|
"but only [#{versions.join(', ')}] supported."
|
|
63
|
-
dont_acknowledge message, 'Version message rejected', reason, force: true
|
|
64
70
|
raise HandshakeError, reason
|
|
65
71
|
end
|
|
66
72
|
end
|
|
@@ -80,13 +86,13 @@ module RSMP
|
|
|
80
86
|
end
|
|
81
87
|
|
|
82
88
|
def send_version_response(site_id, core_versions)
|
|
83
|
-
if
|
|
89
|
+
if core_3_3_0_or_newer?
|
|
84
90
|
send_generated_message Version.new({
|
|
85
91
|
'step' => 'Response',
|
|
86
92
|
'RSMP' => [{ 'vers' => core_version }],
|
|
87
93
|
'supervisorId' => site_id,
|
|
88
94
|
'SXLS' => version_response_sxls,
|
|
89
|
-
'
|
|
95
|
+
'useAlarms' => @site_settings['use_alarms'] != false
|
|
90
96
|
}), validate: false
|
|
91
97
|
else
|
|
92
98
|
send_legacy_version_response(site_id, core_versions)
|
|
@@ -106,7 +112,14 @@ module RSMP
|
|
|
106
112
|
|
|
107
113
|
def send_version_message(site_id, core_versions, step:)
|
|
108
114
|
attributes = version_message_attributes(site_id, core_versions)
|
|
109
|
-
|
|
115
|
+
if step == 'Request'
|
|
116
|
+
@offered_core_versions = normalized_core_versions(core_versions).filter_map do |version|
|
|
117
|
+
RSMP::Schema.normalize_core_version(version)
|
|
118
|
+
end
|
|
119
|
+
legacy_sxl = attributes['SXL']
|
|
120
|
+
attributes.merge!(version_request_attributes)
|
|
121
|
+
attributes['SXL'] = legacy_sxl unless @offered_core_versions.include?('3.3.0')
|
|
122
|
+
end
|
|
110
123
|
send_generated_message Version.new(attributes), validate: false
|
|
111
124
|
end
|
|
112
125
|
|
|
@@ -151,6 +164,7 @@ module RSMP
|
|
|
151
164
|
def version_request_attributes
|
|
152
165
|
{
|
|
153
166
|
'step' => 'Request',
|
|
167
|
+
'SXL' => primary_configured_sxl ? sxl_request_items.first['version'] : '',
|
|
154
168
|
'SXLS' => sxl_request_items
|
|
155
169
|
}
|
|
156
170
|
end
|
|
@@ -159,21 +173,6 @@ module RSMP
|
|
|
159
173
|
accepted_sxls + rejected_sxls
|
|
160
174
|
end
|
|
161
175
|
|
|
162
|
-
def validate_sxl_response!(sxls)
|
|
163
|
-
invalid = sxls.find { |accepted| !valid_sxl_response?(accepted) }
|
|
164
|
-
return unless invalid
|
|
165
|
-
|
|
166
|
-
raise HandshakeError,
|
|
167
|
-
"Invalid SXL version #{invalid['name']} #{invalid['version']}; " \
|
|
168
|
-
'Core 3.3 requires an exact MAJOR.MINOR.PATCH match'
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def valid_sxl_response?(accepted)
|
|
172
|
-
requested = sxl_request_items.find { |item| item['name'] == accepted['name'] }
|
|
173
|
-
RSMP::Schema.strict_version?(accepted['version']) && requested &&
|
|
174
|
-
requested['version'] == accepted['version']
|
|
175
|
-
end
|
|
176
|
-
|
|
177
176
|
def version_acknowledged; end
|
|
178
177
|
|
|
179
178
|
def component_list_acknowledged; end
|
data/lib/rsmp/proxy/proxy.rb
CHANGED
|
@@ -80,6 +80,10 @@ module RSMP
|
|
|
80
80
|
@ingoing_acknowledged = {}
|
|
81
81
|
@outgoing_acknowledged = {}
|
|
82
82
|
@latest_watchdog_send_at = nil
|
|
83
|
+
@last_site_status = nil
|
|
84
|
+
@last_component_list = nil
|
|
85
|
+
@initial_aggregated_status_id = nil
|
|
86
|
+
@site_aggregated_status = nil
|
|
83
87
|
@component_list_received = false
|
|
84
88
|
@outgoing_watchdog_acknowledged = false
|
|
85
89
|
@sxl_interfaces = {}
|
|
@@ -104,7 +108,7 @@ module RSMP
|
|
|
104
108
|
@sxls = []
|
|
105
109
|
@accepted_sxls = []
|
|
106
110
|
@rejected_sxls = []
|
|
107
|
-
@
|
|
111
|
+
@use_alarms = true
|
|
108
112
|
@site_settings = nil # can't pick until we know the site id
|
|
109
113
|
collect = options[:collect]
|
|
110
114
|
return unless collect
|
|
@@ -135,8 +139,22 @@ module RSMP
|
|
|
135
139
|
schemas
|
|
136
140
|
end
|
|
137
141
|
|
|
138
|
-
def
|
|
139
|
-
|
|
142
|
+
def application_message?(message)
|
|
143
|
+
!%w[Version Watchdog MessageAck MessageNotAck ComponentList AggregatedStatus].include?(message.type)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def sxl_message_allowed?(message)
|
|
147
|
+
return true unless core_3_3_0_or_newer?
|
|
148
|
+
return false if message.is_a?(Alarm) && !use_alarms?
|
|
149
|
+
return true if RSMP::Schema.core_message_type?(message.attributes)
|
|
150
|
+
|
|
151
|
+
!RSMP::Schema.resolve_sxl(message.attributes, schemas: schemas).nil?
|
|
152
|
+
rescue RSMP::Schema::UnknownMessageCodeError
|
|
153
|
+
false
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def use_alarms?
|
|
157
|
+
!core_3_3_0_or_newer? || @use_alarms != false
|
|
140
158
|
end
|
|
141
159
|
|
|
142
160
|
def primary_sxl
|
|
@@ -4,7 +4,7 @@ module RSMP
|
|
|
4
4
|
# Handles aggregated status requests and responses
|
|
5
5
|
module AggregatedStatus
|
|
6
6
|
# Build and send an AggregatedStatusRequest. Returns Result<AggregatedStatusRequest>.
|
|
7
|
-
def request_aggregated_status(component, m_id: nil, validate: true)
|
|
7
|
+
def request_aggregated_status(component = nil, m_id: nil, validate: true)
|
|
8
8
|
readiness = validate_ready 'request aggregated status'
|
|
9
9
|
return readiness if readiness.failure?
|
|
10
10
|
|
|
@@ -13,6 +13,7 @@ module RSMP
|
|
|
13
13
|
'cId' => component,
|
|
14
14
|
'mId' => m_id
|
|
15
15
|
})
|
|
16
|
+
message.attributes.delete('cId') if core_3_3_0_or_newer?
|
|
16
17
|
apply_nts_message_attributes message
|
|
17
18
|
send_message(message, validate: validate).map(&:message)
|
|
18
19
|
end
|
|
@@ -22,7 +23,7 @@ module RSMP
|
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
# Build, send an AggregatedStatusRequest and collect the response. Returns Result<Exchange>.
|
|
25
|
-
def request_aggregated_status_and_collect(component, within:, m_id: nil, validate: true)
|
|
26
|
+
def request_aggregated_status_and_collect(component = nil, within:, m_id: nil, validate: true)
|
|
26
27
|
readiness = validate_ready 'request aggregated status'
|
|
27
28
|
return readiness if readiness.failure?
|
|
28
29
|
|
|
@@ -31,6 +32,7 @@ module RSMP
|
|
|
31
32
|
'cId' => component,
|
|
32
33
|
'mId' => m_id
|
|
33
34
|
})
|
|
35
|
+
message.attributes.delete('cId') if core_3_3_0_or_newer?
|
|
34
36
|
apply_nts_message_attributes message
|
|
35
37
|
collector = AggregatedStatusCollector.new(self, timeout: within, m_id: m_id, num: 1)
|
|
36
38
|
send_message_and_collect(message, collector, validate: validate)
|
|
@@ -48,6 +50,8 @@ module RSMP
|
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
def process_aggregated_status(message)
|
|
53
|
+
return process_site_aggregated_status(message) if core_3_3_0_or_newer?
|
|
54
|
+
|
|
51
55
|
status_elements = message.attribute('se')
|
|
52
56
|
validate_aggregated_status(message, status_elements)
|
|
53
57
|
c_id = message.attributes['cId']
|
|
@@ -64,6 +68,22 @@ module RSMP
|
|
|
64
68
|
acknowledge message
|
|
65
69
|
end
|
|
66
70
|
|
|
71
|
+
def process_site_aggregated_status(message)
|
|
72
|
+
@site_aggregated_status = message.attribute('se').dup.freeze
|
|
73
|
+
acknowledge message
|
|
74
|
+
@supervisor.site_aggregated_status_changed(self)
|
|
75
|
+
complete_initial_status(message)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
attr_reader :site_aggregated_status
|
|
79
|
+
|
|
80
|
+
def complete_initial_status(message)
|
|
81
|
+
return unless core_3_3_0_or_newer? && !ready? && @component_list_received && @outgoing_watchdog_acknowledged
|
|
82
|
+
return unless message.is_a?(RSMP::AggregatedStatus)
|
|
83
|
+
|
|
84
|
+
handshake_complete
|
|
85
|
+
end
|
|
86
|
+
|
|
67
87
|
def aggregated_status_changed(component, _options = {})
|
|
68
88
|
@supervisor.aggregated_status_changed self, component
|
|
69
89
|
end
|
|
@@ -66,7 +66,7 @@ module RSMP
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def process_command_response(message)
|
|
69
|
-
return reject_multiple_command_codes(message) if
|
|
69
|
+
return reject_multiple_command_codes(message) if core_3_3_0_or_newer? && multiple_command_codes?(message)
|
|
70
70
|
|
|
71
71
|
log "Received #{message.type}", message: message, level: :log
|
|
72
72
|
acknowledge message
|
|
@@ -86,8 +86,9 @@ module RSMP
|
|
|
86
86
|
log "Received Version message for site #{@site_id}", message: message, level: :log
|
|
87
87
|
start_timer
|
|
88
88
|
acknowledge message
|
|
89
|
-
response_id =
|
|
89
|
+
response_id = core_3_3_0_or_newer? ? (@supervisor.site_id || @site_id) : @site_id
|
|
90
90
|
send_version_response response_id, core_versions
|
|
91
|
+
@use_alarms = @site_settings['use_alarms'] != false if core_3_3_0_or_newer?
|
|
91
92
|
@version_determined = true
|
|
92
93
|
end
|
|
93
94
|
|
|
@@ -101,9 +102,9 @@ module RSMP
|
|
|
101
102
|
def acknowledged_first_outgoing(message)
|
|
102
103
|
case message.type
|
|
103
104
|
when 'Watchdog'
|
|
104
|
-
if
|
|
105
|
+
if core_3_3_0_or_newer?
|
|
105
106
|
@outgoing_watchdog_acknowledged = true
|
|
106
|
-
|
|
107
|
+
|
|
107
108
|
else
|
|
108
109
|
handshake_complete
|
|
109
110
|
end
|
|
@@ -130,30 +131,35 @@ module RSMP
|
|
|
130
131
|
def process_component_list(message)
|
|
131
132
|
log "Received #{message.type}", message: message, level: :log
|
|
132
133
|
rebuild_components_from_list message.attributes['components']
|
|
133
|
-
build_sxl_interfaces
|
|
134
134
|
acknowledge message
|
|
135
135
|
@component_list_received = true
|
|
136
|
-
handshake_complete if @outgoing_watchdog_acknowledged
|
|
137
136
|
end
|
|
138
137
|
|
|
139
138
|
def rebuild_components_from_list(items)
|
|
140
139
|
main_id = @site_settings.dig('components', 'main')&.keys&.first
|
|
140
|
+
ids = items.map { |item| item['id'] }
|
|
141
|
+
raise MessageRejected, 'Duplicate component IDs' unless ids.uniq == ids
|
|
142
|
+
|
|
143
|
+
previous = @components
|
|
141
144
|
@components = {}
|
|
142
145
|
@main = nil
|
|
143
146
|
items.each do |item|
|
|
144
147
|
grouped = item['id'] == main_id
|
|
145
|
-
component =
|
|
146
|
-
id: item['id'],
|
|
147
|
-
node: self,
|
|
148
|
-
type: item['type'],
|
|
149
|
-
name: item['name'],
|
|
150
|
-
grouped: grouped
|
|
151
|
-
)
|
|
148
|
+
component = listed_component(item, previous[item['id']], grouped)
|
|
152
149
|
@components[component.c_id] = component
|
|
153
150
|
@main = component if grouped
|
|
154
151
|
end
|
|
155
152
|
end
|
|
156
153
|
|
|
154
|
+
def listed_component(item, previous, grouped)
|
|
155
|
+
if previous && previous.component_type == item['type']
|
|
156
|
+
previous.update_metadata(name: item['name'] || item['id'])
|
|
157
|
+
return previous
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
ComponentProxy.new(id: item['id'], node: self, type: item['type'], name: item['name'], grouped: grouped)
|
|
161
|
+
end
|
|
162
|
+
|
|
157
163
|
def site_ids_changed
|
|
158
164
|
@supervisor.site_ids_changed
|
|
159
165
|
end
|
|
@@ -165,8 +171,9 @@ module RSMP
|
|
|
165
171
|
def process_version(message)
|
|
166
172
|
return extraneous_version message if @version_determined
|
|
167
173
|
|
|
168
|
-
check_site_ids message
|
|
169
174
|
check_core_version message
|
|
175
|
+
validate_version! message, step: 'Request'
|
|
176
|
+
check_site_ids message
|
|
170
177
|
check_sxl_version message
|
|
171
178
|
version_accepted message
|
|
172
179
|
end
|
|
@@ -174,6 +181,8 @@ module RSMP
|
|
|
174
181
|
def check_site_ids(message)
|
|
175
182
|
# RSMP support multiple site ids. we don't support this yet. instead we use the first id only
|
|
176
183
|
site_id = message.attribute('siteId').map { |item| item['sId'] }.first
|
|
184
|
+
raise HandshakeError, 'Unexpected site identity' if core_3_3_0_or_newer? && @site_id && @site_id != site_id
|
|
185
|
+
|
|
177
186
|
@site_id ||= site_id
|
|
178
187
|
@supervisor.check_site_id site_id
|
|
179
188
|
site_ids_changed
|
|
@@ -2,7 +2,7 @@ module RSMP
|
|
|
2
2
|
# Selects the SXL versions accepted by a supervisor-side site proxy.
|
|
3
3
|
module SiteSxlSelection
|
|
4
4
|
def check_sxl_version(message)
|
|
5
|
-
if
|
|
5
|
+
if core_3_3_0_or_newer?
|
|
6
6
|
select_sxls message
|
|
7
7
|
else
|
|
8
8
|
select_legacy_sxl message
|
|
@@ -26,34 +26,25 @@ module RSMP
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def select_sxls(message)
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
selected = message.sxls.map { |requested| select_sxl(requested) }
|
|
30
|
+
offered = message.sxls.map { |item| item['name'] }
|
|
31
|
+
configured_sxls.each do |item|
|
|
32
|
+
selected << { 'name' => item['name'], 'status' => 'expected' } unless offered.include?(item['name'])
|
|
33
|
+
end
|
|
34
|
+
@accepted_sxls, @rejected_sxls = selected.partition { |item| item['status'] == 'ok' }
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
def select_sxl(requested)
|
|
34
38
|
configured = configured_sxls.find { |item| item['name'] == requested['name'] }
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
unless RSMP::Schema.strict_version?(requested['version'])
|
|
38
|
-
return rejected_sxl(requested, 2, 'Core 3.3 requires SXL versions to use MAJOR.MINOR.PATCH')
|
|
39
|
-
end
|
|
39
|
+
return { 'name' => requested['name'], 'status' => 'unsupported' } unless configured
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
if
|
|
43
|
-
RSMP::Schema.find_schema! requested['name'],
|
|
44
|
-
requested
|
|
41
|
+
version = RSMP::Schema.sanitize_version(configured['version'].to_s)
|
|
42
|
+
if version == requested['version']
|
|
43
|
+
RSMP::Schema.find_schema! requested['name'], version
|
|
44
|
+
{ 'name' => requested['name'], 'status' => 'ok', 'version' => version }
|
|
45
45
|
else
|
|
46
|
-
|
|
46
|
+
{ 'name' => requested['name'], 'status' => 'mismatch', 'supported' => [version] }
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
|
-
|
|
50
|
-
def rejected_sxl(requested, code, reason)
|
|
51
|
-
{
|
|
52
|
-
'name' => requested['name'],
|
|
53
|
-
'version' => requested['version'],
|
|
54
|
-
'rejected' => code,
|
|
55
|
-
'reason' => reason
|
|
56
|
-
}.compact
|
|
57
|
-
end
|
|
58
49
|
end
|
|
59
50
|
end
|
|
@@ -3,14 +3,44 @@ module RSMP
|
|
|
3
3
|
module Modules
|
|
4
4
|
# Handles aggregated status messages
|
|
5
5
|
module AggregatedStatus
|
|
6
|
+
def send_component_list
|
|
7
|
+
components = @site.component_list
|
|
8
|
+
result = send_generated_message ComponentList.new('components' => components)
|
|
9
|
+
@last_component_list = JSON.parse(JSON.generate(components)) if result.success?
|
|
10
|
+
result
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def synchronize_site_state
|
|
14
|
+
send_component_list if @site.component_list != @last_component_list
|
|
15
|
+
send_changed_site_status
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def component_list_acknowledged
|
|
19
|
+
return if ready? || @initial_aggregated_status_id
|
|
20
|
+
|
|
21
|
+
@initial_aggregated_status_id = Message.make_m_id
|
|
22
|
+
send_aggregated_status(nil, m_id: @initial_aggregated_status_id)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initial_status_acknowledged(original)
|
|
26
|
+
return unless core_3_3_0_or_newer? && original.m_id == @initial_aggregated_status_id
|
|
27
|
+
|
|
28
|
+
@initial_aggregated_status_id = nil
|
|
29
|
+
handshake_complete unless ready?
|
|
30
|
+
end
|
|
31
|
+
|
|
6
32
|
def send_all_aggregated_status
|
|
33
|
+
return send_site_aggregated_status if core_3_3_0_or_newer?
|
|
34
|
+
|
|
7
35
|
@site.components.each_pair do |_c_id, component|
|
|
8
36
|
send_aggregated_status component if component.grouped
|
|
9
37
|
end
|
|
10
38
|
end
|
|
11
39
|
|
|
12
40
|
# Send aggregated status for a component
|
|
13
|
-
def send_aggregated_status(component, m_id: nil)
|
|
41
|
+
def send_aggregated_status(component = nil, m_id: nil)
|
|
42
|
+
return send_site_aggregated_status(m_id: m_id) if core_3_3_0_or_newer?
|
|
43
|
+
|
|
14
44
|
m_id ||= RSMP::Message.make_m_id
|
|
15
45
|
|
|
16
46
|
se = if core_version && Proxy.version_meets_requirement?(core_version, '<=3.1.2')
|
|
@@ -32,8 +62,33 @@ module RSMP
|
|
|
32
62
|
send_generated_message message
|
|
33
63
|
end
|
|
34
64
|
|
|
65
|
+
def send_changed_site_status
|
|
66
|
+
bits = @site.site_aggregated_status
|
|
67
|
+
return if bits == @last_site_status
|
|
68
|
+
return if connected? && !ready?
|
|
69
|
+
|
|
70
|
+
result = send_site_aggregated_status
|
|
71
|
+
@last_site_status = bits if result.success?
|
|
72
|
+
result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def send_site_aggregated_status(m_id: nil)
|
|
76
|
+
message = RSMP::AggregatedStatus.new(
|
|
77
|
+
'mId' => m_id, 'aSTS' => clock.to_s, 'se' => @site.site_aggregated_status
|
|
78
|
+
)
|
|
79
|
+
apply_nts_message_attributes message
|
|
80
|
+
result = send_generated_message message
|
|
81
|
+
@last_site_status = message.attributes['se'].dup if result.success?
|
|
82
|
+
result
|
|
83
|
+
end
|
|
84
|
+
|
|
35
85
|
def process_aggregated_status_request(message)
|
|
36
86
|
log "Received #{message.type}", message: message, level: :log
|
|
87
|
+
if core_3_3_0_or_newer?
|
|
88
|
+
acknowledge message
|
|
89
|
+
return send_site_aggregated_status
|
|
90
|
+
end
|
|
91
|
+
|
|
37
92
|
component_id = message.attributes['cId']
|
|
38
93
|
component = @site.find_component component_id
|
|
39
94
|
acknowledge message
|
|
@@ -6,14 +6,21 @@ module RSMP
|
|
|
6
6
|
def send_active_alarms
|
|
7
7
|
@site.components.each_pair do |_c_id, component|
|
|
8
8
|
component.alarms.each_pair do |_alarm_code, alarm_state|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
next unless alarm_state.active || core_3_3_0_or_newer?
|
|
10
|
+
|
|
11
|
+
alarm = AlarmIssue.new(alarm_state.to_hash.merge('aSp' => 'Issue'))
|
|
12
|
+
next if core_3_3_0_or_newer? && !sxl_message_allowed?(alarm)
|
|
13
|
+
|
|
14
|
+
send_generated_message alarm
|
|
15
|
+
@initial_alarm_events << alarm_event_key(alarm) if core_3_3_0_or_newer? && @initial_alarm_events
|
|
13
16
|
end
|
|
14
17
|
end
|
|
15
18
|
end
|
|
16
19
|
|
|
20
|
+
def alarm_event_key(message)
|
|
21
|
+
message.attributes.except('mId', 'ntsOId', 'xNId')
|
|
22
|
+
end
|
|
23
|
+
|
|
17
24
|
def process_alarm(message)
|
|
18
25
|
case message
|
|
19
26
|
when AlarmAcknowledge
|
|
@@ -49,7 +49,7 @@ module RSMP
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def check_required_command_arguments(message)
|
|
52
|
-
return unless
|
|
52
|
+
return unless core_3_3_0_or_newer?
|
|
53
53
|
|
|
54
54
|
command_codes(message).each do |command_code|
|
|
55
55
|
provided = message.attributes['arg'].select { |item| item['cCI'] == command_code }.map { |item| item['n'] }
|
|
@@ -93,11 +93,12 @@ module RSMP
|
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
def process_command_request(message)
|
|
96
|
-
return reject_multiple_command_codes(message) if
|
|
96
|
+
return reject_multiple_command_codes(message) if core_3_3_0_or_newer? && multiple_command_codes?(message)
|
|
97
97
|
|
|
98
98
|
check_required_command_arguments message
|
|
99
99
|
component_id = message.attributes['cId']
|
|
100
100
|
rvs = build_command_rvs(message.attributes['arg'])
|
|
101
|
+
acknowledge message if core_3_3_0_or_newer?
|
|
101
102
|
execute_commands(message, component_id, rvs)
|
|
102
103
|
|
|
103
104
|
response = CommandResponse.new({
|
|
@@ -106,7 +107,7 @@ module RSMP
|
|
|
106
107
|
'rvs' => rvs
|
|
107
108
|
})
|
|
108
109
|
apply_nts_message_attributes response
|
|
109
|
-
acknowledge message
|
|
110
|
+
acknowledge message unless core_3_3_0_or_newer?
|
|
110
111
|
send_generated_message response
|
|
111
112
|
end
|
|
112
113
|
end
|
|
@@ -76,8 +76,9 @@ module RSMP
|
|
|
76
76
|
|
|
77
77
|
def prepare_message_for_buffer(message, core_version: @core_version, for_send: false)
|
|
78
78
|
return unless site_originated_buffer_candidate? message
|
|
79
|
+
return unless sxl_message_allowed?(message)
|
|
79
80
|
return false if message.is_a?(RSMP::StatusUpdate) && status_buffer_selectors == false
|
|
80
|
-
return false if message.is_a?(RSMP::Alarm) && !
|
|
81
|
+
return false if message.is_a?(RSMP::Alarm) && !use_alarms?
|
|
81
82
|
|
|
82
83
|
if message.is_a? RSMP::AggregatedStatus
|
|
83
84
|
prepare_aggregated_status_for_buffer message, core_version: core_version, for_send: for_send
|
|
@@ -121,6 +122,10 @@ module RSMP
|
|
|
121
122
|
end
|
|
122
123
|
|
|
123
124
|
def flush_buffered_message?(message, queued, index)
|
|
125
|
+
if core_3_3_0_or_newer? && message.is_a?(Alarm) && @initial_alarm_events&.include?(alarm_event_key(message))
|
|
126
|
+
return true
|
|
127
|
+
end
|
|
128
|
+
|
|
124
129
|
prepared = prepare_message_for_buffer message, core_version: @core_version, for_send: true
|
|
125
130
|
return true unless prepared
|
|
126
131
|
|
|
@@ -60,7 +60,8 @@ module RSMP
|
|
|
60
60
|
def add_status_subscription(component_id, subs, update_list, arg, now)
|
|
61
61
|
sci = arg['sCI']
|
|
62
62
|
name = arg['n']
|
|
63
|
-
subcription = { interval: arg['uRt'].
|
|
63
|
+
subcription = { interval: arg['uRt'].to_f, last_sent_at: now }
|
|
64
|
+
subcription[:on_change] = arg['sOc'] if core_3_3_0_or_newer?
|
|
64
65
|
subs[sci] ||= {}
|
|
65
66
|
subs[sci][name] = subcription
|
|
66
67
|
update_list[component_id][sci] ||= []
|
|
@@ -70,6 +71,8 @@ module RSMP
|
|
|
70
71
|
def process_status_subcribe(message)
|
|
71
72
|
log "Received #{message.type}", message: message, level: :log
|
|
72
73
|
|
|
74
|
+
return process_core_3_3_subscription(message) if core_3_3_0_or_newer?
|
|
75
|
+
|
|
73
76
|
update_list = {}
|
|
74
77
|
component_id = message.attributes['cId']
|
|
75
78
|
@status_subscriptions[component_id] ||= {}
|
|
@@ -84,6 +87,48 @@ module RSMP
|
|
|
84
87
|
send_status_updates update_list
|
|
85
88
|
end
|
|
86
89
|
|
|
90
|
+
def process_core_3_3_subscription(message)
|
|
91
|
+
validate_core_3_3_subscription!(message)
|
|
92
|
+
id = message.attributes['cId']
|
|
93
|
+
args = message.attributes['sS']
|
|
94
|
+
component = @site.find_component(id, build: false)
|
|
95
|
+
values = component ? fetch_status_values(component, args) : build_undefined_statuses(args)
|
|
96
|
+
subs = (@status_subscriptions[id] ||= {})
|
|
97
|
+
update_core_3_3_subscriptions(id, args, values, subs)
|
|
98
|
+
@status_subscriptions.delete(id) if subs.empty?
|
|
99
|
+
acknowledge message
|
|
100
|
+
update = core_3_3_subscription_update(id, values)
|
|
101
|
+
send_generated_message update
|
|
102
|
+
store_last_sent_status update
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def core_3_3_subscription_update(id, values)
|
|
106
|
+
values = values.map { |value| value.except('uRt', 'sOc') }
|
|
107
|
+
StatusUpdate.new('cId' => id, 'sTs' => clock.to_s, 'sS' => values)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def update_core_3_3_subscriptions(id, args, values, subs)
|
|
111
|
+
updates = { id => {} }
|
|
112
|
+
values.each_with_index do |value, index|
|
|
113
|
+
arg = args[index]
|
|
114
|
+
if %w[unknown undefined].include?(value['q'])
|
|
115
|
+
remove_status_subscription(subs, arg)
|
|
116
|
+
else
|
|
117
|
+
add_status_subscription(id, subs, updates, arg, Time.now)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def validate_core_3_3_subscription!(message)
|
|
123
|
+
invalid = message.attributes['sS'].any? do |arg|
|
|
124
|
+
interval = arg['uRt'].to_f
|
|
125
|
+
interval.negative? || (interval.zero? && !arg['sOc'])
|
|
126
|
+
end
|
|
127
|
+
return unless invalid
|
|
128
|
+
|
|
129
|
+
raise MessageRejected, 'Subscription interval must be nonnegative and enable interval or sendOnChange'
|
|
130
|
+
end
|
|
131
|
+
|
|
87
132
|
def get_status_subscribe_interval(component_id, sci, name)
|
|
88
133
|
@status_subscriptions.dig component_id, sci, name
|
|
89
134
|
end
|
|
@@ -22,7 +22,8 @@ module RSMP
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def check_on_change_update(subscription, component, code, name)
|
|
25
|
-
|
|
25
|
+
enabled = core_3_3_0_or_newer? ? subscription[:on_change] : subscription[:interval].zero?
|
|
26
|
+
return [nil, false] unless enabled
|
|
26
27
|
|
|
27
28
|
current = current_status_value(component, code, name)
|
|
28
29
|
last_sent = fetch_last_sent_status component.c_id, code, name
|
|
@@ -34,6 +35,10 @@ module RSMP
|
|
|
34
35
|
|
|
35
36
|
value, quality = *(component.get_status code, name)
|
|
36
37
|
rsmpify_value(value, quality)
|
|
38
|
+
rescue UnknownStatus
|
|
39
|
+
raise unless core_3_3_0_or_newer?
|
|
40
|
+
|
|
41
|
+
nil
|
|
37
42
|
end
|
|
38
43
|
|
|
39
44
|
def encode_status_value(code, name, value)
|
|
@@ -59,6 +64,8 @@ module RSMP
|
|
|
59
64
|
end
|
|
60
65
|
|
|
61
66
|
def status_update_timer(now)
|
|
67
|
+
return if core_3_3_0_or_newer? && connected? && !ready?
|
|
68
|
+
|
|
62
69
|
send_status_updates status_updates_due(now)
|
|
63
70
|
end
|
|
64
71
|
|
|
@@ -71,8 +78,15 @@ module RSMP
|
|
|
71
78
|
end
|
|
72
79
|
|
|
73
80
|
def collect_component_status_updates(update_list, component_id, by_code, now)
|
|
81
|
+
if core_3_3_0_or_newer? && !@site.components.key?(component_id)
|
|
82
|
+
@status_subscriptions.delete(component_id)
|
|
83
|
+
return
|
|
84
|
+
end
|
|
74
85
|
component = @site.find_component component_id
|
|
75
86
|
by_code.each_pair do |code, by_name|
|
|
87
|
+
probe = StatusUpdate.new('sS' => [{ 'sCI' => code }])
|
|
88
|
+
next if core_3_3_0_or_newer? && !sxl_message_allowed?(probe)
|
|
89
|
+
|
|
76
90
|
by_name.each_pair do |name, subscription|
|
|
77
91
|
current, should_send = check_status_subscription(subscription, component, code, name, now)
|
|
78
92
|
next unless should_send
|
|
@@ -90,6 +104,8 @@ module RSMP
|
|
|
90
104
|
end
|
|
91
105
|
|
|
92
106
|
def precomputed_status_value(subscription, current)
|
|
107
|
+
return if core_3_3_0_or_newer? # Fetch value and quality together; do not replace quality with recent.
|
|
108
|
+
|
|
93
109
|
PrecomputedStatusValue.new(current) if subscription[:interval].zero?
|
|
94
110
|
end
|
|
95
111
|
|