rsmp 0.11.5 → 0.12.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/Gemfile.lock +2 -2
- data/config/tlc.yaml +4 -1
- data/lib/rsmp/alarm_state.rb +25 -0
- data/lib/rsmp/collect/collector.rb +1 -0
- data/lib/rsmp/collect/state_collector.rb +3 -3
- data/lib/rsmp/component.rb +24 -159
- data/lib/rsmp/component_base.rb +69 -0
- data/lib/rsmp/component_proxy.rb +67 -0
- data/lib/rsmp/components.rb +3 -3
- data/lib/rsmp/message.rb +5 -5
- data/lib/rsmp/proxy.rb +1 -1
- data/lib/rsmp/site.rb +22 -2
- data/lib/rsmp/site_proxy.rb +55 -16
- data/lib/rsmp/supervisor_proxy.rb +62 -34
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 957caaa3191011a85a0284ef530434184e15620b7762e4971a8547f45a955ec3
|
4
|
+
data.tar.gz: 8905eb13b36d26d6c50fe0e4466f40e2f742d18384e93bc2b7e67d2e77975441
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 100b1c357a27939ec7142ac0f63e9589ee36a044bfc54fcaea30303c2fb1eba277a3d0e1927f578fabe99717f9ffeaa83f5eceeb65dd7492d260458ab4d61689
|
7
|
+
data.tar.gz: 729b6355adef1914f9621c10c69bd88318c688b40ef2881125a0783ed83533a4f34bc1e1192e338d073f44af4e7ae6c416732693622fa7ef478a48b63c31ba7d
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rsmp (0.
|
4
|
+
rsmp (0.12.0)
|
5
5
|
async (~> 1.29.1)
|
6
6
|
async-io (~> 1.32.2)
|
7
7
|
colorize (~> 0.8.1)
|
@@ -27,7 +27,7 @@ GEM
|
|
27
27
|
builder (3.2.4)
|
28
28
|
childprocess (4.1.0)
|
29
29
|
colorize (0.8.1)
|
30
|
-
console (1.
|
30
|
+
console (1.15.0)
|
31
31
|
fiber-local
|
32
32
|
contracts (0.17)
|
33
33
|
cucumber (7.1.0)
|
data/config/tlc.yaml
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
module RSMP
|
2
|
+
# class that tracks the state of an alarm
|
3
|
+
class AlarmState
|
4
|
+
attr_reader :component_id, :code, :acknowledged, :suspended, :active, :timestamp, :category, :priority
|
5
|
+
|
6
|
+
def initialize component_id:, code:
|
7
|
+
@component_id = component_id
|
8
|
+
@code = code
|
9
|
+
@acknowledged = false
|
10
|
+
@suspended = false
|
11
|
+
@active = false
|
12
|
+
@timestamp = nil
|
13
|
+
@category = nil
|
14
|
+
@priority = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def suspend
|
18
|
+
@suspended = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def resume
|
22
|
+
@suspended = false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -159,6 +159,7 @@ module RSMP
|
|
159
159
|
if message.attribute('oMId') == @options[:m_id]
|
160
160
|
m_id_short = RSMP::Message.shorten_m_id @options[:m_id], 8
|
161
161
|
cancel RSMP::MessageRejected.new("#{@title} #{m_id_short} was rejected with '#{message.attribute('rea')}'")
|
162
|
+
@notifier.log "#{identifier}: cancelled due to a NotAck", level: :debug
|
162
163
|
true
|
163
164
|
end
|
164
165
|
end
|
@@ -186,19 +186,19 @@ module RSMP
|
|
186
186
|
@queries.each do |query|
|
187
187
|
want = query.want
|
188
188
|
got = query.got
|
189
|
-
if
|
189
|
+
if want['cCI']
|
190
190
|
cCI = want['cCI']
|
191
191
|
h[cCI] ||= {}
|
192
192
|
cO = want['cO']
|
193
193
|
h[cCI][cO] ||= {}
|
194
194
|
n = want['n']
|
195
|
-
v = got['v']
|
195
|
+
v = got ? got['v'] : nil
|
196
196
|
h[cCI][cO][n] = v
|
197
197
|
elsif want['sCI']
|
198
198
|
sCI = want['sCI']
|
199
199
|
h[sCI] ||= {}
|
200
200
|
n = want['n']
|
201
|
-
s = got['s']
|
201
|
+
s = got ? got['s'] : nil
|
202
202
|
h[sCI][n] = s
|
203
203
|
end
|
204
204
|
end
|
data/lib/rsmp/component.rb
CHANGED
@@ -1,72 +1,10 @@
|
|
1
1
|
module RSMP
|
2
2
|
|
3
|
-
#
|
4
|
-
# Currently this class is used by both SiteProxy and SupervisorProxy, and can
|
5
|
-
# therefore represent either a local or remote (proxy) component.
|
6
|
-
|
7
|
-
class Component
|
8
|
-
include Inspect
|
9
|
-
|
10
|
-
attr_reader :c_id, :node, :alarms, :statuses, :aggregated_status, :aggregated_status_bools, :grouped
|
11
|
-
|
12
|
-
AGGREGATED_STATUS_KEYS = [ :local_control,
|
13
|
-
:communication_distruption,
|
14
|
-
:high_priority_alarm,
|
15
|
-
:medium_priority_alarm,
|
16
|
-
:low_priority_alarm,
|
17
|
-
:normal,
|
18
|
-
:rest,
|
19
|
-
:not_connected ]
|
3
|
+
# RSMP component
|
20
4
|
|
5
|
+
class Component < ComponentBase
|
21
6
|
def initialize node:, id:, grouped: false
|
22
|
-
|
23
|
-
@node = node
|
24
|
-
@grouped = grouped
|
25
|
-
@alarms = {}
|
26
|
-
@statuses = {}
|
27
|
-
@subscribes = {}
|
28
|
-
clear_aggregated_status
|
29
|
-
end
|
30
|
-
|
31
|
-
def clear_aggregated_status
|
32
|
-
@aggregated_status = []
|
33
|
-
@aggregated_status_bools = Array.new(8,false)
|
34
|
-
@aggregated_status_bools[5] = true
|
35
|
-
end
|
36
|
-
|
37
|
-
def set_aggregated_status status, options={}
|
38
|
-
status = [status] if status.is_a? Symbol
|
39
|
-
raise InvalidArgument unless status.is_a? Array
|
40
|
-
input = status & AGGREGATED_STATUS_KEYS
|
41
|
-
if input != @aggregated_status
|
42
|
-
AGGREGATED_STATUS_KEYS.each_with_index do |key,index|
|
43
|
-
@aggregated_status_bools[index] = status.include?(key)
|
44
|
-
end
|
45
|
-
aggregated_status_changed options
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def set_aggregated_status_bools status
|
50
|
-
raise InvalidArgument unless status.is_a? Array
|
51
|
-
raise InvalidArgument unless status.size == 8
|
52
|
-
if status != @aggregated_status_bools
|
53
|
-
@aggregated_status = []
|
54
|
-
AGGREGATED_STATUS_KEYS.each_with_index do |key,index|
|
55
|
-
on = status[index] == true
|
56
|
-
@aggregated_status_bools[index] = on
|
57
|
-
@aggregated_status << key if on
|
58
|
-
end
|
59
|
-
aggregated_status_changed
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def aggregated_status_changed options={}
|
64
|
-
@node.aggregated_status_changed self, options
|
65
|
-
end
|
66
|
-
|
67
|
-
def log str, options
|
68
|
-
default = { component: c_id}
|
69
|
-
@node.log str, default.merge(options)
|
7
|
+
super
|
70
8
|
end
|
71
9
|
|
72
10
|
def handle_command command_code, arg
|
@@ -77,108 +15,35 @@ module RSMP
|
|
77
15
|
raise UnknownStatus.new "Status #{status_code}/#{status_name} not implemented by #{self.class}"
|
78
16
|
end
|
79
17
|
|
80
|
-
|
81
|
-
|
82
|
-
code = message.attribute('aCId')
|
83
|
-
previous = @alarms[code]
|
84
|
-
if previous
|
85
|
-
unless message.differ?(previous)
|
86
|
-
raise RepeatedAlarmError.new("no changes from previous alarm #{previous.m_id_short}")
|
87
|
-
end
|
88
|
-
if Time.parse(message.attribute('aTs')) < Time.parse(previous.attribute('aTs'))
|
89
|
-
raise TimestampError.new("timestamp is earlier than previous alarm #{previous.m_id_short}")
|
90
|
-
end
|
91
|
-
end
|
92
|
-
ensure
|
93
|
-
@alarms[code] = message
|
94
|
-
end
|
95
|
-
|
96
|
-
# set alarm
|
97
|
-
def send_alarm code:, status:
|
98
|
-
# TODO
|
99
|
-
# we need to manage the state of alarms internally (an ALarm class probably),
|
100
|
-
# and handle request from the supervisor to suspend and resume alarms etc.
|
101
|
-
# when this state changes, we then send an alarm message
|
102
|
-
alarm = Alarm.new(
|
103
|
-
'cId' => c_id,
|
104
|
-
'aTs' => @node.clock.to_s,
|
105
|
-
'aCId' => code,
|
106
|
-
'aSp' => 'Issue',
|
107
|
-
'ack' => 'Acknowledged',
|
108
|
-
'sS' => 'notSuspended',
|
109
|
-
'aS' => status,
|
110
|
-
'cat' => 'D',
|
111
|
-
'pri' => '2',
|
112
|
-
'rvs' => []
|
113
|
-
)
|
114
|
-
@node.alarm_changed self, alarm
|
115
|
-
end
|
116
|
-
|
117
|
-
# Handle an incoming status respone, by storing the values
|
118
|
-
def handle_status_response message
|
119
|
-
store_status message, check_repeated: false
|
18
|
+
def get_alarm_state alarm_code
|
19
|
+
alarm = @alarms[alarm_code] ||= RSMP::AlarmState.new component_id: c_id, code: alarm_code
|
120
20
|
end
|
121
21
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
# After subscribing, an update status us send regarless of whether values changes,
|
131
|
-
# and we store that.
|
132
|
-
def handle_status_subscribe status_list
|
133
|
-
status_list.each do |item|
|
134
|
-
sCI, n, uRt = item['sCI'], item['n'], item['uRt']
|
135
|
-
|
136
|
-
# record the update rate, so we can check for repeated status values if rate is zero
|
137
|
-
@subscribes[sCI] ||= {}
|
138
|
-
@subscribes[sCI][n] = {'uRt'=>uRt}
|
139
|
-
|
140
|
-
# record that we expect an upeate, even though the value might not change
|
141
|
-
@statuses[sCI] ||= {}
|
142
|
-
@statuses[sCI][n] ||= {}
|
143
|
-
@statuses[sCI][n][:initial] = true
|
22
|
+
def suspend_alarm alarm_code
|
23
|
+
alarm_state = get_alarm_state alarm_code
|
24
|
+
if alarm.suspended == false
|
25
|
+
log "Suspending alarm #{alarm_code}", level: :info
|
26
|
+
alarm.suspend
|
27
|
+
@node.alarm_suspended_or_resumed alarm
|
28
|
+
else
|
29
|
+
log "Alarm #{alarm_code} already suspended", level: :info
|
144
30
|
end
|
145
31
|
end
|
146
32
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
if @subscribes[sCI].empty?
|
156
|
-
@subscribes.delete sCI
|
157
|
-
end
|
158
|
-
|
159
|
-
# remove any mark that would allow the next update to be a repeat
|
160
|
-
item = @statuses.dig sCI, n
|
161
|
-
item.delete(:initial) if item
|
33
|
+
def resume_alarm alarm_code
|
34
|
+
alarm_state = get_alarm_state alarm_code
|
35
|
+
if alarm.suspended
|
36
|
+
log "Resuming alarm #{alarm_code}", level: :info
|
37
|
+
alarm.resume
|
38
|
+
@node.alarm_suspended_or_resumed alarm
|
39
|
+
else
|
40
|
+
log "Alarm #{alarm_code} not suspended", level: :info
|
162
41
|
end
|
163
42
|
end
|
164
43
|
|
165
|
-
#
|
166
|
-
|
167
|
-
|
168
|
-
def store_status message, check_repeated:
|
169
|
-
message.attribute('sS').each do |item|
|
170
|
-
sCI, n, s, q = item['sCI'], item['n'], item['s'], item['q']
|
171
|
-
uRt = @subscribes.dig(sCI,n,'uRt')
|
172
|
-
new_values = {'s'=>s,'q'=>q}
|
173
|
-
old_values = @statuses.dig(sCI,n)
|
174
|
-
if check_repeated && uRt.to_i == 0
|
175
|
-
if new_values == old_values
|
176
|
-
raise RSMP::RepeatedStatusError.new "no change for #{sCI} '#{n}'"
|
177
|
-
end
|
178
|
-
end
|
179
|
-
@statuses[sCI] ||= {}
|
180
|
-
@statuses[sCI][n] = new_values
|
181
|
-
end
|
44
|
+
# send alarm
|
45
|
+
def send_alarm code:, status:
|
46
|
+
@node.alarm_changed self, alarm
|
182
47
|
end
|
183
48
|
|
184
49
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module RSMP
|
2
|
+
|
3
|
+
# RSMP component base class.
|
4
|
+
|
5
|
+
class ComponentBase
|
6
|
+
include Inspect
|
7
|
+
|
8
|
+
attr_reader :c_id, :node, :alarms, :statuses, :aggregated_status, :aggregated_status_bools, :grouped
|
9
|
+
|
10
|
+
AGGREGATED_STATUS_KEYS = [ :local_control,
|
11
|
+
:communication_distruption,
|
12
|
+
:high_priority_alarm,
|
13
|
+
:medium_priority_alarm,
|
14
|
+
:low_priority_alarm,
|
15
|
+
:normal,
|
16
|
+
:rest,
|
17
|
+
:not_connected ]
|
18
|
+
|
19
|
+
def initialize node:, id:, grouped: false
|
20
|
+
@c_id = id
|
21
|
+
@node = node
|
22
|
+
@grouped = grouped
|
23
|
+
@alarms = {}
|
24
|
+
clear_aggregated_status
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear_aggregated_status
|
28
|
+
@aggregated_status = []
|
29
|
+
@aggregated_status_bools = Array.new(8,false)
|
30
|
+
@aggregated_status_bools[5] = true
|
31
|
+
end
|
32
|
+
|
33
|
+
def log str, options
|
34
|
+
default = { component: c_id}
|
35
|
+
@node.log str, default.merge(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_aggregated_status status, options={}
|
39
|
+
status = [status] if status.is_a? Symbol
|
40
|
+
raise InvalidArgument unless status.is_a? Array
|
41
|
+
input = status & AGGREGATED_STATUS_KEYS
|
42
|
+
if input != @aggregated_status
|
43
|
+
AGGREGATED_STATUS_KEYS.each_with_index do |key,index|
|
44
|
+
@aggregated_status_bools[index] = status.include?(key)
|
45
|
+
end
|
46
|
+
aggregated_status_changed options
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_aggregated_status_bools status
|
51
|
+
raise InvalidArgument unless status.is_a? Array
|
52
|
+
raise InvalidArgument unless status.size == 8
|
53
|
+
if status != @aggregated_status_bools
|
54
|
+
@aggregated_status = []
|
55
|
+
AGGREGATED_STATUS_KEYS.each_with_index do |key,index|
|
56
|
+
on = status[index] == true
|
57
|
+
@aggregated_status_bools[index] = on
|
58
|
+
@aggregated_status << key if on
|
59
|
+
end
|
60
|
+
aggregated_status_changed
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def aggregated_status_changed options={}
|
65
|
+
@node.aggregated_status_changed self, options
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module RSMP
|
2
|
+
|
3
|
+
# A proxy to a remote RSMP component.
|
4
|
+
|
5
|
+
class ComponentProxy < ComponentBase
|
6
|
+
def initialize node:, id:, grouped: false
|
7
|
+
super
|
8
|
+
@statuses = {}
|
9
|
+
@allow_repeat_updates = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
# allow the next status update to be a repeat value
|
13
|
+
def allow_repeat_updates subscribe_list
|
14
|
+
subscribe_list.each do |item|
|
15
|
+
sCI = item['sCI']
|
16
|
+
n = item['n']
|
17
|
+
@allow_repeat_updates[sCI] ||= Set.new # Set is like an array, but with no duplicates
|
18
|
+
@allow_repeat_updates[sCI] << n
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Check that were not receiving repeated update values.
|
23
|
+
# The check is not performed for item with an update interval.
|
24
|
+
def check_repeat_values message, subscription_list
|
25
|
+
message.attribute('sS').each do |item|
|
26
|
+
sCI, n, s, q = item['sCI'], item['n'], item['s'], item['q']
|
27
|
+
uRt = subscription_list.dig(c_id,sCI,n,'uRt')
|
28
|
+
next if uRt.to_i > 0
|
29
|
+
next if @allow_repeat_updates[sCI] && @allow_repeat_updates[sCI].include?(n)
|
30
|
+
new_values = {'s'=>s,'q'=>q}
|
31
|
+
old_values = @statuses.dig(sCI,n)
|
32
|
+
if new_values == old_values
|
33
|
+
raise RSMP::RepeatedStatusError.new "no change for #{sCI} '#{n}'"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
# Store the latest status update values
|
38
|
+
def store_status message
|
39
|
+
message.attribute('sS').each do |item|
|
40
|
+
sCI, n, s, q = item['sCI'], item['n'], item['s'], item['q']
|
41
|
+
@statuses[sCI] ||= {}
|
42
|
+
@statuses[sCI][n] = {'s'=>s,'q'=>q}
|
43
|
+
|
44
|
+
# once a value is received, don't allow the value to be a repeat
|
45
|
+
@allow_repeat_updates[sCI].delete(n) if @allow_repeat_updates[sCI]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# handle incoming alarm
|
50
|
+
def handle_alarm message
|
51
|
+
# code = message.attribute('aCId')
|
52
|
+
# previous = @alarms[code]
|
53
|
+
# if previous
|
54
|
+
# unless message.differ?(previous)
|
55
|
+
# raise RepeatedAlarmError.new("no changes from previous alarm #{previous.m_id_short}")
|
56
|
+
# end
|
57
|
+
# if Time.parse(message.attribute('aTs')) < Time.parse(previous.attribute('aTs'))
|
58
|
+
# raise TimestampError.new("timestamp is earlier than previous alarm #{previous.m_id_short}")
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
# p message
|
62
|
+
# ensure
|
63
|
+
# @alarms[code] = message
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
data/lib/rsmp/components.rb
CHANGED
@@ -36,9 +36,9 @@ module RSMP
|
|
36
36
|
@components[component.c_id] = component
|
37
37
|
end
|
38
38
|
|
39
|
-
def build_component id:, type:, settings:{}
|
40
|
-
|
41
|
-
end
|
39
|
+
#def build_component id:, type:, settings:{}
|
40
|
+
# Component.new id:id, node: self, grouped: type=='main'
|
41
|
+
#end
|
42
42
|
|
43
43
|
def infer_component_type component_id
|
44
44
|
Component
|
data/lib/rsmp/message.rb
CHANGED
@@ -61,15 +61,15 @@ module RSMP
|
|
61
61
|
|
62
62
|
def self.build_alarm attributes
|
63
63
|
case attributes["aSp"]
|
64
|
-
when
|
64
|
+
when /Issue/i
|
65
65
|
AlarmIssue.new attributes
|
66
|
-
when
|
66
|
+
when /Request/i
|
67
67
|
AlarmRequest.new attributes
|
68
|
-
when
|
68
|
+
when /Acknowledge/i
|
69
69
|
AlarmAcknowledged.new attributes
|
70
|
-
when
|
70
|
+
when /Suspend/i
|
71
71
|
AlarmSuspend.new attributes
|
72
|
-
when
|
72
|
+
when /Resume/i
|
73
73
|
AlarmResume.new attributes
|
74
74
|
else
|
75
75
|
Alarm.new attributes
|
data/lib/rsmp/proxy.rb
CHANGED
@@ -209,7 +209,6 @@ module RSMP
|
|
209
209
|
|
210
210
|
def start_watchdog
|
211
211
|
log "Starting watchdog with interval #{@site_settings['intervals']['watchdog']} seconds", level: :debug
|
212
|
-
send_watchdog
|
213
212
|
@watchdog_started = true
|
214
213
|
end
|
215
214
|
|
@@ -439,6 +438,7 @@ module RSMP
|
|
439
438
|
end
|
440
439
|
|
441
440
|
def will_not_handle message
|
441
|
+
"WILL NOT HANDLE"
|
442
442
|
reason = "since we're a #{self.class.name.downcase}" unless reason
|
443
443
|
log "Ignoring #{message.type}, #{reason}", message: message, level: :warning
|
444
444
|
dont_acknowledge message, nil, reason
|
data/lib/rsmp/site.rb
CHANGED
@@ -93,9 +93,25 @@ module RSMP
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
-
def
|
96
|
+
def alarm_state_to_message alarm_state
|
97
|
+
Alarm.new(
|
98
|
+
'cId' => alarm_state.component_id,
|
99
|
+
'aTs' => clock.to_s,
|
100
|
+
'aCId' => alarm_state.code,
|
101
|
+
'aSp' => (alarm_state.suspended ? 'Suspend' : 'Issue'),
|
102
|
+
'ack' => (alarm_state.acknowledged ? 'Acknowledged' : 'notAcknowledged'),
|
103
|
+
'sS' => (alarm_state.suspended ? 'suspended' : 'notSuspended'),
|
104
|
+
'aS' => (alarm_state.active ? 'Active' : 'inActive'),
|
105
|
+
'cat' => (alarm_state.category || 'D'),
|
106
|
+
'pri' => (alarm_state.priority || '2'),
|
107
|
+
'rvs' => []
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
def alarm_suspended_or_resumed alarm_state
|
112
|
+
alarm = alarm_state_to_message alarm_state
|
97
113
|
@proxies.each do |proxy|
|
98
|
-
proxy.
|
114
|
+
proxy.send_message alarm if proxy.ready?
|
99
115
|
end
|
100
116
|
end
|
101
117
|
|
@@ -135,5 +151,9 @@ module RSMP
|
|
135
151
|
end
|
136
152
|
nil
|
137
153
|
end
|
154
|
+
|
155
|
+
def build_component id:, type:, settings:{}
|
156
|
+
Component.new id:id, node: self, grouped: type=='main'
|
157
|
+
end
|
138
158
|
end
|
139
159
|
end
|
data/lib/rsmp/site_proxy.rb
CHANGED
@@ -12,6 +12,7 @@ module RSMP
|
|
12
12
|
@supervisor = options[:supervisor]
|
13
13
|
@settings = @supervisor.supervisor_settings.clone
|
14
14
|
@site_id = options[:site_id]
|
15
|
+
@status_subscriptions = {}
|
15
16
|
end
|
16
17
|
|
17
18
|
# handle communication
|
@@ -47,7 +48,8 @@ module RSMP
|
|
47
48
|
def handshake_complete
|
48
49
|
super
|
49
50
|
sanitized_sxl_version = RSMP::Schemer.sanitize_version(@site_sxl_version)
|
50
|
-
log "Connection to site #{@site_id} established, using core #{@rsmp_version}, #{@sxl} #{sanitized_sxl_version}", level: :
|
51
|
+
log "Connection to site #{@site_id} established, using core #{@rsmp_version}, #{@sxl} #{sanitized_sxl_version}", level: :log
|
52
|
+
start_watchdog
|
51
53
|
end
|
52
54
|
|
53
55
|
def process_message message
|
@@ -91,6 +93,20 @@ module RSMP
|
|
91
93
|
@version_determined = true
|
92
94
|
end
|
93
95
|
|
96
|
+
def acknowledged_first_ingoing message
|
97
|
+
case message.type
|
98
|
+
when "Watchdog"
|
99
|
+
send_watchdog
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def acknowledged_first_outgoing message
|
104
|
+
case message.type
|
105
|
+
when "Watchdog"
|
106
|
+
handshake_complete
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
94
110
|
def validate_ready action
|
95
111
|
raise NotReady.new("Can't #{action} because connection is not ready. (Currently #{@state})") unless ready?
|
96
112
|
end
|
@@ -152,14 +168,10 @@ module RSMP
|
|
152
168
|
end
|
153
169
|
|
154
170
|
def version_acknowledged
|
155
|
-
handshake_complete
|
156
171
|
end
|
157
172
|
|
158
173
|
def process_watchdog message
|
159
174
|
super
|
160
|
-
if @watchdog_started == false
|
161
|
-
start_watchdog
|
162
|
-
end
|
163
175
|
end
|
164
176
|
|
165
177
|
def site_ids_changed
|
@@ -192,7 +204,7 @@ module RSMP
|
|
192
204
|
|
193
205
|
def process_status_response message
|
194
206
|
component = find_component message.attribute("cId")
|
195
|
-
component.
|
207
|
+
component.store_status message
|
196
208
|
log "Received #{message.type}", message: message, level: :log
|
197
209
|
acknowledge message
|
198
210
|
end
|
@@ -202,11 +214,23 @@ module RSMP
|
|
202
214
|
m_id = options[:m_id] || RSMP::Message.make_m_id
|
203
215
|
|
204
216
|
# additional items can be used when verifying the response,
|
205
|
-
# but must
|
217
|
+
# but must be removed from the subscribe message
|
206
218
|
subscribe_list = status_list.map { |item| item.slice('sCI','n','uRt') }
|
207
219
|
|
220
|
+
# update our subcription list
|
221
|
+
@status_subscriptions[component_id] ||= {}
|
222
|
+
subscribe_list.each do |item|
|
223
|
+
sCI = item["sCI"]
|
224
|
+
n = item["n"]
|
225
|
+
uRt = item["uRt"]
|
226
|
+
@status_subscriptions[component_id][sCI] ||= {}
|
227
|
+
@status_subscriptions[component_id][sCI][n] ||= {}
|
228
|
+
@status_subscriptions[component_id][sCI][n]['uRt'] = uRt
|
229
|
+
end
|
230
|
+
|
231
|
+
p @status_subscriptions
|
208
232
|
component = find_component component_id
|
209
|
-
component.
|
233
|
+
component.allow_repeat_updates subscribe_list
|
210
234
|
|
211
235
|
message = RSMP::StatusSubscribe.new({
|
212
236
|
"ntsOId" => '',
|
@@ -225,15 +249,24 @@ module RSMP
|
|
225
249
|
end
|
226
250
|
|
227
251
|
def unsubscribe_to_status component_id, status_list, options={}
|
228
|
-
component = find_component component_id
|
229
|
-
component.handle_status_subscribe status_list
|
230
|
-
|
231
252
|
validate_ready 'unsubscribe to status'
|
253
|
+
|
254
|
+
# update our subcription list
|
255
|
+
status_list.each do |item|
|
256
|
+
sCI = item["sCI"]
|
257
|
+
n = item["n"]
|
258
|
+
if @status_subscriptions.dig(component_id,sCI,n)
|
259
|
+
@status_subscriptions[component_id][sCI].delete n
|
260
|
+
@status_subscriptions[component_id].delete(sCI) if @status_subscriptions[component_id][sCI].empty?
|
261
|
+
@status_subscriptions.delete(component_id) if @status_subscriptions[component_id].empty?
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
232
265
|
message = RSMP::StatusUnsubscribe.new({
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
266
|
+
"ntsOId" => '',
|
267
|
+
"xNId" => '',
|
268
|
+
"cId" => component_id,
|
269
|
+
"sS" => status_list
|
237
270
|
})
|
238
271
|
send_message message, validate: options[:validate]
|
239
272
|
message
|
@@ -241,7 +274,8 @@ module RSMP
|
|
241
274
|
|
242
275
|
def process_status_update message
|
243
276
|
component = find_component message.attribute("cId")
|
244
|
-
component.
|
277
|
+
component.check_repeat_values message, @status_subscriptions
|
278
|
+
component.store_status message
|
245
279
|
log "Received #{message.type}", message: message, level: :log
|
246
280
|
acknowledge message
|
247
281
|
end
|
@@ -346,5 +380,10 @@ module RSMP
|
|
346
380
|
@supervisor.notify_error e, options if @supervisor
|
347
381
|
distribute_error e, options
|
348
382
|
end
|
383
|
+
|
384
|
+
def build_component id:, type:, settings:{}
|
385
|
+
ComponentProxy.new id:id, node: self, grouped: type=='main'
|
386
|
+
end
|
387
|
+
|
349
388
|
end
|
350
389
|
end
|
@@ -82,33 +82,33 @@ module RSMP
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def handshake_complete
|
85
|
-
super
|
86
85
|
sanitized_sxl_version = RSMP::Schemer.sanitize_version(sxl_version)
|
87
86
|
log "Connection to supervisor established, using core #{@rsmp_version}, #{sxl} #{sanitized_sxl_version}", level: :info
|
88
87
|
start_watchdog
|
88
|
+
send_all_aggregated_status if @site_settings['send_after_connect']
|
89
|
+
super
|
89
90
|
end
|
90
91
|
|
91
92
|
def process_message message
|
92
93
|
case message
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
super message
|
94
|
+
when StatusResponse, StatusUpdate, AggregatedStatus, AlarmIssue
|
95
|
+
will_not_handle message
|
96
|
+
when AggregatedStatusRequest
|
97
|
+
process_aggregated_status_request message
|
98
|
+
when CommandRequest
|
99
|
+
process_command_request message
|
100
|
+
when CommandResponse
|
101
|
+
process_command_response message
|
102
|
+
when StatusRequest
|
103
|
+
process_status_request message
|
104
|
+
when StatusSubscribe
|
105
|
+
process_status_subcribe message
|
106
|
+
when StatusUnsubscribe
|
107
|
+
process_status_unsubcribe message
|
108
|
+
when AlarmAcknowledged, AlarmSuspend, AlarmResume, AlarmRequest
|
109
|
+
process_alarm message
|
110
|
+
else
|
111
|
+
super message
|
112
112
|
end
|
113
113
|
rescue UnknownComponent, UnknownCommand, UnknownStatus,
|
114
114
|
MessageRejected, MissingAttribute => e
|
@@ -120,10 +120,8 @@ module RSMP
|
|
120
120
|
# aggregateds status should only be send for later version of rsmp
|
121
121
|
# to handle verison differences, we probably need inherited classes
|
122
122
|
case message.type
|
123
|
-
|
124
|
-
|
125
|
-
send_all_aggregated_status
|
126
|
-
end
|
123
|
+
when "Watchdog"
|
124
|
+
handshake_complete
|
127
125
|
end
|
128
126
|
end
|
129
127
|
|
@@ -148,8 +146,8 @@ module RSMP
|
|
148
146
|
log "Received Version message, using RSMP #{@rsmp_version}", message: message, level: :log
|
149
147
|
start_timer
|
150
148
|
acknowledge message
|
151
|
-
handshake_complete
|
152
149
|
@version_determined = true
|
150
|
+
send_watchdog
|
153
151
|
end
|
154
152
|
|
155
153
|
def send_aggregated_status component, options={}
|
@@ -183,13 +181,39 @@ module RSMP
|
|
183
181
|
end
|
184
182
|
|
185
183
|
def process_alarm message
|
184
|
+
case message
|
185
|
+
when AlarmAcknowledged
|
186
|
+
handle_alarm_acknowledge message
|
187
|
+
when AlarmSuspend
|
188
|
+
handle_alarm_suspend message
|
189
|
+
when AlarmResume
|
190
|
+
handle_alarm_resume message
|
191
|
+
when AlarmRequest
|
192
|
+
handle_alarm_request message
|
193
|
+
else
|
194
|
+
dont_acknowledge message, "Invalid alarm message type"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# handle incoming alarm suspend
|
199
|
+
def handle_alarm_suspend message
|
200
|
+
component_id = message.attributes["cId"]
|
201
|
+
component = @site.find_component component_id
|
186
202
|
alarm_code = message.attribute("aCId")
|
187
|
-
|
188
|
-
status = ["ack","aS","sS"].map { |key| message.attribute(key) }.join(',')
|
189
|
-
log "Received #{message.type}, #{alarm_code} #{asp} [#{status}]", message: message, level: :log
|
203
|
+
log "Received #{message.type} #{alarm_code} suspend", message: message, level: :log
|
190
204
|
acknowledge message
|
205
|
+
component.suspend_alarm alarm_code
|
191
206
|
end
|
192
207
|
|
208
|
+
# handle incoming alarm resume
|
209
|
+
def handle_alarm_resume message
|
210
|
+
component_id = message.attributes["cId"]
|
211
|
+
component = @site.find_component component_id
|
212
|
+
alarm_code = message.attribute("aCId")
|
213
|
+
log "Received #{message.type} #{alarm_code} resume", message: message, level: :log
|
214
|
+
acknowledge message
|
215
|
+
component.resume_alarm alarm_code
|
216
|
+
end
|
193
217
|
# reorganize rmsp command request arg attribute:
|
194
218
|
# [{"cCI":"M0002","cO":"setPlan","n":"status","v":"True"},{"cCI":"M0002","cO":"setPlan","n":"securityCode","v":"5678"},{"cCI":"M0002","cO":"setPlan","n":"timeplan","v":"3"}]
|
195
219
|
# into the simpler, but equivalent:
|
@@ -263,24 +287,28 @@ module RSMP
|
|
263
287
|
# for each component, containing all the requested statuses
|
264
288
|
|
265
289
|
update_list = {}
|
266
|
-
|
267
|
-
@status_subscriptions[
|
268
|
-
update_list[
|
290
|
+
component_id = message.attributes["cId"]
|
291
|
+
@status_subscriptions[component_id] ||= {}
|
292
|
+
update_list[component_id] ||= {}
|
269
293
|
now = Time.now # internal timestamp
|
270
|
-
subs = @status_subscriptions[
|
294
|
+
subs = @status_subscriptions[component_id]
|
271
295
|
|
272
296
|
message.attributes["sS"].each do |arg|
|
273
297
|
sCI = arg["sCI"]
|
274
298
|
subcription = {interval: arg["uRt"].to_i, last_sent_at: now}
|
275
299
|
subs[sCI] ||= {}
|
276
300
|
subs[sCI][arg["n"]] = subcription
|
277
|
-
update_list[
|
278
|
-
update_list[
|
301
|
+
update_list[component_id][sCI] ||= []
|
302
|
+
update_list[component_id][sCI] << arg["n"]
|
279
303
|
end
|
280
304
|
acknowledge message
|
281
305
|
send_status_updates update_list # send status after subscribing is accepted
|
282
306
|
end
|
283
307
|
|
308
|
+
def get_status_subscribe_interval component_id, sCI, n
|
309
|
+
@status_subscriptions.dig component_id, sCI, n
|
310
|
+
end
|
311
|
+
|
284
312
|
def process_status_unsubcribe message
|
285
313
|
log "Received #{message.type}", message: message, level: :log
|
286
314
|
component = message.attributes["cId"]
|
data/lib/rsmp/version.rb
CHANGED
data/lib/rsmp.rb
CHANGED
@@ -30,7 +30,10 @@ require 'rsmp/collect/status_collector'
|
|
30
30
|
require 'rsmp/collect/command_response_collector'
|
31
31
|
require 'rsmp/collect/aggregated_status_collector'
|
32
32
|
require 'rsmp/collect/alarm_collector'
|
33
|
+
require 'rsmp/alarm_state'
|
34
|
+
require 'rsmp/component_base'
|
33
35
|
require 'rsmp/component'
|
36
|
+
require 'rsmp/component_proxy'
|
34
37
|
require 'rsmp/site'
|
35
38
|
require 'rsmp/proxy'
|
36
39
|
require 'rsmp/supervisor_proxy'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rsmp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Emil Tin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async
|
@@ -208,6 +208,7 @@ files:
|
|
208
208
|
- documentation/tasks.md
|
209
209
|
- exe/rsmp
|
210
210
|
- lib/rsmp.rb
|
211
|
+
- lib/rsmp/alarm_state.rb
|
211
212
|
- lib/rsmp/archive.rb
|
212
213
|
- lib/rsmp/cli.rb
|
213
214
|
- lib/rsmp/collect/aggregated_status_collector.rb
|
@@ -225,6 +226,8 @@ files:
|
|
225
226
|
- lib/rsmp/collect/status_collector.rb
|
226
227
|
- lib/rsmp/collect/status_query.rb
|
227
228
|
- lib/rsmp/component.rb
|
229
|
+
- lib/rsmp/component_base.rb
|
230
|
+
- lib/rsmp/component_proxy.rb
|
228
231
|
- lib/rsmp/components.rb
|
229
232
|
- lib/rsmp/convert/export/json_schema.rb
|
230
233
|
- lib/rsmp/convert/import/yaml.rb
|