rsmp 0.35.2 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/devcontainer.json +22 -0
  3. data/.github/copilot-instructions.md +33 -0
  4. data/.github/workflows/copilot-setup-steps.yml +35 -0
  5. data/.github/workflows/rspec.yaml +2 -1
  6. data/.github/workflows/rubocop.yaml +17 -0
  7. data/.gitignore +7 -7
  8. data/.rubocop.yml +80 -0
  9. data/Gemfile +13 -1
  10. data/Gemfile.lock +73 -35
  11. data/Rakefile +3 -3
  12. data/bin/console +2 -4
  13. data/documentation/tasks.md +4 -4
  14. data/lib/rsmp/cli.rb +147 -124
  15. data/lib/rsmp/collect/ack_collector.rb +8 -7
  16. data/lib/rsmp/collect/aggregated_status_collector.rb +4 -4
  17. data/lib/rsmp/collect/alarm_collector.rb +31 -23
  18. data/lib/rsmp/collect/alarm_matcher.rb +3 -3
  19. data/lib/rsmp/collect/collector/logging.rb +17 -0
  20. data/lib/rsmp/collect/collector/reporting.rb +44 -0
  21. data/lib/rsmp/collect/collector/status.rb +34 -0
  22. data/lib/rsmp/collect/collector.rb +69 -150
  23. data/lib/rsmp/collect/command_matcher.rb +19 -6
  24. data/lib/rsmp/collect/command_response_collector.rb +7 -7
  25. data/lib/rsmp/collect/distributor.rb +14 -11
  26. data/lib/rsmp/collect/filter.rb +31 -15
  27. data/lib/rsmp/collect/matcher.rb +7 -11
  28. data/lib/rsmp/collect/queue.rb +4 -4
  29. data/lib/rsmp/collect/receiver.rb +10 -12
  30. data/lib/rsmp/collect/state_collector.rb +116 -77
  31. data/lib/rsmp/collect/status_collector.rb +6 -6
  32. data/lib/rsmp/collect/status_matcher.rb +17 -7
  33. data/lib/rsmp/{alarm_state.rb → component/alarm_state.rb} +76 -37
  34. data/lib/rsmp/{component.rb → component/component.rb} +15 -15
  35. data/lib/rsmp/component/component_base.rb +89 -0
  36. data/lib/rsmp/component/component_proxy.rb +75 -0
  37. data/lib/rsmp/component/components.rb +63 -0
  38. data/lib/rsmp/convert/export/json_schema.rb +116 -110
  39. data/lib/rsmp/convert/import/yaml.rb +21 -18
  40. data/lib/rsmp/{rsmp.rb → helpers/clock.rb} +5 -6
  41. data/lib/rsmp/{deep_merge.rb → helpers/deep_merge.rb} +2 -1
  42. data/lib/rsmp/helpers/error.rb +71 -0
  43. data/lib/rsmp/{inspect.rb → helpers/inspect.rb} +6 -10
  44. data/lib/rsmp/log/archive.rb +98 -0
  45. data/lib/rsmp/log/colorization.rb +41 -0
  46. data/lib/rsmp/log/filtering.rb +54 -0
  47. data/lib/rsmp/log/logger.rb +206 -0
  48. data/lib/rsmp/{logging.rb → log/logging.rb} +5 -7
  49. data/lib/rsmp/message.rb +159 -148
  50. data/lib/rsmp/{node.rb → node/node.rb} +19 -17
  51. data/lib/rsmp/node/protocol.rb +37 -0
  52. data/lib/rsmp/node/site/site.rb +195 -0
  53. data/lib/rsmp/node/supervisor/modules/configuration.rb +59 -0
  54. data/lib/rsmp/node/supervisor/modules/connection.rb +140 -0
  55. data/lib/rsmp/node/supervisor/modules/sites.rb +64 -0
  56. data/lib/rsmp/node/supervisor/supervisor.rb +72 -0
  57. data/lib/rsmp/{task.rb → node/task.rb} +29 -19
  58. data/lib/rsmp/proxy/modules/acknowledgements.rb +144 -0
  59. data/lib/rsmp/proxy/modules/receive.rb +119 -0
  60. data/lib/rsmp/proxy/modules/send.rb +76 -0
  61. data/lib/rsmp/proxy/modules/state.rb +25 -0
  62. data/lib/rsmp/proxy/modules/tasks.rb +105 -0
  63. data/lib/rsmp/proxy/modules/versions.rb +69 -0
  64. data/lib/rsmp/proxy/modules/watchdogs.rb +66 -0
  65. data/lib/rsmp/proxy/proxy.rb +199 -0
  66. data/lib/rsmp/proxy/site/modules/aggregated_status.rb +52 -0
  67. data/lib/rsmp/proxy/site/modules/alarms.rb +27 -0
  68. data/lib/rsmp/proxy/site/modules/commands.rb +31 -0
  69. data/lib/rsmp/proxy/site/modules/status.rb +110 -0
  70. data/lib/rsmp/proxy/site/site_proxy.rb +205 -0
  71. data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +47 -0
  72. data/lib/rsmp/proxy/supervisor/modules/alarms.rb +73 -0
  73. data/lib/rsmp/proxy/supervisor/modules/commands.rb +53 -0
  74. data/lib/rsmp/proxy/supervisor/modules/status.rb +204 -0
  75. data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +178 -0
  76. data/lib/rsmp/tlc/detector_logic.rb +18 -34
  77. data/lib/rsmp/tlc/input_states.rb +126 -0
  78. data/lib/rsmp/tlc/modules/detector_logics.rb +50 -0
  79. data/lib/rsmp/tlc/modules/display.rb +78 -0
  80. data/lib/rsmp/tlc/modules/helpers.rb +41 -0
  81. data/lib/rsmp/tlc/modules/inputs.rb +173 -0
  82. data/lib/rsmp/tlc/modules/modes.rb +253 -0
  83. data/lib/rsmp/tlc/modules/outputs.rb +30 -0
  84. data/lib/rsmp/tlc/modules/plans.rb +218 -0
  85. data/lib/rsmp/tlc/modules/signal_groups.rb +109 -0
  86. data/lib/rsmp/tlc/modules/startup_sequence.rb +22 -0
  87. data/lib/rsmp/tlc/modules/system.rb +140 -0
  88. data/lib/rsmp/tlc/modules/traffic_data.rb +49 -0
  89. data/lib/rsmp/tlc/signal_group.rb +37 -41
  90. data/lib/rsmp/tlc/signal_plan.rb +14 -11
  91. data/lib/rsmp/tlc/signal_priority.rb +39 -35
  92. data/lib/rsmp/tlc/startup_sequence.rb +59 -0
  93. data/lib/rsmp/tlc/traffic_controller.rb +39 -1006
  94. data/lib/rsmp/tlc/traffic_controller_site.rb +58 -57
  95. data/lib/rsmp/version.rb +1 -1
  96. data/lib/rsmp.rb +86 -49
  97. data/rsmp.gemspec +24 -30
  98. metadata +87 -130
  99. data/lib/rsmp/archive.rb +0 -76
  100. data/lib/rsmp/collect/message_matchers.rb +0 -0
  101. data/lib/rsmp/component_base.rb +0 -87
  102. data/lib/rsmp/component_proxy.rb +0 -57
  103. data/lib/rsmp/components.rb +0 -65
  104. data/lib/rsmp/error.rb +0 -71
  105. data/lib/rsmp/logger.rb +0 -216
  106. data/lib/rsmp/proxy.rb +0 -695
  107. data/lib/rsmp/site.rb +0 -188
  108. data/lib/rsmp/site_proxy.rb +0 -389
  109. data/lib/rsmp/supervisor.rb +0 -287
  110. data/lib/rsmp/supervisor_proxy.rb +0 -516
  111. data/lib/rsmp/tlc/inputs.rb +0 -134
@@ -1,287 +0,0 @@
1
- # RSMP supervisor (server)
2
- # The supervisor waits for sites to connect.
3
- # Connections to sites are handles via site proxies.
4
-
5
- module RSMP
6
- class Supervisor < Node
7
- attr_reader :core_version, :site_id, :supervisor_settings, :proxies, :logger
8
-
9
- def initialize options={}
10
- handle_supervisor_settings( options[:supervisor_settings] || {} )
11
- super options
12
- @proxies = []
13
- @site_id_condition = Async::Notification.new
14
- end
15
-
16
- def site_id
17
- @supervisor_settings['site_id']
18
- end
19
-
20
- def handle_supervisor_settings supervisor_settings
21
- defaults = {
22
- 'port' => 12111,
23
- 'ips' => 'all',
24
- 'guest' => {
25
- 'sxl' => 'tlc',
26
- 'intervals' => {
27
- 'timer' => 1,
28
- 'watchdog' => 1
29
- },
30
- 'timeouts' => {
31
- 'watchdog' => 2,
32
- 'acknowledgement' => 2
33
- }
34
- }
35
- }
36
-
37
- # merge options into defaults
38
- @supervisor_settings = defaults.deep_merge(supervisor_settings)
39
- @core_version = @supervisor_settings["guest"]["core_version"]
40
- check_site_sxl_types
41
- end
42
-
43
- def check_site_sxl_types
44
- sites = @supervisor_settings['sites'].clone || {}
45
- sites['guest'] = @supervisor_settings['guest']
46
- sites.each do |site_id,settings|
47
- unless settings
48
- raise RSMP::ConfigurationError.new("Configuration for site '#{site_id}' is empty")
49
- end
50
- sxl = settings['sxl']
51
- unless sxl
52
- raise RSMP::ConfigurationError.new("Configuration error for site '#{site_id}': No SXL specified")
53
- end
54
- RSMP::Schema.find_schemas! sxl if sxl
55
- rescue RSMP::Schema::UnknownSchemaError => e
56
- raise RSMP::ConfigurationError.new("Configuration error for site '#{site_id}': #{e}")
57
- end
58
- end
59
-
60
- # listen for connections
61
- # Async::IO::Endpoint#accept createa an async task that we will wait for
62
- def run
63
- log "Starting supervisor on port #{@supervisor_settings["port"]}",
64
- level: :info,
65
- timestamp: @clock.now
66
-
67
- @endpoint = Async::IO::Endpoint.tcp('0.0.0.0', @supervisor_settings["port"])
68
- tasks = @endpoint.accept do |socket| # creates async tasks
69
- handle_connection(socket)
70
- rescue StandardError => e
71
- distribute_error e, level: :internal
72
- end
73
- tasks.each { |task| task.wait }
74
- rescue StandardError => e
75
- distribute_error e, level: :internal
76
- end
77
-
78
- # stop
79
- def stop
80
- log "Stopping supervisor #{@supervisor_settings["site_id"]}", level: :info
81
- super
82
- end
83
-
84
- # handle an incoming connction by either accepting of rejecting it
85
- def handle_connection socket
86
- remote_port = socket.remote_address.ip_port
87
- remote_hostname = socket.remote_address.ip_address
88
- remote_ip = socket.remote_address.ip_address
89
-
90
- info = {ip:remote_ip, port:remote_port, hostname:remote_hostname, now:Clock.now}
91
- if accept? socket, info
92
- accept_connection socket, info
93
- else
94
- reject_connection socket, info
95
- end
96
- rescue ConnectionError, HandshakeError => e
97
- log "Rejected connection from #{remote_ip}:#{remote_port}, #{e.to_s}", level: :warning
98
- distribute_error e
99
- rescue StandardError => e
100
- log "Connection: #{e.to_s}", exception: e, level: :error
101
- distribute_error e, level: :internal
102
- ensure
103
- close socket, info
104
- end
105
-
106
- def accept? socket, info
107
- true
108
- end
109
-
110
- def build_proxy settings
111
- SiteProxy.new settings
112
- end
113
-
114
- def format_ip_and_port info
115
- if @logger.settings['hide_ip_and_port']
116
- '********'
117
- else
118
- "#{info[:ip]}:#{info[:port]}"
119
- end
120
- end
121
-
122
- def authorize_ip ip
123
- return if @supervisor_settings['ips'] == 'all'
124
- return if @supervisor_settings['ips'].include? ip
125
- raise ConnectionError.new('guest ip not allowed')
126
- end
127
-
128
- def check_max_sites
129
- max = @supervisor_settings['max_sites']
130
- if max
131
- if @proxies.size >= max
132
- raise ConnectionError.new("maximum of #{max} sites already connected")
133
- end
134
- end
135
- end
136
-
137
- def peek_version_message protocol
138
- json = protocol.peek_line
139
- attributes = Message.parse_attributes json
140
- Message.build attributes, json
141
- end
142
-
143
- # accept an incoming connecting by creating and starting a proxy
144
- def accept_connection socket, info
145
- log "Site connected from #{format_ip_and_port(info)}",
146
- ip: info[:ip],
147
- port: info[:port],
148
- level: :info,
149
- timestamp: Clock.now
150
-
151
- authorize_ip info[:ip]
152
-
153
- stream = Async::IO::Stream.new socket
154
- protocol = Async::IO::Protocol::Line.new stream, Proxy::WRAPPING_DELIMITER
155
-
156
- settings = {
157
- supervisor: self,
158
- ip: info[:ip],
159
- port: info[:port],
160
- task: @task,
161
- collect: @collect,
162
- socket: socket,
163
- stream: stream,
164
- protocol: protocol,
165
- info: info,
166
- logger: @logger,
167
- archive: @archive
168
- }
169
-
170
- version_message = peek_version_message protocol
171
- id = version_message.attribute('siteId').first['sId']
172
-
173
- proxy = find_site id
174
- if proxy
175
- if proxy.connected?
176
- raise ConnectionError.new("Site #{id} alredy connected from port #{proxy.port}")
177
- else
178
- proxy.revive settings
179
- end
180
- else
181
- check_max_sites
182
- proxy = build_proxy settings.merge(site_id:id) # keep the id learned by peeking above
183
- @proxies.push proxy
184
- end
185
-
186
- proxy.setup_site_settings
187
- proxy.check_core_version version_message
188
- log "Validating using core version #{proxy.core_version}", level: :debug
189
-
190
- proxy.start # will run until the site disconnects
191
- proxy.wait
192
- ensure
193
- site_ids_changed
194
- stop if @supervisor_settings['one_shot']
195
- end
196
-
197
- def site_ids_changed
198
- @site_id_condition.signal
199
- end
200
-
201
- def reject_connection socket, info
202
- log "Site rejected", ip: info[:ip], level: :info
203
- end
204
-
205
- def close socket, info
206
- if info
207
- log "Connection to #{format_ip_and_port(info)} closed", ip: info[:ip], level: :info, timestamp: Clock.now
208
- else
209
- log "Connection closed", level: :info, timestamp: Clock.now
210
- end
211
-
212
- socket.close
213
- end
214
-
215
- def site_connected? site_id
216
- return find_site(site_id) != nil
217
- end
218
-
219
- def find_site_from_ip_port ip, port
220
- @proxies.each do |site|
221
- return site if site.ip == ip && site.port == port
222
- end
223
- nil
224
- end
225
-
226
- def find_site site_id
227
- @proxies.each do |site|
228
- return site if site_id == :any || site.site_id == site_id
229
- end
230
- nil
231
- end
232
-
233
- def wait_for_site site_id, timeout:
234
- site = find_site site_id
235
- return site if site
236
- wait_for_condition(@site_id_condition,timeout:timeout) do
237
- find_site site_id
238
- end
239
-
240
- rescue Async::TimeoutError
241
- if site_id == :any
242
- str = "No site connected"
243
- else
244
- str = "Site '#{site_id}' did not connect"
245
- end
246
- raise RSMP::TimeoutError.new "#{str} within #{timeout}s"
247
- end
248
-
249
- def wait_for_site_disconnect site_id, timeout:
250
- wait_for_condition(@site_id_condition,timeout:timeout) { true unless find_site site_id }
251
- rescue Async::TimeoutError
252
- raise RSMP::TimeoutError.new "Site '#{site_id}' did not disconnect within #{timeout}s"
253
- end
254
-
255
- def check_site_id site_id
256
- #check_site_already_connected site_id
257
- return site_id_to_site_setting site_id
258
- end
259
-
260
- def check_site_already_connected site_id
261
- site = find_site(site_id)
262
- raise HandshakeError.new "Site '#{site_id}' already connected" if site != nil && site != self
263
- end
264
-
265
- def site_id_to_site_setting site_id
266
- return {} unless @supervisor_settings['sites']
267
- @supervisor_settings['sites'].each_pair do |id,settings|
268
- if id == 'guest' || id == site_id
269
- return settings
270
- end
271
- end
272
- raise HandshakeError.new "site id #{site_id} unknown"
273
- end
274
-
275
- def ip_to_site_settings ip
276
- @supervisor_settings['sites'][ip] || @supervisor_settings['sites']['guest']
277
- end
278
-
279
- def aggregated_status_changed site_proxy, component
280
- end
281
-
282
- def self.build_id_from_ip_port ip, port
283
- Digest::MD5.hexdigest("#{ip}:#{port}")[0..8]
284
- end
285
-
286
- end
287
- end