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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/devcontainer.json +22 -0
  3. data/.github/workflows/rubocop.yaml +17 -0
  4. data/.gitignore +5 -6
  5. data/.rubocop.yml +80 -0
  6. data/Gemfile +13 -1
  7. data/Gemfile.lock +34 -1
  8. data/Rakefile +3 -3
  9. data/lib/rsmp/cli.rb +147 -124
  10. data/lib/rsmp/collect/ack_collector.rb +8 -7
  11. data/lib/rsmp/collect/aggregated_status_collector.rb +4 -4
  12. data/lib/rsmp/collect/alarm_collector.rb +31 -23
  13. data/lib/rsmp/collect/alarm_matcher.rb +3 -3
  14. data/lib/rsmp/collect/collector/logging.rb +17 -0
  15. data/lib/rsmp/collect/collector/reporting.rb +44 -0
  16. data/lib/rsmp/collect/collector/status.rb +34 -0
  17. data/lib/rsmp/collect/collector.rb +69 -150
  18. data/lib/rsmp/collect/command_matcher.rb +19 -6
  19. data/lib/rsmp/collect/command_response_collector.rb +7 -7
  20. data/lib/rsmp/collect/distributor.rb +14 -11
  21. data/lib/rsmp/collect/filter.rb +31 -15
  22. data/lib/rsmp/collect/matcher.rb +7 -11
  23. data/lib/rsmp/collect/queue.rb +4 -4
  24. data/lib/rsmp/collect/receiver.rb +10 -12
  25. data/lib/rsmp/collect/state_collector.rb +116 -77
  26. data/lib/rsmp/collect/status_collector.rb +6 -6
  27. data/lib/rsmp/collect/status_matcher.rb +17 -7
  28. data/lib/rsmp/{alarm_state.rb → component/alarm_state.rb} +76 -37
  29. data/lib/rsmp/{component.rb → component/component.rb} +15 -15
  30. data/lib/rsmp/component/component_base.rb +89 -0
  31. data/lib/rsmp/component/component_proxy.rb +75 -0
  32. data/lib/rsmp/component/components.rb +63 -0
  33. data/lib/rsmp/convert/export/json_schema.rb +116 -110
  34. data/lib/rsmp/convert/import/yaml.rb +21 -18
  35. data/lib/rsmp/{rsmp.rb → helpers/clock.rb} +5 -6
  36. data/lib/rsmp/{deep_merge.rb → helpers/deep_merge.rb} +2 -1
  37. data/lib/rsmp/helpers/error.rb +71 -0
  38. data/lib/rsmp/{inspect.rb → helpers/inspect.rb} +6 -10
  39. data/lib/rsmp/log/archive.rb +98 -0
  40. data/lib/rsmp/log/colorization.rb +41 -0
  41. data/lib/rsmp/log/filtering.rb +54 -0
  42. data/lib/rsmp/log/logger.rb +206 -0
  43. data/lib/rsmp/{logging.rb → log/logging.rb} +5 -7
  44. data/lib/rsmp/message.rb +159 -148
  45. data/lib/rsmp/{node.rb → node/node.rb} +19 -17
  46. data/lib/rsmp/{protocol.rb → node/protocol.rb} +5 -3
  47. data/lib/rsmp/node/site/site.rb +195 -0
  48. data/lib/rsmp/node/supervisor/modules/configuration.rb +59 -0
  49. data/lib/rsmp/node/supervisor/modules/connection.rb +140 -0
  50. data/lib/rsmp/node/supervisor/modules/sites.rb +64 -0
  51. data/lib/rsmp/node/supervisor/supervisor.rb +72 -0
  52. data/lib/rsmp/{task.rb → node/task.rb} +12 -14
  53. data/lib/rsmp/proxy/modules/acknowledgements.rb +144 -0
  54. data/lib/rsmp/proxy/modules/receive.rb +119 -0
  55. data/lib/rsmp/proxy/modules/send.rb +76 -0
  56. data/lib/rsmp/proxy/modules/state.rb +25 -0
  57. data/lib/rsmp/proxy/modules/tasks.rb +105 -0
  58. data/lib/rsmp/proxy/modules/versions.rb +69 -0
  59. data/lib/rsmp/proxy/modules/watchdogs.rb +66 -0
  60. data/lib/rsmp/proxy/proxy.rb +199 -0
  61. data/lib/rsmp/proxy/site/modules/aggregated_status.rb +52 -0
  62. data/lib/rsmp/proxy/site/modules/alarms.rb +27 -0
  63. data/lib/rsmp/proxy/site/modules/commands.rb +31 -0
  64. data/lib/rsmp/proxy/site/modules/status.rb +110 -0
  65. data/lib/rsmp/proxy/site/site_proxy.rb +205 -0
  66. data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +47 -0
  67. data/lib/rsmp/proxy/supervisor/modules/alarms.rb +73 -0
  68. data/lib/rsmp/proxy/supervisor/modules/commands.rb +53 -0
  69. data/lib/rsmp/proxy/supervisor/modules/status.rb +204 -0
  70. data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +178 -0
  71. data/lib/rsmp/tlc/detector_logic.rb +18 -34
  72. data/lib/rsmp/tlc/input_states.rb +126 -0
  73. data/lib/rsmp/tlc/modules/detector_logics.rb +50 -0
  74. data/lib/rsmp/tlc/modules/display.rb +78 -0
  75. data/lib/rsmp/tlc/modules/helpers.rb +41 -0
  76. data/lib/rsmp/tlc/modules/inputs.rb +173 -0
  77. data/lib/rsmp/tlc/modules/modes.rb +253 -0
  78. data/lib/rsmp/tlc/modules/outputs.rb +30 -0
  79. data/lib/rsmp/tlc/modules/plans.rb +218 -0
  80. data/lib/rsmp/tlc/modules/signal_groups.rb +109 -0
  81. data/lib/rsmp/tlc/modules/startup_sequence.rb +22 -0
  82. data/lib/rsmp/tlc/modules/system.rb +140 -0
  83. data/lib/rsmp/tlc/modules/traffic_data.rb +49 -0
  84. data/lib/rsmp/tlc/signal_group.rb +37 -41
  85. data/lib/rsmp/tlc/signal_plan.rb +14 -11
  86. data/lib/rsmp/tlc/signal_priority.rb +39 -35
  87. data/lib/rsmp/tlc/startup_sequence.rb +59 -0
  88. data/lib/rsmp/tlc/traffic_controller.rb +38 -1010
  89. data/lib/rsmp/tlc/traffic_controller_site.rb +58 -57
  90. data/lib/rsmp/version.rb +1 -1
  91. data/lib/rsmp.rb +82 -48
  92. data/rsmp.gemspec +24 -31
  93. metadata +79 -139
  94. data/lib/rsmp/archive.rb +0 -76
  95. data/lib/rsmp/collect/message_matchers.rb +0 -0
  96. data/lib/rsmp/component_base.rb +0 -87
  97. data/lib/rsmp/component_proxy.rb +0 -57
  98. data/lib/rsmp/components.rb +0 -65
  99. data/lib/rsmp/error.rb +0 -71
  100. data/lib/rsmp/logger.rb +0 -216
  101. data/lib/rsmp/proxy.rb +0 -693
  102. data/lib/rsmp/site.rb +0 -188
  103. data/lib/rsmp/site_proxy.rb +0 -389
  104. data/lib/rsmp/supervisor.rb +0 -302
  105. data/lib/rsmp/supervisor_proxy.rb +0 -510
  106. data/lib/rsmp/tlc/inputs.rb +0 -134
@@ -0,0 +1,199 @@
1
+ # A connection to a remote site or supervisor.
2
+ # Uses the Task module to handle asyncronous work, but adds
3
+ # the concept of a connection that can be connected or disconnected.
4
+
5
+ require 'rubygems'
6
+
7
+ module RSMP
8
+ class Proxy
9
+ WRAPPING_DELIMITER = "\f".freeze
10
+
11
+ include Logging
12
+ include Distributor
13
+ include Inspect
14
+ include Task
15
+ include Modules::State
16
+ include Modules::Watchdogs
17
+ include Modules::Acknowledgements
18
+ include Modules::Send
19
+ include Modules::Receive
20
+ include Modules::Versions
21
+ include Modules::Tasks
22
+
23
+ attr_reader :state, :archive, :connection_info, :sxl, :collector, :ip, :port, :node, :core_version
24
+
25
+ def initialize(options)
26
+ @node = options[:node]
27
+ initialize_logging options
28
+ initialize_distributor
29
+ initialize_task
30
+ setup options
31
+ clear
32
+ @state = :disconnected
33
+ @state_condition = Async::Notification.new
34
+ end
35
+
36
+ def now
37
+ node.now
38
+ end
39
+
40
+ # Connection lifecycle methods
41
+
42
+ def disconnect; end
43
+
44
+ # wait for the reader task to complete,
45
+ # which is not expected to happen before the connection is closed
46
+ def wait_for_reader
47
+ @reader&.wait
48
+ end
49
+
50
+ # close connection, but keep our main task running so we can reconnect
51
+ def close
52
+ log 'Closing connection', level: :warning
53
+ close_stream
54
+ close_socket
55
+ stop_reader
56
+ self.state = :disconnected
57
+ distribute_error DisconnectError.new('Connection was closed')
58
+
59
+ # stop timer
60
+ # as we're running inside the timer, code after stop_timer() will not be called,
61
+ # unless it's in the ensure block
62
+ stop_timer
63
+ end
64
+
65
+ def stop_subtasks
66
+ stop_timer
67
+ stop_reader
68
+ clear
69
+ super
70
+ end
71
+
72
+ def stop_timer
73
+ @timer&.stop
74
+ ensure
75
+ @timer = nil
76
+ end
77
+
78
+ def stop_reader
79
+ @reader&.stop
80
+ ensure
81
+ @reader = nil
82
+ end
83
+
84
+ def close_stream
85
+ @stream&.close
86
+ ensure
87
+ @stream = nil
88
+ end
89
+
90
+ def close_socket
91
+ @socket&.close
92
+ ensure
93
+ @socket = nil
94
+ end
95
+
96
+ def stop_task
97
+ close
98
+ super
99
+ end
100
+
101
+ # State management methods
102
+
103
+ def ready?
104
+ @state == :ready
105
+ end
106
+
107
+ def connected?
108
+ @state == :connected || @state == :ready
109
+ end
110
+
111
+ def disconnected?
112
+ @state == :disconnected
113
+ end
114
+
115
+ # change our state
116
+ def state=(state)
117
+ return if state == @state
118
+
119
+ @state = state
120
+ state_changed
121
+ end
122
+
123
+ # the state changed
124
+ # override to to things like notifications
125
+ def state_changed
126
+ @state_condition.signal @state
127
+ end
128
+
129
+ def clear
130
+ @awaiting_acknowledgement = {}
131
+ @latest_watchdog_received = nil
132
+ @watchdog_started = false
133
+ @version_determined = false
134
+ @ingoing_acknowledged = {}
135
+ @outgoing_acknowledged = {}
136
+ @latest_watchdog_send_at = nil
137
+
138
+ @acknowledgements = {}
139
+ @acknowledgement_condition = Async::Notification.new
140
+ end
141
+
142
+ # revive after a reconnect
143
+ def revive(options)
144
+ setup options
145
+ end
146
+
147
+ def setup(options)
148
+ @settings = options[:settings]
149
+ @socket = options[:socket]
150
+ @stream = options[:stream]
151
+ @protocol = options[:protocol]
152
+ @ip = options[:ip]
153
+ @port = options[:port]
154
+ @connection_info = options[:info]
155
+ @sxl = nil
156
+ @site_settings = nil # can't pick until we know the site id
157
+ return unless options[:collect]
158
+
159
+ @collector = RSMP::Collector.new self, options[:collect]
160
+ @collector.start
161
+ end
162
+
163
+ def inspect
164
+ "#<#{self.class.name}:#{object_id}, #{inspector(
165
+ :@acknowledgements, :@settings, :@site_settings
166
+ )}>"
167
+ end
168
+
169
+ def clock
170
+ @node.clock
171
+ end
172
+
173
+ def receive_error(error, options = {})
174
+ @node.receive_error error, options
175
+ end
176
+
177
+ def log(str, options = {})
178
+ super(str, options.merge(ip: @ip, port: @port, site_id: @site_id))
179
+ end
180
+
181
+ def schemas
182
+ schemas = { core: RSMP::Schema.latest_core_version } # use latest core
183
+ schemas[:core] = core_version if core_version
184
+ schemas[sxl] = RSMP::Schema.sanitize_version(sxl_version.to_s) if sxl && sxl_version
185
+ schemas
186
+ end
187
+
188
+ def author
189
+ @node.site_id
190
+ end
191
+
192
+ # Use Gem class to check version requirement
193
+ # Requirement must be a string like '1.1', '>=1.0.3' or '<2.1.4',
194
+ # or list of strings, like ['<=1.4','<1.5']
195
+ def self.version_meets_requirement?(version, requirement)
196
+ Modules::Versions.version_meets_requirement?(version, requirement)
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,52 @@
1
+ module RSMP
2
+ class SiteProxy < Proxy
3
+ module Modules
4
+ # Handles aggregated status requests and responses
5
+ module AggregatedStatus
6
+ def request_aggregated_status(component, options = {})
7
+ validate_ready 'request aggregated status'
8
+ m_id = options[:m_id] || RSMP::Message.make_m_id
9
+ message = RSMP::AggregatedStatusRequest.new({
10
+ 'cId' => component,
11
+ 'mId' => m_id
12
+ })
13
+ apply_nts_message_attributes message
14
+ send_and_optionally_collect message, options do |collect_options|
15
+ AggregatedStatusCollector.new(
16
+ self,
17
+ collect_options.merge(task: @task, m_id: m_id, num: 1)
18
+ )
19
+ end
20
+ end
21
+
22
+ def validate_aggregated_status(message, status_elements)
23
+ return if status_elements.is_a?(Array) && status_elements.size == 8
24
+
25
+ dont_acknowledge message, 'Received', reaons
26
+ raise InvalidMessage
27
+ end
28
+
29
+ def process_aggregated_status(message)
30
+ status_elements = message.attribute('se')
31
+ validate_aggregated_status(message, status_elements)
32
+ c_id = message.attributes['cId']
33
+ component = find_component c_id
34
+ unless component
35
+ reason = "component #{c_id} not found"
36
+ dont_acknowledge message, "Ignoring #{message.type}:", reason
37
+ return
38
+ end
39
+
40
+ component.aggregated_status_bools = status_elements
41
+ log "Received #{message.type} status for component #{c_id} [#{component.aggregated_status.join(', ')}]",
42
+ message: message
43
+ acknowledge message
44
+ end
45
+
46
+ def aggregated_status_changed(component, _options = {})
47
+ @supervisor.aggregated_status_changed self, component
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,27 @@
1
+ module RSMP
2
+ class SiteProxy < Proxy
3
+ module Modules
4
+ # Handles alarm messages
5
+ module Alarms
6
+ def process_alarm(message)
7
+ component = find_component message.attribute('cId')
8
+ status = %w[ack aS sS].map { |key| message.attribute(key) }.join(',')
9
+ component.handle_alarm message
10
+ alarm_code = message.attribute('aCId')
11
+ asp = message.attribute('aSp')
12
+ log "Received #{message.type}, #{alarm_code} #{asp} [#{status}]", message: message, level: :log
13
+ acknowledge message
14
+ end
15
+
16
+ def send_alarm_acknowledgement(component, alarm_code, options = {})
17
+ message = RSMP::AlarmAcknowledged.new({
18
+ 'cId' => component,
19
+ 'aCId' => alarm_code
20
+ })
21
+ send_message message, validate: options[:validate]
22
+ message
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ module RSMP
2
+ class SiteProxy < Proxy
3
+ module Modules
4
+ # Handles command requests and responses
5
+ module Commands
6
+ def send_command(component, command_list, options = {})
7
+ validate_ready 'send command'
8
+ m_id = options[:m_id] || RSMP::Message.make_m_id
9
+ message = RSMP::CommandRequest.new({
10
+ 'cId' => component,
11
+ 'arg' => command_list,
12
+ 'mId' => m_id
13
+ })
14
+ apply_nts_message_attributes message
15
+ send_and_optionally_collect message, options do |collect_options|
16
+ CommandResponseCollector.new(
17
+ self,
18
+ command_list,
19
+ collect_options.merge(task: @task, m_id: m_id)
20
+ )
21
+ end
22
+ end
23
+
24
+ def process_command_response(message)
25
+ log "Received #{message.type}", message: message, level: :log
26
+ acknowledge message
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,110 @@
1
+ module RSMP
2
+ class SiteProxy < Proxy
3
+ module Modules
4
+ # Handles status requests, responses, subscriptions and updates
5
+ module Status
6
+ def request_status(component, status_list, options = {})
7
+ validate_ready 'request status'
8
+ m_id = options[:m_id] || RSMP::Message.make_m_id
9
+
10
+ # additional items can be used when verifying the response,
11
+ # but must be removed from the request
12
+ request_list = status_list.map { |item| item.slice('sCI', 'n') }
13
+
14
+ message = RSMP::StatusRequest.new({
15
+ 'cId' => component,
16
+ 'sS' => request_list,
17
+ 'mId' => m_id
18
+ })
19
+ apply_nts_message_attributes message
20
+ send_and_optionally_collect message, options do |collect_options|
21
+ StatusCollector.new(
22
+ self,
23
+ status_list,
24
+ collect_options.merge(task: @task, m_id: m_id)
25
+ )
26
+ end
27
+ end
28
+
29
+ def process_status_response(message)
30
+ component = find_component message.attribute('cId')
31
+ component.store_status message
32
+ log "Received #{message.type}", message: message, level: :log
33
+ acknowledge message
34
+ end
35
+
36
+ def ensure_subscription_path(component_id, code, name)
37
+ @status_subscriptions[component_id] ||= {}
38
+ @status_subscriptions[component_id][code] ||= {}
39
+ @status_subscriptions[component_id][code][name] ||= {}
40
+ end
41
+
42
+ def update_subscription(component_id, subscribe_list)
43
+ subscribe_list.each do |item|
44
+ code = item['sCI']
45
+ name = item['n']
46
+ sub = ensure_subscription_path(component_id, code, name)
47
+ sub['uRt'] = item['uRt']
48
+ sub['sOc'] = item['sOc']
49
+ end
50
+ end
51
+
52
+ def subscribe_to_status(component_id, status_list, options = {})
53
+ validate_ready 'subscribe to status'
54
+ m_id = options[:m_id] || RSMP::Message.make_m_id
55
+ subscribe_list = status_list.map { |item| item.slice('sCI', 'n', 'uRt', 'sOc') }
56
+
57
+ update_subscription(component_id, subscribe_list)
58
+ find_component component_id
59
+
60
+ message = RSMP::StatusSubscribe.new({
61
+ 'cId' => component_id,
62
+ 'sS' => subscribe_list,
63
+ 'mId' => m_id
64
+ })
65
+ apply_nts_message_attributes message
66
+
67
+ send_and_optionally_collect message, options do |collect_options|
68
+ StatusCollector.new(
69
+ self,
70
+ status_list,
71
+ collect_options.merge(task: @task, m_id: m_id)
72
+ )
73
+ end
74
+ end
75
+
76
+ def remove_subscription_item(component_id, code, name)
77
+ return unless @status_subscriptions.dig(component_id, code, name)
78
+
79
+ @status_subscriptions[component_id][code].delete name
80
+ @status_subscriptions[component_id].delete(code) if @status_subscriptions[component_id][code].empty?
81
+ @status_subscriptions.delete(component_id) if @status_subscriptions[component_id].empty?
82
+ end
83
+
84
+ def unsubscribe_to_status(component_id, status_list, options = {})
85
+ validate_ready 'unsubscribe to status'
86
+
87
+ status_list.each do |item|
88
+ remove_subscription_item(component_id, item['sCI'], item['n'])
89
+ end
90
+
91
+ message = RSMP::StatusUnsubscribe.new({
92
+ 'cId' => component_id,
93
+ 'sS' => status_list
94
+ })
95
+ apply_nts_message_attributes message
96
+ send_message message, validate: options[:validate]
97
+ message
98
+ end
99
+
100
+ def process_status_update(message)
101
+ component = find_component message.attribute('cId')
102
+ component.check_repeat_values message, @status_subscriptions
103
+ component.store_status message
104
+ log "Received #{message.type}", message: message, level: :log
105
+ acknowledge message
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,205 @@
1
+ # Handles a supervisor connection to a remote client
2
+
3
+ module RSMP
4
+ class SiteProxy < Proxy
5
+ include Components
6
+ include Modules::Status
7
+ include Modules::AggregatedStatus
8
+ include Modules::Alarms
9
+ include Modules::Commands
10
+
11
+ attr_reader :supervisor, :site_id
12
+
13
+ def initialize(options)
14
+ super(options.merge(node: options[:supervisor]))
15
+ initialize_components
16
+ @supervisor = options[:supervisor]
17
+ @settings = @supervisor.supervisor_settings.clone
18
+ @site_id = options[:site_id]
19
+ @status_subscriptions = {}
20
+ end
21
+
22
+ # handle communication
23
+ # when we're created, the socket is already open
24
+ def run
25
+ self.state = :connected
26
+ start_reader
27
+ wait_for_reader # run until disconnected
28
+ rescue RSMP::ConnectionError => e
29
+ log e, level: :error
30
+ rescue StandardError => e
31
+ distribute_error e, level: :internal
32
+ ensure
33
+ close
34
+ end
35
+
36
+ def revive(options)
37
+ super
38
+ @supervisor = options[:supervisor]
39
+ @settings = @supervisor.supervisor_settings.clone
40
+ end
41
+
42
+ def inspect
43
+ "#<#{self.class.name}:#{object_id}, #{inspector(
44
+ :@acknowledgements, :@settings, :@site_settings, :@components
45
+ )}>"
46
+ end
47
+
48
+ def node
49
+ supervisor
50
+ end
51
+
52
+ def handshake_complete
53
+ super
54
+ sanitized_sxl_version = RSMP::Schema.sanitize_version(@site_sxl_version)
55
+ log "Connection to site #{@site_id} established, using core #{@core_version}, #{@sxl} #{sanitized_sxl_version}",
56
+ level: :info
57
+ start_watchdog
58
+ end
59
+
60
+ def process_message(message)
61
+ return super if handled_by_parent?(message)
62
+
63
+ case message
64
+ when StatusUnsubscribe, AggregatedStatusRequest
65
+ will_not_handle message
66
+ when AggregatedStatus
67
+ process_aggregated_status message
68
+ when AlarmIssue, AlarmSuspended, AlarmResumed, AlarmAcknowledged
69
+ process_alarm message
70
+ when CommandResponse
71
+ process_command_response message
72
+ when StatusResponse
73
+ process_status_response message
74
+ when StatusUpdate
75
+ process_status_update message
76
+ else
77
+ super
78
+ end
79
+ rescue RSMP::RepeatedAlarmError, RSMP::RepeatedStatusError, RSMP::TimestampError => e
80
+ str = "Rejected #{message.type} message,"
81
+ dont_acknowledge message, str, e.to_s
82
+ distribute_error e.exception("#{str}#{e.message} #{message.json}")
83
+ end
84
+
85
+ def handled_by_parent?(message)
86
+ message.is_a?(CommandRequest) || message.is_a?(StatusRequest) || message.is_a?(StatusSubscribe)
87
+ end
88
+
89
+ def version_accepted(message)
90
+ log "Received Version message for site #{@site_id}", message: message, level: :log
91
+ start_timer
92
+ acknowledge message
93
+ send_version @site_id, core_versions
94
+ @version_determined = true
95
+ end
96
+
97
+ def acknowledged_first_ingoing(message)
98
+ case message.type
99
+ when 'Watchdog'
100
+ send_watchdog
101
+ end
102
+ end
103
+
104
+ def acknowledged_first_outgoing(message)
105
+ case message.type
106
+ when 'Watchdog'
107
+ handshake_complete
108
+ end
109
+ end
110
+
111
+ def validate_ready(action)
112
+ raise NotReady, "Can't #{action} because connection is not ready. (Currently #{@state})" unless ready?
113
+ end
114
+
115
+ def version_acknowledged; end
116
+
117
+ def site_ids_changed
118
+ @supervisor.site_ids_changed
119
+ end
120
+
121
+ def watchdog_interval=(interval)
122
+ @settings['intervals']['watchdog'] = interval
123
+ end
124
+
125
+ def check_sxl_version(message)
126
+ # check that we have a schema for specified sxl type and version
127
+ # note that the type comes from the site config, while the version
128
+ # comes from the Version message send by the site
129
+ type = @site_settings['sxl']
130
+ version = message.attribute 'SXL'
131
+ RSMP::Schema.find_schema! type, version, lenient: true
132
+
133
+ # store sxl version requested by site
134
+ # TODO should check agaist site settings
135
+ @site_sxl_version = message.attribute 'SXL'
136
+ rescue RSMP::Schema::UnknownSchemaError => e
137
+ dont_acknowledge message, "Rejected #{message.type} message,", e.to_s
138
+ end
139
+
140
+ def sxl_version
141
+ # a supervisor does not maintain it's own sxl version
142
+ # instead we use what the site requests
143
+ @site_sxl_version
144
+ end
145
+
146
+ def process_version(message)
147
+ return extraneous_version message if @version_determined
148
+
149
+ check_site_ids message
150
+ check_sxl_version message
151
+ version_accepted message
152
+ end
153
+
154
+ def check_site_ids(message)
155
+ # RSMP support multiple site ids. we don't support this yet. instead we use the first id only
156
+ site_id = message.attribute('siteId').map { |item| item['sId'] }.first
157
+ @supervisor.check_site_id site_id
158
+ site_ids_changed
159
+ end
160
+
161
+ def find_site_settings(_site_id)
162
+ if @settings['sites'] && @settings['sites'][@site_id]
163
+ log "Using site settings for site id #{@site_id}", level: :debug
164
+ return @settings['sites'][@site_id]
165
+ end
166
+
167
+ @settings['guest']
168
+ if @settings['guest']
169
+ log 'Using site settings for guest', level: :debug
170
+ return @settings['guest']
171
+ end
172
+
173
+ nil
174
+ end
175
+
176
+ def setup_site_settings
177
+ @site_settings = find_site_settings @site_id
178
+ if @site_settings
179
+ @sxl = @site_settings['sxl']
180
+ setup_components @site_settings['components']
181
+ else
182
+ dont_acknowledge message, 'Rejected', "No config found for site #{@site_id}"
183
+ end
184
+ end
185
+
186
+ def receive_error(error, options = {})
187
+ @supervisor&.receive_error error, options
188
+ distribute_error error, options
189
+ end
190
+
191
+ def build_component(id:, type:, settings: {})
192
+ settings ||= {}
193
+ if type == 'main'
194
+ ComponentProxy.new id: id, node: self, grouped: true,
195
+ ntsoid: settings['ntsOId'], xnid: settings['xNId']
196
+ else
197
+ ComponentProxy.new id: id, node: self, grouped: false
198
+ end
199
+ end
200
+
201
+ def infer_component_type(_component_id)
202
+ ComponentProxy
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,47 @@
1
+ module RSMP
2
+ class SupervisorProxy < Proxy
3
+ module Modules
4
+ # Handles aggregated status messages
5
+ module AggregatedStatus
6
+ def send_all_aggregated_status
7
+ @site.components.each_pair do |_c_id, component|
8
+ send_aggregated_status component if component.grouped
9
+ end
10
+ end
11
+
12
+ # Send aggregated status for a component
13
+ def send_aggregated_status(component, options = {})
14
+ m_id = options[:m_id] || RSMP::Message.make_m_id
15
+
16
+ se = if Proxy.version_meets_requirement?(core_version, '<=3.1.2')
17
+ component.aggregated_status_bools.map { |bool| bool ? 'true' : 'false' }
18
+ else
19
+ component.aggregated_status_bools
20
+ end
21
+
22
+ message = RSMP::AggregatedStatus.new({
23
+ 'aSTS' => clock.to_s,
24
+ 'cId' => component.c_id,
25
+ 'fP' => nil,
26
+ 'fS' => nil,
27
+ 'se' => se,
28
+ 'mId' => m_id
29
+ })
30
+
31
+ apply_nts_message_attributes message
32
+ send_and_optionally_collect message, options do |collect_options|
33
+ Collector.new self, collect_options.merge(task: @task, type: 'MessageAck')
34
+ end
35
+ end
36
+
37
+ def process_aggregated_status_request(message)
38
+ log "Received #{message.type}", message: message, level: :log
39
+ component_id = message.attributes['cId']
40
+ component = @site.find_component component_id
41
+ acknowledge message
42
+ send_aggregated_status component
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end