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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e36c6f5f96850c9ad1a07ab856efc1e094deafee596074a611a08a2535f57ec
4
- data.tar.gz: efedbe0726d80f1fff7e3d37fa171fa1afe1df1705cf50e142eee0667c6e8dff
3
+ metadata.gz: 957caaa3191011a85a0284ef530434184e15620b7762e4971a8547f45a955ec3
4
+ data.tar.gz: 8905eb13b36d26d6c50fe0e4466f40e2f742d18384e93bc2b7e67d2e77975441
5
5
  SHA512:
6
- metadata.gz: 6f4362c8689573a0c7ce8e91ea079c0ce06a6b8d73bc27ce964d781dd64ee53b92c271a7ba41f31e89af62475adf341ae2a5b6682418758d3558930e72c037d3
7
- data.tar.gz: 9f595995ec7247fdfb2e782e235c83adb475b1db85c8834aa434a42b5bc5e1ca6f4ae8e71233473bcee21b7c722b8f5a4862a8531098f793f629e7832b0eda3c
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.11.5)
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.14.0)
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
@@ -51,8 +51,11 @@ security_codes:
51
51
  log:
52
52
  ip: false
53
53
  site_id: 9
54
- component: 2
54
+ component: 3
55
55
  level: false
56
56
  debug: true
57
57
  json: true
58
58
  live_output: tmp/tlc.state
59
+ skip_validation:
60
+ - AlarmSuspend
61
+ - AlarmResume
@@ -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 got['cCI']
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
@@ -1,72 +1,10 @@
1
1
  module RSMP
2
2
 
3
- # Class that represents an RMSP component.
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
- @c_id = id
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
- # handle incoming alarm
81
- def handle_alarm message
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
- # Handle an incoming status update, by storing the values
123
- def handle_status_update message
124
- store_status message, check_repeated: true
125
- end
126
-
127
- # Our proxy subscribed to status updates
128
- # Store update rates, so we can check for repeated alarm if we asked for updates only
129
- # when there's a change, not on a regular interval.
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
- # Our proxy unsubscribed to status updates.
148
- # Update our list of update rates.
149
- def handle_status_unsubscribe status_list
150
- status_list.each do |item|
151
- sCI, n = item['sCI'], item['n']
152
- if @subscribes[sCI]
153
- @subscribes[sCI].delete n
154
- end
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
- # Store the latest status update values, optionally
166
- # checking that we're not receiving unchanged values if we're subscribed
167
- # wit updates only on change
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
@@ -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
- Component.new id:id, node: self, grouped: type=='main'
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 'Issue'
64
+ when /Issue/i
65
65
  AlarmIssue.new attributes
66
- when 'Request'
66
+ when /Request/i
67
67
  AlarmRequest.new attributes
68
- when 'Acknowledge'
68
+ when /Acknowledge/i
69
69
  AlarmAcknowledged.new attributes
70
- when 'Suspend'
70
+ when /Suspend/i
71
71
  AlarmSuspend.new attributes
72
- when 'Resume'
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 alarm_changed component, alarm
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.send_alarm component, alarm if proxy.ready?
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
@@ -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: :info
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.handle_status_response message
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 to remove from the subscribe message
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.handle_status_subscribe subscribe_list
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
- "ntsOId" => '',
234
- "xNId" => '',
235
- "cId" => component_id,
236
- "sS" => status_list
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.handle_status_update message
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
- when Alarm
94
- when StatusResponse
95
- when StatusUpdate
96
- when AggregatedStatus
97
- will_not_handle message
98
- when AggregatedStatusRequest
99
- process_aggregated_status_request message
100
- when CommandRequest
101
- process_command_request message
102
- when CommandResponse
103
- process_command_response message
104
- when StatusRequest
105
- process_status_request message
106
- when StatusSubscribe
107
- process_status_subcribe message
108
- when StatusUnsubscribe
109
- process_status_unsubcribe message
110
- else
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
- when "Watchdog"
124
- if @site_settings['send_after_connect']
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
- asp = message.attribute("aSp")
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
- component = message.attributes["cId"]
267
- @status_subscriptions[component] ||= {}
268
- update_list[component] ||= {}
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[component]
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[component][sCI] ||= []
278
- update_list[component][sCI] << arg["n"]
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
@@ -1,3 +1,3 @@
1
1
  module RSMP
2
- VERSION = "0.11.5"
2
+ VERSION = "0.12.0"
3
3
  end
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.11.5
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-03-22 00:00:00.000000000 Z
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