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,516 +0,0 @@
1
- # Handles a site connection to a remote supervisor
2
-
3
- require 'digest'
4
-
5
- module RSMP
6
- class SupervisorProxy < Proxy
7
-
8
- attr_reader :supervisor_id, :site
9
-
10
- def initialize options
11
- super options.merge(node:options[:site])
12
- @site = options[:site]
13
- @site_settings = @site.site_settings.clone
14
- @ip = options[:ip]
15
- @port = options[:port]
16
- @status_subscriptions = {}
17
- @sxl = @site_settings['sxl']
18
- @synthetic_id = Supervisor.build_id_from_ip_port @ip, @port
19
- end
20
-
21
- # handle communication
22
- # if disconnected, then try to reconnect
23
- def run
24
- loop do
25
- connect
26
- start_reader
27
- start_handshake
28
- wait_for_reader # run until disconnected
29
- break if reconnect_delay == false
30
- rescue Restart
31
- @logger.mute @ip, @port
32
- raise
33
- rescue RSMP::ConnectionError => e
34
- log e, level: :error
35
- break if reconnect_delay == false
36
- rescue StandardError => e
37
- distribute_error e, level: :internal
38
- break if reconnect_delay == false
39
- ensure
40
- close
41
- stop_subtasks
42
- end
43
- end
44
-
45
- def start_handshake
46
- send_version @site_settings['site_id'], core_versions
47
- end
48
-
49
- # connect to the supervisor and initiate handshake supervisor
50
- def connect
51
- log "Connecting to supervisor at #{@ip}:#{@port}", level: :info
52
- set_state :connecting
53
- connect_tcp
54
- @logger.unmute @ip, @port
55
- log "Connected to supervisor at #{@ip}:#{@port}", level: :info
56
- rescue SystemCallError => e
57
- raise ConnectionError.new "Could not connect to supervisor at #{@ip}:#{@port}: Errno #{e.errno} #{e}"
58
- rescue StandardError => e
59
- raise ConnectionError.new "Error while connecting to supervisor at #{@ip}:#{@port}: #{e}"
60
- end
61
-
62
- def stop_task
63
- super
64
- @last_status_sent = nil
65
- end
66
-
67
- def connect_tcp
68
- @endpoint = Async::IO::Endpoint.tcp(@ip, @port)
69
-
70
- error = nil
71
- # Async::IO::Endpoint#connect renames the current task. run in a subtask to avoid this see issue #22
72
- result = @task.async do |task|
73
- task.annotate 'socket task'
74
- # this timeout is a workaround for connect hanging on windows if the other side is not present yet
75
- timeout = @site_settings.dig('timeouts','connect') || 1.1
76
- task.with_timeout timeout do
77
- @socket = @endpoint.connect
78
- end
79
- delay = @site_settings.dig('intervals','after_connect')
80
- task.sleep delay if delay
81
- rescue Errno::ECONNREFUSED => e # rescue to avoid log output
82
- log "Connection refused", level: :warning
83
- error = e
84
- end.wait
85
- raise error if error # reraise any error outside task
86
-
87
- @stream = Async::IO::Stream.new(@socket)
88
- @protocol = Async::IO::Protocol::Line.new(@stream,WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
89
- set_state :connected
90
- end
91
-
92
- def handshake_complete
93
- sanitized_sxl_version = RSMP::Schema.sanitize_version(sxl_version)
94
- log "Connection to supervisor established, using core #{@core_version}, #{sxl} #{sanitized_sxl_version}", level: :info
95
- set_state :ready
96
- start_watchdog
97
- if @site_settings['send_after_connect']
98
- send_all_aggregated_status
99
- send_active_alarms
100
- end
101
- super
102
- end
103
-
104
- def process_message message
105
- case message
106
- when StatusResponse, StatusUpdate, AggregatedStatus, AlarmIssue
107
- will_not_handle message
108
- when AggregatedStatusRequest
109
- process_aggregated_status_request message
110
- when CommandRequest
111
- process_command_request message
112
- when CommandResponse
113
- process_command_response message
114
- when StatusRequest
115
- process_status_request message
116
- when StatusSubscribe
117
- process_status_subcribe message
118
- when StatusUnsubscribe
119
- process_status_unsubcribe message
120
- when Alarm, AlarmAcknowledged, AlarmSuspend, AlarmResume, AlarmRequest
121
- process_alarm message
122
- else
123
- super message
124
- end
125
- rescue UnknownComponent, UnknownCommand, UnknownStatus,
126
- MessageRejected, MissingAttribute => e
127
- dont_acknowledge message, '', e.to_s
128
- end
129
-
130
- def acknowledged_first_ingoing message
131
- case message.type
132
- when "Watchdog"
133
- handshake_complete
134
- end
135
- end
136
-
137
- def send_all_aggregated_status
138
- @site.components.each_pair do |c_id,component|
139
- if component.grouped
140
- send_aggregated_status component
141
- end
142
- end
143
- end
144
-
145
- def send_active_alarms
146
- @site.components.each_pair do |c_id,component|
147
- component.alarms.each_pair do |alarm_code, alarm_state|
148
- if alarm_state.active
149
- alarm = AlarmIssue.new( alarm_state.to_hash.merge('aSp' => 'Issue') )
150
- send_message alarm
151
- end
152
- end
153
- end
154
- end
155
-
156
- def reconnect_delay
157
- return false if @site_settings['intervals']['reconnect'] == :no
158
- interval = @site_settings['intervals']['reconnect']
159
- log "Will try to reconnect again every #{interval} seconds...", level: :info
160
- @logger.mute @ip, @port
161
- @task.sleep interval
162
- true
163
- end
164
-
165
- def version_accepted message
166
- log "Received Version message, using RSMP #{@core_version}", message: message, level: :log
167
- start_timer
168
- acknowledge message
169
- @version_determined = true
170
- send_watchdog
171
- end
172
-
173
- def send_aggregated_status component, options={}
174
- m_id = options[:m_id] || RSMP::Message.make_m_id
175
-
176
- # For core <=3.1.2, se items must be send as strings
177
- # For core > 3.1.2, se items must be send as booleans
178
- if Proxy::version_meets_requirement?(core_version,"<=3.1.2")
179
- se = component.aggregated_status_bools.map {|bool| bool ? "true" : "false"}
180
- else
181
- se = component.aggregated_status_bools
182
- end
183
-
184
- message = AggregatedStatus.new({
185
- "aSTS" => clock.to_s,
186
- "cId" => component.c_id,
187
- "fP" => nil,
188
- "fS" => nil,
189
- "se" => se,
190
- "mId" => m_id,
191
- })
192
-
193
-
194
- set_nts_message_attributes message
195
- send_and_optionally_collect message, options do |collect_options|
196
- Collector.new self, collect_options.merge(task:@task, type: 'MessageAck')
197
- end
198
- end
199
-
200
- def send_alarm component, alarm, options={}
201
- send_and_optionally_collect alarm, options do |collect_options|
202
- Collector.new self, collect_options.merge(task:@task, type: 'MessageAck')
203
- end
204
- end
205
-
206
- def process_aggregated_status message
207
- se = message.attribute("se")
208
- validate_aggregated_status(message,se)
209
- on = set_aggregated_status se
210
- log "Received #{message.type} status [#{on.join(', ')}]", message: message, level: :log
211
- acknowledge message
212
- end
213
-
214
- def process_alarm message
215
- case message
216
- when AlarmAcknowledge
217
- handle_alarm_acknowledge message
218
- when AlarmSuspend
219
- handle_alarm_suspend message
220
- when AlarmResume
221
- handle_alarm_resume message
222
- when AlarmRequest
223
- handle_alarm_request message
224
- else
225
- dont_acknowledge message, "Invalid alarm message type"
226
- end
227
- end
228
-
229
- # handle incoming alarm acknowledge
230
- def handle_alarm_acknowledge message
231
- component_id = message.attributes["cId"]
232
- component = @site.find_component component_id
233
- alarm_code = message.attribute("aCId")
234
- log "Received #{message.type} #{alarm_code} acknowledgement", message: message, level: :log
235
- acknowledge message
236
- component.acknowledge_alarm alarm_code
237
- end
238
-
239
- # handle incoming alarm suspend
240
- def handle_alarm_suspend message
241
- component_id = message.attributes["cId"]
242
- component = @site.find_component component_id
243
- alarm_code = message.attribute("aCId")
244
- log "Received #{message.type} #{alarm_code} suspend", message: message, level: :log
245
- acknowledge message
246
- component.suspend_alarm alarm_code
247
- end
248
-
249
- # handle incoming alarm resume
250
- def handle_alarm_resume message
251
- component_id = message.attributes["cId"]
252
- component = @site.find_component component_id
253
- alarm_code = message.attribute("aCId")
254
- log "Received #{message.type} #{alarm_code} resume", message: message, level: :log
255
- acknowledge message
256
- component.resume_alarm alarm_code
257
- end
258
- # reorganize rmsp command request arg attribute:
259
- # [{"cCI":"M0002","cO":"setPlan","n":"status","v":"True"},{"cCI":"M0002","cO":"setPlan","n":"securityCode","v":"5678"},{"cCI":"M0002","cO":"setPlan","n":"timeplan","v":"3"}]
260
- # into the simpler, but equivalent:
261
- # {"M0002"=>{"status"=>"True", "securityCode"=>"5678", "timeplan"=>"3"}}
262
- def simplify_command_requests arg
263
- sorted = {}
264
- arg.each do |item|
265
- sorted[item['cCI']] ||= {}
266
- sorted[item['cCI']][item['n']] = item['v']
267
- end
268
- sorted
269
- end
270
-
271
- def process_aggregated_status_request message
272
- log "Received #{message.type}", message: message, level: :log
273
- component_id = message.attributes["cId"]
274
- component = @site.find_component component_id
275
- acknowledge message
276
- send_aggregated_status component
277
- end
278
-
279
- def process_command_request message
280
- component_id = message.attributes["cId"]
281
-
282
- rvs = message.attributes["arg"].map do |item|
283
- item = item.dup.merge('age'=>'recent')
284
- item.delete 'cO'
285
- item
286
- end
287
-
288
- begin
289
- component = @site.find_component component_id
290
- commands = simplify_command_requests message.attributes["arg"]
291
- commands.each_pair do |command_code,arg|
292
- component.handle_command command_code,arg
293
- end
294
- log "Received #{message.type}", message: message, level: :log
295
- rescue UnknownComponent
296
- log "Received #{message.type} with unknown component id '#{component_id}' and cannot infer type", message: message, level: :warning
297
- # If the component is unknown, we must set age=undefined for all items,
298
- # while still acknowledge the message.
299
- # See https://github.com/rsmp-nordic/rsmp_validator/issues/271
300
- rvs.map do |item|
301
- item['age'] = 'undefined'
302
- end
303
- end
304
-
305
- response = CommandResponse.new({
306
- "cId"=>component_id,
307
- "cTS"=>clock.to_s,
308
- "rvs"=>rvs
309
- })
310
- set_nts_message_attributes response
311
- acknowledge message
312
- send_message response
313
- end
314
-
315
- def rsmpify_value v, q
316
- if v.is_a?(Array) || v.is_a?(Set)
317
- v
318
- elsif ['undefined','unknown'].include?(q.to_s)
319
- nil
320
- else
321
- v.to_s
322
- end
323
- end
324
-
325
- def process_status_request message, options={}
326
- sS = []
327
- begin
328
- component_id = message.attributes["cId"]
329
- component = @site.find_component component_id
330
- sS = message.attributes["sS"].map do |arg|
331
- value, quality = component.get_status arg['sCI'], arg['n'], {sxl_version: sxl_version}
332
- { "s" => rsmpify_value(value, quality), "q" => quality.to_s }.merge arg
333
- end
334
- log "Received #{message.type}", message: message, level: :log
335
-
336
- rescue UnknownComponent
337
- log "Received #{message.type} with unknown component id '#{component_id}' and cannot infer type", message: message, level: :warning
338
- # If the component is unknown, we must set q=undefined and s=nil for all items,
339
- # while still acknowledge the message.
340
- sS = message.attributes["sS"].map do |arg|
341
- arg.dup.merge('q'=>'undefined','s'=>nil)
342
- end
343
- end
344
-
345
- response = StatusResponse.new({
346
- "cId"=>component_id,
347
- "sTs"=>clock.to_s,
348
- "sS"=>sS,
349
- "mId" => options[:m_id]
350
- })
351
-
352
- set_nts_message_attributes response
353
- acknowledge message
354
- send_message response
355
- end
356
-
357
- def process_status_subcribe message
358
- log "Received #{message.type}", message: message, level: :log
359
-
360
- # @status_subscriptions is organized by component/code/name, for example:
361
- #
362
- # {"AA+BBCCC=DDDEE002"=>{"S001"=>["number"]}}
363
- #
364
- # This is done to make it easy to send a single status update
365
- # for each component, containing all the requested statuses
366
-
367
- update_list = {}
368
- component_id = message.attributes["cId"]
369
- @status_subscriptions[component_id] ||= {}
370
- update_list[component_id] ||= {}
371
- now = Time.now # internal timestamp
372
- subs = @status_subscriptions[component_id]
373
-
374
- message.attributes["sS"].each do |arg|
375
- sCI = arg["sCI"]
376
- subcription = {interval: arg["uRt"].to_i, last_sent_at: now}
377
- subs[sCI] ||= {}
378
- subs[sCI][arg["n"]] = subcription
379
- update_list[component_id][sCI] ||= []
380
- update_list[component_id][sCI] << arg["n"]
381
- end
382
- acknowledge message
383
- send_status_updates update_list # send status after subscribing is accepted
384
- end
385
-
386
- def get_status_subscribe_interval component_id, sCI, n
387
- @status_subscriptions.dig component_id, sCI, n
388
- end
389
-
390
- def process_status_unsubcribe message
391
- log "Received #{message.type}", message: message, level: :log
392
- component = message.attributes["cId"]
393
-
394
- subs = @status_subscriptions[component]
395
- if subs
396
- message.attributes["sS"].each do |arg|
397
- sCI = arg["sCI"]
398
- if subs[sCI]
399
- subs[sCI].delete arg["n"]
400
- subs.delete(sCI) if subs[sCI].empty?
401
- end
402
- end
403
- @status_subscriptions.delete(component) if subs.empty?
404
- end
405
- acknowledge message
406
- end
407
-
408
- def timer now
409
- super
410
- status_update_timer now if ready?
411
- end
412
-
413
- def fetch_last_sent_status component, code, name
414
- @last_status_sent.dig component, code, name if @last_status_sent
415
- end
416
-
417
- def store_last_sent_status message
418
- component_id = message.attribute('cId')
419
- @last_status_sent ||= {}
420
- @last_status_sent[component_id] ||= {}
421
- message.attribute('sS').each do |item|
422
- sCI, n, s = item['sCI'], item['n'], item['s']
423
- @last_status_sent[component_id][sCI] ||= {}
424
- @last_status_sent[component_id][sCI][n] = s
425
- end
426
- end
427
-
428
- def status_update_timer now
429
- update_list = {}
430
- # go through subscriptons and build a similarly organized list,
431
- # that only contains what should be send
432
-
433
- @status_subscriptions.each_pair do |component,by_code|
434
- component_object = @site.find_component component
435
- by_code.each_pair do |code,by_name|
436
- by_name.each_pair do |name,subscription|
437
- current = nil
438
- should_send = false
439
- if subscription[:interval] == 0
440
- # send as soon as the data changes
441
- if component_object
442
- current, quality = *(component_object.get_status code, name)
443
- current = rsmpify_value(current,quality)
444
- end
445
- last_sent = fetch_last_sent_status component, code, name
446
- if current != last_sent
447
- should_send = true
448
- end
449
- else
450
- # send at regular intervals
451
- if subscription[:last_sent_at] == nil || (now - subscription[:last_sent_at]) >= subscription[:interval]
452
- should_send = true
453
- end
454
- end
455
- if should_send
456
- subscription[:last_sent_at] = now
457
- update_list[component] ||= {}
458
- update_list[component][code] ||= {}
459
- update_list[component][code][name] = current
460
- end
461
- end
462
- end
463
- end
464
- send_status_updates update_list
465
- end
466
-
467
- def send_status_updates update_list
468
- now = clock.to_s
469
- update_list.each_pair do |component_id,by_code|
470
- component = @site.find_component component_id
471
- sS = []
472
- by_code.each_pair do |code,names|
473
- names.map do |status_name,value|
474
- if value
475
- quality = 'recent'
476
- else
477
- value,quality = component.get_status code, status_name
478
- end
479
- sS << { "sCI" => code,
480
- "n" => status_name,
481
- "s" => rsmpify_value(value, quality),
482
- "q" => quality }
483
- end
484
- end
485
- update = StatusUpdate.new({
486
- "cId"=>component_id,
487
- "sTs"=>now,
488
- "sS"=>sS
489
- })
490
- set_nts_message_attributes update
491
- send_message update
492
- store_last_sent_status update
493
- component.status_updates_sent
494
- end
495
- end
496
-
497
- def sxl_version
498
- @site_settings['sxl_version'].to_s
499
- end
500
-
501
- def process_version message
502
- return extraneous_version message if @version_determined
503
- check_core_version message
504
- check_sxl_version message
505
- @site_id = Supervisor.build_id_from_ip_port @ip, @port
506
- version_accepted message
507
- end
508
-
509
- def check_sxl_version message
510
- end
511
-
512
- def main
513
- @site.main
514
- end
515
- end
516
- end
@@ -1,134 +0,0 @@
1
- module RSMP
2
- module TLC
3
- # class that maintains the state of TLC inputs
4
- # indexing is 1-based since that's how the RSMP messages are specified
5
- class Inputs
6
- attr_reader :size
7
-
8
- def initialize size
9
- @size = size
10
- reset
11
- end
12
-
13
- def reset
14
- string_size = @size+1
15
- @value = '0'*string_size
16
- @forced = '0'*string_size
17
- @forced_value = '0'*string_size
18
- @actual = '0'*string_size
19
- end
20
-
21
- def set input, value
22
- check_input input
23
- report_change(input) do
24
- @value[input] = to_digit value
25
- update_actual input
26
- end
27
- end
28
-
29
- def set_forcing input, force=true, forced_value=true
30
- check_input input
31
- report_change(input) do
32
- @forced[input] = to_digit force
33
- @forced_value[input] = to_digit forced_value
34
- update_actual input
35
- end
36
- end
37
-
38
- def force input, forced_value=true
39
- report_change(input) do
40
- set_forcing input, true, forced_value
41
- end
42
- end
43
-
44
- def release input
45
- report_change(input) do
46
- set_forcing input, false, false
47
- end
48
- end
49
-
50
- def value input
51
- check_input input
52
- from_digit @value[input]
53
- end
54
-
55
- def forced? input
56
- check_input input
57
- from_digit @forced[input]
58
- end
59
-
60
- def forced_value input
61
- check_input input
62
- from_digit @forced_value[input]
63
- end
64
-
65
- def actual input
66
- check_input input
67
- from_digit @actual[input]
68
- end
69
-
70
- def report input
71
- {
72
- value: value(input),
73
- forced: forced?(input),
74
- forced_value: forced_value(input),
75
- actual:actual(input)
76
- }
77
- end
78
-
79
- def value_string
80
- @value[1..-1]
81
- end
82
-
83
- def value_string
84
- @value[1..-1]
85
- end
86
-
87
- def forced_string
88
- @forced[1..-1]
89
- end
90
-
91
- def forced_value_string
92
- @forced[1..-1]
93
- end
94
-
95
- def actual_string
96
- @actual[1..-1]
97
- end
98
-
99
- protected
100
-
101
- def check_input input
102
- raise ArgumentError.new("Input index #{input} must be in the range 1-#{@size}") if input<1 || input>@size
103
- end
104
-
105
- def from_digit input
106
- input == '1'
107
- end
108
-
109
- def to_digit input
110
- input ? '1' : '0'
111
- end
112
-
113
- def update_actual input
114
- if from_digit @forced[input]
115
- @actual[input] = @forced_value[input]
116
- else
117
- @actual[input] = @value[input]
118
- end
119
- end
120
-
121
- def report_change input, &block
122
- before = @actual[input]
123
- yield
124
- if @actual[input] != before
125
- from_digit @actual[input]
126
- else
127
- nil
128
- end
129
- end
130
- end
131
- end
132
- end
133
-
134
-