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