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
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
module RSMP
|
|
2
|
-
|
|
3
2
|
# The state of an alarm on a component.
|
|
4
3
|
# The alarm state is for a particular alarm code,
|
|
5
4
|
# a component typically have an alarm state for each
|
|
@@ -8,10 +7,8 @@ module RSMP
|
|
|
8
7
|
class AlarmState
|
|
9
8
|
attr_reader :component_id, :code, :acknowledged, :suspended, :active, :timestamp, :category, :priority, :rvs
|
|
10
9
|
|
|
11
|
-
def self.create_from_message
|
|
12
|
-
|
|
13
|
-
component: component,
|
|
14
|
-
code: message.attribute("aCId"),
|
|
10
|
+
def self.create_from_message(component, message)
|
|
11
|
+
options = {
|
|
15
12
|
timestamp: RSMP::Clock.parse(message.attribute('aTs')),
|
|
16
13
|
acknowledged: message.attribute('ack') == 'Acknowledged',
|
|
17
14
|
suspended: message.attribute('aS') == 'Suspended',
|
|
@@ -19,22 +16,21 @@ module RSMP
|
|
|
19
16
|
category: message.attribute('cat'),
|
|
20
17
|
priority: message.attribute('pri').to_i,
|
|
21
18
|
rvs: message.attribute('rvs')
|
|
22
|
-
|
|
19
|
+
}
|
|
20
|
+
new(component: component, code: message.attribute('aCId'), **options)
|
|
23
21
|
end
|
|
24
22
|
|
|
25
|
-
def initialize
|
|
26
|
-
suspended: false, acknowledged: false, active: false, timestamp: nil,
|
|
27
|
-
category: 'D', priority: 2, rvs: []
|
|
23
|
+
def initialize(component:, code:, **options)
|
|
28
24
|
@component = component
|
|
29
25
|
@component_id = component.c_id
|
|
30
26
|
@code = code
|
|
31
|
-
@suspended = !!suspended
|
|
32
|
-
@acknowledged = !!acknowledged
|
|
33
|
-
@active = !!active
|
|
34
|
-
@timestamp =
|
|
35
|
-
@category = category || 'D'
|
|
36
|
-
@priority = priority || 2
|
|
37
|
-
@rvs = rvs
|
|
27
|
+
@suspended = !!options[:suspended]
|
|
28
|
+
@acknowledged = !!options[:acknowledged]
|
|
29
|
+
@active = !!options[:active]
|
|
30
|
+
@timestamp = options[:timestamp]
|
|
31
|
+
@category = options[:category] || 'D'
|
|
32
|
+
@priority = options[:priority] || 2
|
|
33
|
+
@rvs = options[:rvs] || []
|
|
38
34
|
end
|
|
39
35
|
|
|
40
36
|
def to_hash
|
|
@@ -52,19 +48,22 @@ module RSMP
|
|
|
52
48
|
end
|
|
53
49
|
|
|
54
50
|
def acknowledge
|
|
55
|
-
change
|
|
51
|
+
change = !@acknowledged
|
|
52
|
+
@acknowledged = true
|
|
56
53
|
update_timestamp if change
|
|
57
54
|
change
|
|
58
55
|
end
|
|
59
56
|
|
|
60
57
|
def suspend
|
|
61
|
-
change
|
|
58
|
+
change = !@suspended
|
|
59
|
+
@suspended = true
|
|
62
60
|
update_timestamp if change
|
|
63
61
|
change
|
|
64
62
|
end
|
|
65
63
|
|
|
66
64
|
def resume
|
|
67
|
-
change
|
|
65
|
+
change = @suspended
|
|
66
|
+
@suspended = false
|
|
68
67
|
update_timestamp if change
|
|
69
68
|
change
|
|
70
69
|
end
|
|
@@ -73,29 +72,33 @@ module RSMP
|
|
|
73
72
|
# is when it's activated. See:
|
|
74
73
|
# https://rsmp-nordic.org/rsmp_specifications/core/3.2.0/applicability/basic_structure.html#alarm-status
|
|
75
74
|
def activate
|
|
76
|
-
change
|
|
75
|
+
change = !@active
|
|
76
|
+
@active = true
|
|
77
|
+
@acknowledged = false
|
|
77
78
|
update_timestamp if change
|
|
78
79
|
change
|
|
79
80
|
end
|
|
80
81
|
|
|
81
82
|
def deactivate
|
|
82
|
-
change
|
|
83
|
+
change = @active
|
|
84
|
+
@active = false
|
|
83
85
|
update_timestamp if change
|
|
84
86
|
change
|
|
85
87
|
end
|
|
86
|
-
|
|
88
|
+
|
|
87
89
|
def update_timestamp
|
|
88
90
|
@timestamp = @component.now
|
|
89
91
|
end
|
|
90
92
|
|
|
91
|
-
def differ_from_message?
|
|
92
|
-
return true if
|
|
93
|
-
return true if
|
|
94
|
-
return true if
|
|
95
|
-
return true if
|
|
96
|
-
return true if
|
|
97
|
-
return true if
|
|
98
|
-
|
|
93
|
+
def differ_from_message?(message)
|
|
94
|
+
return true if timestamp_differs?(message)
|
|
95
|
+
return true if acknowledgment_differs?(message)
|
|
96
|
+
return true if suspension_differs?(message)
|
|
97
|
+
return true if activity_differs?(message)
|
|
98
|
+
return true if category_differs?(message)
|
|
99
|
+
return true if priority_differs?(message)
|
|
100
|
+
|
|
101
|
+
# return true @rvs = message.attribute('rvs')
|
|
99
102
|
false
|
|
100
103
|
end
|
|
101
104
|
|
|
@@ -103,20 +106,20 @@ module RSMP
|
|
|
103
106
|
@timestamp = nil
|
|
104
107
|
end
|
|
105
108
|
|
|
106
|
-
def older_message?
|
|
107
|
-
return false if @timestamp
|
|
109
|
+
def older_message?(message)
|
|
110
|
+
return false if @timestamp.nil?
|
|
111
|
+
|
|
108
112
|
RSMP::Clock.parse(message.attribute('aTs')) < @timestamp
|
|
109
113
|
end
|
|
110
114
|
|
|
111
115
|
# update from rsmp message
|
|
112
116
|
# component id, alarm code and specialization are not updated
|
|
113
|
-
def update_from_message
|
|
117
|
+
def update_from_message(message)
|
|
114
118
|
unless differ_from_message? message
|
|
115
|
-
raise RepeatedAlarmError
|
|
116
|
-
|
|
117
|
-
if older_message? message
|
|
118
|
-
raise TimestampError.new("timestamp is earlier than previous alarm #{message.m_id_short}")
|
|
119
|
+
raise RepeatedAlarmError,
|
|
120
|
+
"no changes from previous alarm #{message.m_id_short}"
|
|
119
121
|
end
|
|
122
|
+
raise TimestampError, "timestamp is earlier than previous alarm #{message.m_id_short}" if older_message? message
|
|
120
123
|
ensure
|
|
121
124
|
@timestamp = RSMP::Clock.parse message.attribute('aTs')
|
|
122
125
|
@acknowledged = message.attribute('ack') == 'True'
|
|
@@ -126,5 +129,41 @@ module RSMP
|
|
|
126
129
|
@priority = message.attribute('pri').to_i
|
|
127
130
|
@rvs = message.attribute('rvs')
|
|
128
131
|
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def timestamp_differs?(message)
|
|
136
|
+
RSMP::Clock.to_s(@timestamp) != message.attribute('aTs')
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def acknowledgment_differs?(message)
|
|
140
|
+
return false unless message.attribute('ack')
|
|
141
|
+
|
|
142
|
+
@acknowledged != (message.attribute('ack').downcase == 'acknowledged')
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def suspension_differs?(message)
|
|
146
|
+
return false unless message.attribute('sS')
|
|
147
|
+
|
|
148
|
+
@suspended != (message.attribute('sS').downcase == 'suspended')
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def activity_differs?(message)
|
|
152
|
+
return false unless message.attribute('aS')
|
|
153
|
+
|
|
154
|
+
@active != (message.attribute('aS').downcase == 'active')
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def category_differs?(message)
|
|
158
|
+
return false unless message.attribute('cat')
|
|
159
|
+
|
|
160
|
+
@category != message.attribute('cat')
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def priority_differs?(message)
|
|
164
|
+
return false unless message.attribute('pri')
|
|
165
|
+
|
|
166
|
+
@priority != message.attribute('pri').to_i
|
|
167
|
+
end
|
|
129
168
|
end
|
|
130
169
|
end
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
module RSMP
|
|
2
2
|
# RSMP component
|
|
3
3
|
class Component < ComponentBase
|
|
4
|
-
def initialize
|
|
4
|
+
def initialize(node:, id:, ntsoid: nil, xnid: nil, grouped: false)
|
|
5
5
|
super
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
def handle_command
|
|
9
|
-
raise UnknownCommand
|
|
8
|
+
def handle_command(command_code, _arg)
|
|
9
|
+
raise UnknownCommand, "Command #{command_code} not implemented by #{self.class}"
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def get_status
|
|
13
|
-
raise UnknownStatus
|
|
12
|
+
def get_status(status_code, status_name = nil, _options = {})
|
|
13
|
+
raise UnknownStatus, "Status #{status_code}/#{status_name} not implemented by #{self.class}"
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def acknowledge_alarm
|
|
16
|
+
def acknowledge_alarm(alarm_code)
|
|
17
17
|
alarm = get_alarm_state alarm_code
|
|
18
18
|
if alarm.acknowledge
|
|
19
19
|
log "Acknowledging alarm #{alarm_code}", level: :info
|
|
@@ -23,17 +23,17 @@ module RSMP
|
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def suspend_alarm
|
|
26
|
+
def suspend_alarm(alarm_code)
|
|
27
27
|
alarm = get_alarm_state alarm_code
|
|
28
28
|
if alarm.suspend
|
|
29
29
|
log "Suspending alarm #{alarm_code}", level: :info
|
|
30
30
|
@node.alarm_suspended_or_resumed alarm
|
|
31
31
|
else
|
|
32
32
|
log "Alarm #{alarm_code} already suspended", level: :info
|
|
33
|
-
end
|
|
33
|
+
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def resume_alarm
|
|
36
|
+
def resume_alarm(alarm_code)
|
|
37
37
|
alarm = get_alarm_state alarm_code
|
|
38
38
|
if alarm.resume
|
|
39
39
|
log "Resuming alarm #{alarm_code}", level: :info
|
|
@@ -43,22 +43,22 @@ module RSMP
|
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
def activate_alarm
|
|
46
|
+
def activate_alarm(alarm_code)
|
|
47
47
|
alarm = get_alarm_state alarm_code
|
|
48
48
|
return unless alarm.activate
|
|
49
|
+
|
|
49
50
|
log "Activating alarm #{alarm_code}", level: :info
|
|
50
51
|
@node.alarm_activated_or_deactivated alarm
|
|
51
52
|
end
|
|
52
53
|
|
|
53
|
-
def deactivate_alarm
|
|
54
|
+
def deactivate_alarm(alarm_code)
|
|
54
55
|
alarm = get_alarm_state alarm_code
|
|
55
56
|
return unless alarm.deactivate
|
|
57
|
+
|
|
56
58
|
log "Deactivating alarm #{alarm_code}", level: :info
|
|
57
59
|
@node.alarm_activated_or_deactivated alarm
|
|
58
60
|
end
|
|
59
61
|
|
|
60
|
-
def status_updates_sent
|
|
61
|
-
end
|
|
62
|
-
|
|
62
|
+
def status_updates_sent; end
|
|
63
63
|
end
|
|
64
|
-
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
# RSMP component base class.
|
|
3
|
+
|
|
4
|
+
class ComponentBase
|
|
5
|
+
include Inspect
|
|
6
|
+
|
|
7
|
+
attr_reader :c_id, :ntsoid, :xnid, :node, :alarms, :statuses,
|
|
8
|
+
:aggregated_status, :aggregated_status_bools, :grouped
|
|
9
|
+
|
|
10
|
+
AGGREGATED_STATUS_KEYS = %i[local_control
|
|
11
|
+
communication_distruption
|
|
12
|
+
high_priority_alarm
|
|
13
|
+
medium_priority_alarm
|
|
14
|
+
low_priority_alarm
|
|
15
|
+
normal
|
|
16
|
+
rest
|
|
17
|
+
not_connected].freeze
|
|
18
|
+
|
|
19
|
+
def initialize(node:, id:, ntsoid: nil, xnid: nil, grouped: false)
|
|
20
|
+
if grouped == false && (ntsoid || xnid)
|
|
21
|
+
raise RSMP::ConfigurationError,
|
|
22
|
+
'ntsoid and xnid are only allowed for grouped objects'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@c_id = id
|
|
26
|
+
@ntsoid = ntsoid
|
|
27
|
+
@xnid = xnid
|
|
28
|
+
@node = node
|
|
29
|
+
@grouped = grouped
|
|
30
|
+
clear_aggregated_status
|
|
31
|
+
@alarms = {}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def now
|
|
35
|
+
node.now
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def clear_alarm_timestamps
|
|
39
|
+
@alarms.each_value(&:clear_timestamp)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def get_alarm_state(alarm_code)
|
|
43
|
+
@alarms[alarm_code] ||= RSMP::AlarmState.new component: self, code: alarm_code
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def clear_aggregated_status
|
|
47
|
+
@aggregated_status = []
|
|
48
|
+
@aggregated_status_bools = Array.new(8, false)
|
|
49
|
+
@aggregated_status_bools[5] = true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def log(str, options)
|
|
53
|
+
default = { component: c_id }
|
|
54
|
+
@node.log str, default.merge(options)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def set_aggregated_status(status, options = {})
|
|
58
|
+
status = [status] if status.is_a? Symbol
|
|
59
|
+
raise InvalidArgument unless status.is_a? Array
|
|
60
|
+
|
|
61
|
+
input = status & AGGREGATED_STATUS_KEYS
|
|
62
|
+
return unless input != @aggregated_status
|
|
63
|
+
|
|
64
|
+
AGGREGATED_STATUS_KEYS.each_with_index do |key, index|
|
|
65
|
+
@aggregated_status_bools[index] = status.include?(key)
|
|
66
|
+
end
|
|
67
|
+
aggregated_status_changed options
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def aggregated_status_bools=(status)
|
|
71
|
+
raise InvalidArgument unless status.is_a? Array
|
|
72
|
+
raise InvalidArgument unless status.size == 8
|
|
73
|
+
|
|
74
|
+
return unless status != @aggregated_status_bools
|
|
75
|
+
|
|
76
|
+
@aggregated_status = []
|
|
77
|
+
AGGREGATED_STATUS_KEYS.each_with_index do |key, index|
|
|
78
|
+
on = status[index] == true
|
|
79
|
+
@aggregated_status_bools[index] = on
|
|
80
|
+
@aggregated_status << key if on
|
|
81
|
+
end
|
|
82
|
+
aggregated_status_changed
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def aggregated_status_changed(options = {})
|
|
86
|
+
@node.aggregated_status_changed self, options
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
# A proxy to a remote RSMP component.
|
|
3
|
+
class ComponentProxy < ComponentBase
|
|
4
|
+
def initialize(node:, id:, ntsoid: nil, xnid: nil, grouped: false)
|
|
5
|
+
super
|
|
6
|
+
@alarms = {}
|
|
7
|
+
@statuses = {}
|
|
8
|
+
@allow_repeat_updates = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# allow the next status update to be a repeat value
|
|
12
|
+
def allow_repeat_updates(subscribe_list)
|
|
13
|
+
subscribe_list.each do |item|
|
|
14
|
+
sci = item['sCI']
|
|
15
|
+
n = item['n']
|
|
16
|
+
@allow_repeat_updates[sci] ||= Set.new # Set is like an array, but with no duplicates
|
|
17
|
+
@allow_repeat_updates[sci] << n
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Check that were not receiving repeated update values.
|
|
22
|
+
# The check is not performed for item with an update interval.
|
|
23
|
+
def check_repeat_values(message, subscription_list)
|
|
24
|
+
message.attribute('sS').each do |item|
|
|
25
|
+
check_status_item_for_repeats(item, subscription_list)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def check_status_item_for_repeats(item, subscription_list)
|
|
30
|
+
status_code = item['sCI']
|
|
31
|
+
status_name = item['n']
|
|
32
|
+
return if update_rate_set?(subscription_list, status_code, status_name)
|
|
33
|
+
return unless should_check_repeats?(subscription_list, status_code, status_name)
|
|
34
|
+
|
|
35
|
+
new_values = { 's' => item['s'], 'q' => item['q'] }
|
|
36
|
+
old_values = @statuses.dig(status_code, status_name)
|
|
37
|
+
raise RSMP::RepeatedStatusError, "no change for #{status_code} '#{status_name}'" if new_values == old_values
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def update_rate_set?(subscription_list, status_code, status_name)
|
|
41
|
+
urt = subscription_list.dig(c_id, status_code, status_name, 'uRt')
|
|
42
|
+
urt.to_i.positive?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def should_check_repeats?(subscription_list, status_code, status_name)
|
|
46
|
+
soc = subscription_list.dig(c_id, status_code, status_name, 'sOc')
|
|
47
|
+
return false if soc == false
|
|
48
|
+
return false if @allow_repeat_updates[status_code]&.include?(status_name)
|
|
49
|
+
|
|
50
|
+
true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Store the latest status update values
|
|
54
|
+
def store_status(message)
|
|
55
|
+
message.attribute('sS').each do |item|
|
|
56
|
+
sci = item['sCI']
|
|
57
|
+
n = item['n']
|
|
58
|
+
s = item['s']
|
|
59
|
+
q = item['q']
|
|
60
|
+
@statuses[sci] ||= {}
|
|
61
|
+
@statuses[sci][n] = { 's' => s, 'q' => q }
|
|
62
|
+
|
|
63
|
+
# once a value is received, don't allow the value to be a repeat
|
|
64
|
+
@allow_repeat_updates[sci]&.delete(n)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# handle incoming alarm
|
|
69
|
+
def handle_alarm(message)
|
|
70
|
+
code = message.attribute('aCId')
|
|
71
|
+
alarm = get_alarm_state code
|
|
72
|
+
alarm.update_from_message message
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Things shared between sites and site proxies
|
|
2
|
+
|
|
3
|
+
module RSMP
|
|
4
|
+
module Components
|
|
5
|
+
attr_reader :components, :main
|
|
6
|
+
|
|
7
|
+
def initialize_components
|
|
8
|
+
@components = {}
|
|
9
|
+
@main = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def aggregated_status_changed(component, options = {}); end
|
|
13
|
+
|
|
14
|
+
def setup_components(settings)
|
|
15
|
+
return unless settings
|
|
16
|
+
|
|
17
|
+
check_main_component settings
|
|
18
|
+
settings.each_pair do |type, components_by_type|
|
|
19
|
+
next unless components_by_type
|
|
20
|
+
|
|
21
|
+
components_by_type.each_pair do |id, component_settings|
|
|
22
|
+
component_settings ||= {}
|
|
23
|
+
@components[id] = build_component(id: id, type: type, settings: component_settings)
|
|
24
|
+
@main = @components[id] if type == 'main'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def check_main_component(settings)
|
|
30
|
+
raise ConfigurationError, 'main component must be defined' unless settings['main'] && settings['main'].size >= 1
|
|
31
|
+
return unless settings['main'].size > 1
|
|
32
|
+
|
|
33
|
+
raise ConfigurationError, "only one main component can be defined, found #{settings['main'].keys.join(', ')}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def add_component(component)
|
|
37
|
+
@components[component.c_id] = component
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def infer_component_type(component_id)
|
|
41
|
+
raise UnknownComponent, "Component #{component_id} mising and cannot infer type"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def find_component(component_id, build: true)
|
|
45
|
+
component = @components[component_id]
|
|
46
|
+
return component if component
|
|
47
|
+
|
|
48
|
+
return unless build
|
|
49
|
+
|
|
50
|
+
inferred_type = infer_component_type component_id
|
|
51
|
+
component = inferred_type.new node: self, id: component_id
|
|
52
|
+
@components[component_id] = component
|
|
53
|
+
class_name = component.class.name.split('::').last
|
|
54
|
+
class_name << ' component' unless %w[Component ComponentProxy].include?(class_name)
|
|
55
|
+
log "Added component #{component_id} with the inferred type #{class_name}", level: :debug
|
|
56
|
+
component
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def clear_alarm_timestamps
|
|
60
|
+
@components.each_value(&:clear_alarm_timestamps)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|