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