rsmp 0.35.2 → 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/copilot-instructions.md +33 -0
- data/.github/workflows/copilot-setup-steps.yml +35 -0
- data/.github/workflows/rspec.yaml +2 -1
- data/.github/workflows/rubocop.yaml +17 -0
- data/.gitignore +7 -7
- data/.rubocop.yml +80 -0
- data/Gemfile +13 -1
- data/Gemfile.lock +73 -35
- data/Rakefile +3 -3
- data/bin/console +2 -4
- data/documentation/tasks.md +4 -4
- 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/node/protocol.rb +37 -0
- 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} +29 -19
- 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 +39 -1006
- data/lib/rsmp/tlc/traffic_controller_site.rb +58 -57
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +86 -49
- data/rsmp.gemspec +24 -30
- metadata +87 -130
- 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 -695
- data/lib/rsmp/site.rb +0 -188
- data/lib/rsmp/site_proxy.rb +0 -389
- data/lib/rsmp/supervisor.rb +0 -287
- data/lib/rsmp/supervisor_proxy.rb +0 -516
- data/lib/rsmp/tlc/inputs.rb +0 -134
|
@@ -35,91 +35,103 @@ module RSMP
|
|
|
35
35
|
attr_reader :matchers
|
|
36
36
|
|
|
37
37
|
# Initialize with a list of wanted statuses
|
|
38
|
-
def initialize
|
|
39
|
-
raise ArgumentError
|
|
40
|
-
|
|
38
|
+
def initialize(proxy, want, options = {})
|
|
39
|
+
raise ArgumentError, 'num option cannot be used' if options[:num]
|
|
40
|
+
|
|
41
|
+
super(proxy, options)
|
|
41
42
|
@matchers = want.map { |item| build_matcher item }
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
# Build a matcher object.
|
|
45
46
|
# Sub-classes should override to use their own matcher classes.
|
|
46
|
-
def build_matcher
|
|
47
|
+
def build_matcher(want)
|
|
47
48
|
Matcher.new want
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
# Get a results
|
|
51
|
-
def matcher_result
|
|
52
|
-
matcher = @matchers.find { |q| q.want == want}
|
|
52
|
+
def matcher_result(want)
|
|
53
|
+
matcher = @matchers.find { |q| q.want == want }
|
|
53
54
|
raise unless matcher
|
|
55
|
+
|
|
54
56
|
matcher.got
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
# Get an array of the last item received for each matcher
|
|
58
60
|
def reached
|
|
59
|
-
@matchers.map
|
|
61
|
+
@matchers.map(&:got).compact
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
# Get messages from results
|
|
63
65
|
def messages
|
|
64
|
-
@matchers.map
|
|
66
|
+
@matchers.map(&:message).uniq.compact
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
# Return progress as completes matchers vs. total number of matchers
|
|
68
70
|
def progress
|
|
69
71
|
need = @matchers.size
|
|
70
|
-
reached =
|
|
72
|
+
reached = @matchers.count(&:done?)
|
|
71
73
|
{ need: need, reached: reached }
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
# Are there matchers left to type_match?
|
|
75
77
|
def done?
|
|
76
|
-
@matchers.all?
|
|
78
|
+
@matchers.all?(&:done?)
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
# Get a simplified hash of matchers, with values set to either true or false,
|
|
80
82
|
# indicating which matchers have been matched.
|
|
81
83
|
def matcher_status
|
|
82
|
-
@matchers.
|
|
84
|
+
@matchers.to_h { |matcher| [matcher.want, matcher.done?] }
|
|
83
85
|
end
|
|
84
86
|
|
|
85
87
|
# Get a simply array of bools, showing which matchers have been matched.
|
|
86
88
|
def summary
|
|
87
|
-
@matchers.map
|
|
89
|
+
@matchers.map(&:done?)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def log_match_result(message, matched, matcher, item)
|
|
93
|
+
type = { true => 'match', false => 'mismatch' }[matched]
|
|
94
|
+
@distributor.log "#{@title.capitalize} #{message.m_id_short} collect #{type} #{matcher.want}, item #{item}",
|
|
95
|
+
level: :debug
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def handle_match_result(matched, matcher, message, item)
|
|
99
|
+
return if matched.nil?
|
|
100
|
+
|
|
101
|
+
log_match_result(message, matched, matcher, item)
|
|
102
|
+
if matched == true
|
|
103
|
+
matcher.keep message, item
|
|
104
|
+
elsif matched == false
|
|
105
|
+
matcher.forget
|
|
106
|
+
end
|
|
88
107
|
end
|
|
89
108
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def perform_match message
|
|
93
|
-
return false if super(message) == false
|
|
109
|
+
def perform_match(message)
|
|
110
|
+
return false if super == false
|
|
94
111
|
return unless collecting?
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
elsif matched == false
|
|
105
|
-
matcher.forget
|
|
106
|
-
end
|
|
107
|
-
end
|
|
112
|
+
|
|
113
|
+
@matchers.each do |matcher|
|
|
114
|
+
break unless collecting?
|
|
115
|
+
|
|
116
|
+
get_items(message).each do |item|
|
|
117
|
+
matched = matcher.perform_match(item, message, @block)
|
|
118
|
+
break unless collecting?
|
|
119
|
+
|
|
120
|
+
handle_match_result(matched, matcher, message, item)
|
|
108
121
|
end
|
|
109
122
|
end
|
|
110
123
|
end
|
|
111
124
|
|
|
112
125
|
# don't collect anything. Matcher will collect them instead
|
|
113
|
-
def keep
|
|
114
|
-
end
|
|
126
|
+
def keep(message); end
|
|
115
127
|
|
|
116
128
|
def describe
|
|
117
|
-
@matchers.map {|q| q.want.to_s }
|
|
129
|
+
@matchers.map { |q| q.want.to_s }
|
|
118
130
|
end
|
|
119
131
|
|
|
120
132
|
# return a string that describes the attributes that we're looking for
|
|
121
133
|
def describe_matcher
|
|
122
|
-
"#{super} matching #{matcher_want_hash
|
|
134
|
+
"#{super} matching #{matcher_want_hash}"
|
|
123
135
|
end
|
|
124
136
|
|
|
125
137
|
# return a hash that describe the status of all matchers
|
|
@@ -128,33 +140,45 @@ module RSMP
|
|
|
128
140
|
@matchers.each do |matcher|
|
|
129
141
|
want = matcher.want
|
|
130
142
|
if want['cCI']
|
|
131
|
-
|
|
132
|
-
h[cCI] ||= {}
|
|
133
|
-
cO = h['cO']
|
|
134
|
-
n = h['n']
|
|
135
|
-
v = h['v']
|
|
136
|
-
h[cCI][cO] ||= {}
|
|
137
|
-
h[cCI][cO][n] = v
|
|
143
|
+
process_command_matcher(h, matcher, want)
|
|
138
144
|
elsif want['sCI']
|
|
139
|
-
|
|
140
|
-
h[sCI] ||= {}
|
|
141
|
-
n = want['n']
|
|
142
|
-
s = want['s']
|
|
143
|
-
if matcher.got && matcher.got['s']
|
|
144
|
-
h[sCI][n] = { {s=>matcher.got['s']} => matcher.done? }
|
|
145
|
-
else
|
|
146
|
-
h[sCI][n] = { s=>nil }
|
|
147
|
-
end
|
|
145
|
+
process_status_matcher(h, matcher, want)
|
|
148
146
|
end
|
|
149
147
|
end
|
|
150
148
|
h
|
|
151
149
|
end
|
|
152
150
|
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def process_command_matcher(hash, _matcher, want)
|
|
154
|
+
cci = want['cCI']
|
|
155
|
+
hash[cci] ||= {}
|
|
156
|
+
co = want['cO']
|
|
157
|
+
n = want['n']
|
|
158
|
+
v = want['v']
|
|
159
|
+
hash[cci][co] ||= {}
|
|
160
|
+
hash[cci][co][n] = v
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def process_status_matcher(hash, matcher, want)
|
|
164
|
+
sci = want['sCI']
|
|
165
|
+
hash[sci] ||= {}
|
|
166
|
+
n = want['n']
|
|
167
|
+
s = want['s']
|
|
168
|
+
hash[sci][n] = if matcher.got && matcher.got['s']
|
|
169
|
+
{ { s => matcher.got['s'] } => matcher.done? }
|
|
170
|
+
else
|
|
171
|
+
{ s => nil }
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
public
|
|
176
|
+
|
|
153
177
|
# return a string that describe how many many messages have been collected
|
|
154
178
|
def describe_progress
|
|
155
179
|
num_matchers = @matchers.size
|
|
156
|
-
num_matched = @matchers.count
|
|
157
|
-
".. Matched #{num_matched}/#{num_matchers} with #{progress_hash
|
|
180
|
+
num_matched = @matchers.count(&:done?)
|
|
181
|
+
".. Matched #{num_matched}/#{num_matchers} with #{progress_hash}"
|
|
158
182
|
end
|
|
159
183
|
|
|
160
184
|
def matcher_want_hash
|
|
@@ -162,44 +186,59 @@ module RSMP
|
|
|
162
186
|
@matchers.each do |matcher|
|
|
163
187
|
item = matcher.want
|
|
164
188
|
if item['cCI']
|
|
165
|
-
|
|
166
|
-
h[cCI] ||= {}
|
|
167
|
-
cO = item['cO']
|
|
168
|
-
h[cCI][cO] ||= {}
|
|
169
|
-
n = item['n']
|
|
170
|
-
v = item['v']
|
|
171
|
-
h[cCI][cO][n] = v || :any
|
|
189
|
+
add_command_want_to_hash(h, item)
|
|
172
190
|
elsif item['sCI']
|
|
173
|
-
|
|
174
|
-
h[sCI] ||= {}
|
|
175
|
-
n = item['n']
|
|
176
|
-
s = item['s']
|
|
177
|
-
h[sCI][n] = s || :any
|
|
191
|
+
add_status_want_to_hash(h, item)
|
|
178
192
|
end
|
|
179
193
|
end
|
|
180
194
|
h
|
|
181
195
|
end
|
|
182
196
|
|
|
183
|
-
|
|
197
|
+
def add_command_want_to_hash(hash, item)
|
|
198
|
+
cci = item['cCI']
|
|
199
|
+
hash[cci] ||= {}
|
|
200
|
+
co = item['cO']
|
|
201
|
+
hash[cci][co] ||= {}
|
|
202
|
+
n = item['n']
|
|
203
|
+
v = item['v']
|
|
204
|
+
hash[cci][co][n] = v || :any
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def add_status_want_to_hash(hash, item)
|
|
208
|
+
sci = item['sCI']
|
|
209
|
+
hash[sci] ||= {}
|
|
210
|
+
n = item['n']
|
|
211
|
+
s = item['s']
|
|
212
|
+
hash[sci][n] = s || :any
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def add_command_result(hash, want, got)
|
|
216
|
+
cci = want['cCI']
|
|
217
|
+
hash[cci] ||= {}
|
|
218
|
+
co = want['cO']
|
|
219
|
+
hash[cci][co] ||= {}
|
|
220
|
+
n = want['n']
|
|
221
|
+
v = got ? got['v'] : nil
|
|
222
|
+
hash[cci][co][n] = v
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def add_status_result(hash, want, got)
|
|
226
|
+
sci = want['sCI']
|
|
227
|
+
hash[sci] ||= {}
|
|
228
|
+
n = want['n']
|
|
229
|
+
s = got ? got['s'] : nil
|
|
230
|
+
hash[sci][n] = s
|
|
231
|
+
end
|
|
232
|
+
|
|
184
233
|
def matcher_got_hash
|
|
185
234
|
h = {}
|
|
186
235
|
@matchers.each do |matcher|
|
|
187
236
|
want = matcher.want
|
|
188
237
|
got = matcher.got
|
|
189
238
|
if want['cCI']
|
|
190
|
-
|
|
191
|
-
h[cCI] ||= {}
|
|
192
|
-
cO = want['cO']
|
|
193
|
-
h[cCI][cO] ||= {}
|
|
194
|
-
n = want['n']
|
|
195
|
-
v = got ? got['v'] : nil
|
|
196
|
-
h[cCI][cO][n] = v
|
|
239
|
+
add_command_result(h, want, got)
|
|
197
240
|
elsif want['sCI']
|
|
198
|
-
|
|
199
|
-
h[sCI] ||= {}
|
|
200
|
-
n = want['n']
|
|
201
|
-
s = got ? got['s'] : nil
|
|
202
|
-
h[sCI][n] = s
|
|
241
|
+
add_status_result(h, want, got)
|
|
203
242
|
end
|
|
204
243
|
end
|
|
205
244
|
h
|
|
@@ -207,7 +246,7 @@ module RSMP
|
|
|
207
246
|
|
|
208
247
|
# log when we end collecting
|
|
209
248
|
def log_complete
|
|
210
|
-
@distributor.log "#{identifier}: Completed with #{matcher_got_hash
|
|
249
|
+
@distributor.log "#{identifier}: Completed with #{matcher_got_hash}", level: :collect
|
|
211
250
|
end
|
|
212
251
|
end
|
|
213
252
|
end
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
module RSMP
|
|
2
2
|
# Base class for waiting for status updates or responses
|
|
3
3
|
class StatusCollector < StateCollector
|
|
4
|
-
def initialize
|
|
4
|
+
def initialize(proxy, want, options = {})
|
|
5
5
|
type = []
|
|
6
6
|
type << 'StatusUpdate' unless options[:updates] == false
|
|
7
7
|
type << 'StatusResponse' unless options[:reponses] == false
|
|
8
8
|
|
|
9
|
-
super
|
|
9
|
+
super(proxy, want, options.merge(
|
|
10
10
|
title: 'status response',
|
|
11
11
|
filter: RSMP::Filter.new(ingoing: true, outgoing: false, type: type)
|
|
12
|
-
)
|
|
12
|
+
))
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def build_matcher
|
|
15
|
+
def build_matcher(want)
|
|
16
16
|
RSMP::StatusMatcher.new want
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# Get items, in our case status values
|
|
20
|
-
def get_items
|
|
20
|
+
def get_items(message)
|
|
21
21
|
message.attributes['sS'] || []
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
|
-
end
|
|
24
|
+
end
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
module RSMP
|
|
2
|
-
# Match a specific status response or update
|
|
3
2
|
class StatusMatcher < Matcher
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return
|
|
7
|
-
return
|
|
8
|
-
|
|
3
|
+
def match_code?(item)
|
|
4
|
+
return false if @want['sCI'] && @want['sCI'] != item['sCI']
|
|
5
|
+
return false if @want['cO'] && @want['cO'] != item['cO']
|
|
6
|
+
return false if @want['n'] && @want['n'] != item['n']
|
|
7
|
+
|
|
8
|
+
true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def match_value?(item)
|
|
9
12
|
return false if @want['q'] && @want['q'] != item['q']
|
|
13
|
+
|
|
10
14
|
if @want['s'].is_a? Regexp
|
|
11
15
|
return false if item['s'] !~ @want['s']
|
|
12
16
|
elsif @want['s']
|
|
@@ -14,5 +18,11 @@ module RSMP
|
|
|
14
18
|
end
|
|
15
19
|
true
|
|
16
20
|
end
|
|
21
|
+
|
|
22
|
+
def match?(item)
|
|
23
|
+
return nil unless match_code?(item)
|
|
24
|
+
|
|
25
|
+
match_value?(item)
|
|
26
|
+
end
|
|
17
27
|
end
|
|
18
|
-
end
|
|
28
|
+
end
|
|
@@ -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
|