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.
- checksums.yaml +4 -4
- data/.devcontainer/devcontainer.json +22 -0
- data/.github/workflows/rubocop.yaml +17 -0
- data/.gitignore +5 -6
- data/.rubocop.yml +80 -0
- data/Gemfile +13 -1
- data/Gemfile.lock +34 -1
- data/Rakefile +3 -3
- data/lib/rsmp/cli.rb +147 -124
- data/lib/rsmp/collect/ack_collector.rb +8 -7
- data/lib/rsmp/collect/aggregated_status_collector.rb +4 -4
- data/lib/rsmp/collect/alarm_collector.rb +31 -23
- data/lib/rsmp/collect/alarm_matcher.rb +3 -3
- data/lib/rsmp/collect/collector/logging.rb +17 -0
- data/lib/rsmp/collect/collector/reporting.rb +44 -0
- data/lib/rsmp/collect/collector/status.rb +34 -0
- data/lib/rsmp/collect/collector.rb +69 -150
- data/lib/rsmp/collect/command_matcher.rb +19 -6
- data/lib/rsmp/collect/command_response_collector.rb +7 -7
- data/lib/rsmp/collect/distributor.rb +14 -11
- data/lib/rsmp/collect/filter.rb +31 -15
- data/lib/rsmp/collect/matcher.rb +7 -11
- data/lib/rsmp/collect/queue.rb +4 -4
- data/lib/rsmp/collect/receiver.rb +10 -12
- data/lib/rsmp/collect/state_collector.rb +116 -77
- data/lib/rsmp/collect/status_collector.rb +6 -6
- data/lib/rsmp/collect/status_matcher.rb +17 -7
- data/lib/rsmp/{alarm_state.rb → component/alarm_state.rb} +76 -37
- data/lib/rsmp/{component.rb → component/component.rb} +15 -15
- data/lib/rsmp/component/component_base.rb +89 -0
- data/lib/rsmp/component/component_proxy.rb +75 -0
- data/lib/rsmp/component/components.rb +63 -0
- data/lib/rsmp/convert/export/json_schema.rb +116 -110
- data/lib/rsmp/convert/import/yaml.rb +21 -18
- data/lib/rsmp/{rsmp.rb → helpers/clock.rb} +5 -6
- data/lib/rsmp/{deep_merge.rb → helpers/deep_merge.rb} +2 -1
- data/lib/rsmp/helpers/error.rb +71 -0
- data/lib/rsmp/{inspect.rb → helpers/inspect.rb} +6 -10
- data/lib/rsmp/log/archive.rb +98 -0
- data/lib/rsmp/log/colorization.rb +41 -0
- data/lib/rsmp/log/filtering.rb +54 -0
- data/lib/rsmp/log/logger.rb +206 -0
- data/lib/rsmp/{logging.rb → log/logging.rb} +5 -7
- data/lib/rsmp/message.rb +159 -148
- data/lib/rsmp/{node.rb → node/node.rb} +19 -17
- data/lib/rsmp/{protocol.rb → node/protocol.rb} +5 -3
- data/lib/rsmp/node/site/site.rb +195 -0
- data/lib/rsmp/node/supervisor/modules/configuration.rb +59 -0
- data/lib/rsmp/node/supervisor/modules/connection.rb +140 -0
- data/lib/rsmp/node/supervisor/modules/sites.rb +64 -0
- data/lib/rsmp/node/supervisor/supervisor.rb +72 -0
- data/lib/rsmp/{task.rb → node/task.rb} +12 -14
- data/lib/rsmp/proxy/modules/acknowledgements.rb +144 -0
- data/lib/rsmp/proxy/modules/receive.rb +119 -0
- data/lib/rsmp/proxy/modules/send.rb +76 -0
- data/lib/rsmp/proxy/modules/state.rb +25 -0
- data/lib/rsmp/proxy/modules/tasks.rb +105 -0
- data/lib/rsmp/proxy/modules/versions.rb +69 -0
- data/lib/rsmp/proxy/modules/watchdogs.rb +66 -0
- data/lib/rsmp/proxy/proxy.rb +199 -0
- data/lib/rsmp/proxy/site/modules/aggregated_status.rb +52 -0
- data/lib/rsmp/proxy/site/modules/alarms.rb +27 -0
- data/lib/rsmp/proxy/site/modules/commands.rb +31 -0
- data/lib/rsmp/proxy/site/modules/status.rb +110 -0
- data/lib/rsmp/proxy/site/site_proxy.rb +205 -0
- data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +47 -0
- data/lib/rsmp/proxy/supervisor/modules/alarms.rb +73 -0
- data/lib/rsmp/proxy/supervisor/modules/commands.rb +53 -0
- data/lib/rsmp/proxy/supervisor/modules/status.rb +204 -0
- data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +178 -0
- data/lib/rsmp/tlc/detector_logic.rb +18 -34
- data/lib/rsmp/tlc/input_states.rb +126 -0
- data/lib/rsmp/tlc/modules/detector_logics.rb +50 -0
- data/lib/rsmp/tlc/modules/display.rb +78 -0
- data/lib/rsmp/tlc/modules/helpers.rb +41 -0
- data/lib/rsmp/tlc/modules/inputs.rb +173 -0
- data/lib/rsmp/tlc/modules/modes.rb +253 -0
- data/lib/rsmp/tlc/modules/outputs.rb +30 -0
- data/lib/rsmp/tlc/modules/plans.rb +218 -0
- data/lib/rsmp/tlc/modules/signal_groups.rb +109 -0
- data/lib/rsmp/tlc/modules/startup_sequence.rb +22 -0
- data/lib/rsmp/tlc/modules/system.rb +140 -0
- data/lib/rsmp/tlc/modules/traffic_data.rb +49 -0
- data/lib/rsmp/tlc/signal_group.rb +37 -41
- data/lib/rsmp/tlc/signal_plan.rb +14 -11
- data/lib/rsmp/tlc/signal_priority.rb +39 -35
- data/lib/rsmp/tlc/startup_sequence.rb +59 -0
- data/lib/rsmp/tlc/traffic_controller.rb +38 -1010
- data/lib/rsmp/tlc/traffic_controller_site.rb +58 -57
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +82 -48
- data/rsmp.gemspec +24 -31
- metadata +79 -139
- data/lib/rsmp/archive.rb +0 -76
- data/lib/rsmp/collect/message_matchers.rb +0 -0
- data/lib/rsmp/component_base.rb +0 -87
- data/lib/rsmp/component_proxy.rb +0 -57
- data/lib/rsmp/components.rb +0 -65
- data/lib/rsmp/error.rb +0 -71
- data/lib/rsmp/logger.rb +0 -216
- data/lib/rsmp/proxy.rb +0 -693
- data/lib/rsmp/site.rb +0 -188
- data/lib/rsmp/site_proxy.rb +0 -389
- data/lib/rsmp/supervisor.rb +0 -302
- data/lib/rsmp/supervisor_proxy.rb +0 -510
- data/lib/rsmp/tlc/inputs.rb +0 -134
|
@@ -0,0 +1,126 @@
|
|
|
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 InputStates
|
|
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, force: true, forced_value: forced_value
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def release(input)
|
|
45
|
+
report_change(input) do
|
|
46
|
+
set_forcing input, force: false, forced_value: 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..]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def forced_string
|
|
84
|
+
@forced[1..]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def forced_value_string
|
|
88
|
+
@forced[1..]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def actual_string
|
|
92
|
+
@actual[1..]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
protected
|
|
96
|
+
|
|
97
|
+
def check_input(input)
|
|
98
|
+
raise ArgumentError, "Input index #{input} must be in the range 1-#{@size}" if input < 1 || input > @size
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def from_digit?(input)
|
|
102
|
+
input == '1'
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def to_digit(input)
|
|
106
|
+
input ? '1' : '0'
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def update_actual(input)
|
|
110
|
+
@actual[input] = if from_digit? @forced[input]
|
|
111
|
+
@forced_value[input]
|
|
112
|
+
else
|
|
113
|
+
@value[input]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def report_change(input)
|
|
118
|
+
before = @actual[input]
|
|
119
|
+
yield
|
|
120
|
+
return unless @actual[input] != before
|
|
121
|
+
|
|
122
|
+
from_digit? @actual[input]
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
module TLC
|
|
3
|
+
module Modules
|
|
4
|
+
# Detector logic management for traffic controller
|
|
5
|
+
# Handles detector logic status queries and forcing
|
|
6
|
+
module DetectorLogics
|
|
7
|
+
def add_detector_logic(logic)
|
|
8
|
+
@detector_logics << logic
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# M0021 - Force detector logic
|
|
12
|
+
def handle_m0021(arg, _options = {})
|
|
13
|
+
@node.verify_security_code 2, arg['securityCode']
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# S0002 - Detector logic status
|
|
17
|
+
def handle_s0002(_status_code, status_name = nil, _options = {})
|
|
18
|
+
case status_name
|
|
19
|
+
when 'detectorlogicstatus'
|
|
20
|
+
TrafficControllerSite.make_status @detector_logics.map { |dl| bool_to_digit(dl.value) }.join
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# S0016 - Number of detector logics
|
|
25
|
+
def handle_s0016(_status_code, status_name = nil, _options = {})
|
|
26
|
+
case status_name
|
|
27
|
+
when 'number'
|
|
28
|
+
TrafficControllerSite.make_status @detector_logics.size
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# S0021 - Detector logic forcing status
|
|
33
|
+
def handle_s0021(_status_code, status_name = nil, _options = {})
|
|
34
|
+
case status_name
|
|
35
|
+
when 'detectorlogics'
|
|
36
|
+
TrafficControllerSite.make_status @detector_logics.map { |logic| bool_to_digit(logic.forced) }.join
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# S0031 - Trigger level sensitivity for loop detector
|
|
41
|
+
def handle_s0031(_status_code, status_name = nil, _options = {})
|
|
42
|
+
case status_name
|
|
43
|
+
when 'status'
|
|
44
|
+
TrafficControllerSite.make_status ''
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
module TLC
|
|
3
|
+
module Modules
|
|
4
|
+
# Display and output formatting
|
|
5
|
+
module Display
|
|
6
|
+
def output_states
|
|
7
|
+
return unless @live_output
|
|
8
|
+
|
|
9
|
+
str = format_colored_signal_states
|
|
10
|
+
modes = format_mode_indicators
|
|
11
|
+
plan = "P#{@plan}"
|
|
12
|
+
|
|
13
|
+
write_state_output(modes, plan, str)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def format_signal_group_status
|
|
17
|
+
@signal_groups.map(&:state).join
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def format_colored_signal_states
|
|
23
|
+
@signal_groups.map do |group|
|
|
24
|
+
state = group.state
|
|
25
|
+
s = "#{group.c_id}:#{state}"
|
|
26
|
+
colorize_signal_state(s, state)
|
|
27
|
+
end.join ' '
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def colorize_signal_state(display_string, state)
|
|
31
|
+
case state
|
|
32
|
+
when /^[1-9]$/
|
|
33
|
+
display_string.colorize(:green)
|
|
34
|
+
when /^[NOPf]$/
|
|
35
|
+
display_string.colorize(:yellow)
|
|
36
|
+
when /^[ae]$/
|
|
37
|
+
display_string.colorize(:light_black)
|
|
38
|
+
else # includes /^g$/ and any other values
|
|
39
|
+
display_string.colorize(:red)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def mode_indicators
|
|
44
|
+
{
|
|
45
|
+
0 => ['N', @function_position == 'NormalControl'],
|
|
46
|
+
1 => ['Y', @function_position == 'YellowFlash'],
|
|
47
|
+
2 => ['D', @function_position == 'Dark'],
|
|
48
|
+
3 => ['B', @booting],
|
|
49
|
+
4 => ['S', @startup_sequence.active?],
|
|
50
|
+
5 => ['M', @manual_control],
|
|
51
|
+
6 => ['F', @fixed_time_control],
|
|
52
|
+
7 => ['R', @all_red],
|
|
53
|
+
8 => ['I', @isolated_control],
|
|
54
|
+
9 => ['P', @police_key != 0]
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def format_mode_indicators
|
|
59
|
+
modes = '.' * 10
|
|
60
|
+
mode_indicators.each do |pos, (char, active)|
|
|
61
|
+
modes[pos] = char if active
|
|
62
|
+
end
|
|
63
|
+
modes
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def write_state_output(modes, plan, signal_states)
|
|
67
|
+
# create folders if needed
|
|
68
|
+
FileUtils.mkdir_p File.dirname(@live_output)
|
|
69
|
+
|
|
70
|
+
# append a line with the current state to the file
|
|
71
|
+
File.open @live_output, 'w' do |file|
|
|
72
|
+
file.puts "#{modes} #{plan.rjust(2)} #{@cycle_counter.to_s.rjust(3)} #{signal_states}\r"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
module TLC
|
|
3
|
+
module Modules
|
|
4
|
+
# Utility helper methods
|
|
5
|
+
module Helpers
|
|
6
|
+
def find_plan(plan_nr)
|
|
7
|
+
plan = @plans[plan_nr.to_i]
|
|
8
|
+
raise InvalidMessage, "unknown signal plan #{plan_nr}, known only [#{@plans.keys.join(', ')}]" unless plan
|
|
9
|
+
|
|
10
|
+
plan
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def string_to_bool(bool_str)
|
|
14
|
+
case bool_str
|
|
15
|
+
when 'True'
|
|
16
|
+
true
|
|
17
|
+
when 'False'
|
|
18
|
+
false
|
|
19
|
+
else
|
|
20
|
+
raise RSMP::MessageRejected, "Invalid boolean '#{bool}', must be 'True' or 'False'"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def bool_string_to_digit(bool)
|
|
25
|
+
case bool
|
|
26
|
+
when 'True'
|
|
27
|
+
'1'
|
|
28
|
+
when 'False'
|
|
29
|
+
'0'
|
|
30
|
+
else
|
|
31
|
+
raise RSMP::MessageRejected, "Invalid boolean '#{bool}', must be 'True' or 'False'"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def bool_to_digit(bool)
|
|
36
|
+
bool ? '1' : '0'
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
module TLC
|
|
3
|
+
module Modules
|
|
4
|
+
# Input programming, control, and status for traffic controllers
|
|
5
|
+
# Handles input commands and queries
|
|
6
|
+
module Inputs
|
|
7
|
+
def setup_inputs(inputs)
|
|
8
|
+
if inputs
|
|
9
|
+
num_inputs = inputs['total']
|
|
10
|
+
@input_programming = inputs['programming']
|
|
11
|
+
else
|
|
12
|
+
@input_programming = nil
|
|
13
|
+
end
|
|
14
|
+
@inputs = TLC::InputStates.new num_inputs || 8
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def input_logic(input, change)
|
|
18
|
+
return unless @input_programming && !change.nil?
|
|
19
|
+
|
|
20
|
+
action = @input_programming[input]
|
|
21
|
+
return unless action
|
|
22
|
+
|
|
23
|
+
return unless action['raise_alarm']
|
|
24
|
+
|
|
25
|
+
component = if action['component']
|
|
26
|
+
node.find_component action['component']
|
|
27
|
+
else
|
|
28
|
+
node.main
|
|
29
|
+
end
|
|
30
|
+
alarm_code = action['raise_alarm']
|
|
31
|
+
if change
|
|
32
|
+
log "Activating input #{input} is programmed to raise alarm #{alarm_code} on #{component.c_id}",
|
|
33
|
+
level: :info
|
|
34
|
+
component.activate_alarm alarm_code
|
|
35
|
+
else
|
|
36
|
+
log "Deactivating input #{input} is programmed to clear alarm #{alarm_code} on #{component.c_id}",
|
|
37
|
+
level: :info
|
|
38
|
+
component.deactivate_alarm alarm_code
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# M0006 - Set input
|
|
43
|
+
def handle_m0006(arg, _options = {})
|
|
44
|
+
@node.verify_security_code 2, arg['securityCode']
|
|
45
|
+
input = arg['input'].to_i
|
|
46
|
+
status = string_to_bool arg['status']
|
|
47
|
+
raise MessageRejected, "Input must be in the range 1-#{@inputs.size}" unless input.between?(1, @inputs.size)
|
|
48
|
+
|
|
49
|
+
if status
|
|
50
|
+
log "Activating input #{input}", level: :info
|
|
51
|
+
else
|
|
52
|
+
log "Deactivating input #{input}", level: :info
|
|
53
|
+
end
|
|
54
|
+
change = @inputs.set input, status
|
|
55
|
+
input_logic input, change unless change.nil?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# M0012 - Set input (simple)
|
|
59
|
+
def handle_m0012(arg, _options = {})
|
|
60
|
+
@node.verify_security_code 2, arg['securityCode']
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# M0013 - Set input (complex bit pattern)
|
|
64
|
+
def handle_m0013(arg, _options = {})
|
|
65
|
+
@node.verify_security_code 2, arg['securityCode']
|
|
66
|
+
set, clear = parse_input_status(arg['status'])
|
|
67
|
+
validate_input_ranges(set, clear)
|
|
68
|
+
apply_input_changes(set, clear)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Helper: Parse input status string into set and clear arrays
|
|
72
|
+
def parse_input_status(status_string)
|
|
73
|
+
set = []
|
|
74
|
+
clear = []
|
|
75
|
+
status_string.split(';').each do |part|
|
|
76
|
+
offset, set_bits, clear_bits = part.split(',').map(&:to_i)
|
|
77
|
+
extract_input_bits(set_bits, offset, set)
|
|
78
|
+
extract_input_bits(clear_bits, offset, clear)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
set = set.uniq.sort
|
|
82
|
+
clear = clear.uniq.sort
|
|
83
|
+
# if input is both activated and deactivated, there is no need to activate first
|
|
84
|
+
set -= (set & clear)
|
|
85
|
+
|
|
86
|
+
[set, clear]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Helper: Extract individual input bits from a bit pattern
|
|
90
|
+
def extract_input_bits(bits, offset, target_array)
|
|
91
|
+
bits.to_s(2).reverse.each_char.with_index do |bit, i|
|
|
92
|
+
target_array << (i + offset) if bit == '1'
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Helper: Validate that input indices are in valid range
|
|
97
|
+
def validate_input_ranges(set, clear)
|
|
98
|
+
[set, clear].each do |inputs|
|
|
99
|
+
inputs.each do |input|
|
|
100
|
+
if input < 1
|
|
101
|
+
raise MessageRejected,
|
|
102
|
+
"Cannot activate inputs #{set} and deactivate inputs #{clear}: " \
|
|
103
|
+
"input #{input} is invalid (must be 1 or higher)"
|
|
104
|
+
end
|
|
105
|
+
next unless input > @inputs.size
|
|
106
|
+
|
|
107
|
+
raise MessageRejected,
|
|
108
|
+
"Cannot activate inputs #{set} and deactivate inputs #{clear}: " \
|
|
109
|
+
"input #{input} is invalid (only #{@inputs.size} inputs present)"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Helper: Apply input changes (activate/deactivate)
|
|
115
|
+
def apply_input_changes(set, clear)
|
|
116
|
+
log "Activating inputs #{set} and deactivating inputs #{clear}", level: :info
|
|
117
|
+
|
|
118
|
+
set.each do |input|
|
|
119
|
+
change = @inputs.set input, true
|
|
120
|
+
input_logic input, change unless change.nil?
|
|
121
|
+
end
|
|
122
|
+
clear.each do |input|
|
|
123
|
+
change = @inputs.set input, false
|
|
124
|
+
input_logic input, change unless change.nil?
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Helper: Set a specific input value
|
|
129
|
+
def set_input(input_index, _value)
|
|
130
|
+
return unless input_index >= 0 && input_index < @num_inputs
|
|
131
|
+
|
|
132
|
+
@inputs[input_index] = bool_to_digit arg['value']
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# M0019 - Force input
|
|
136
|
+
def handle_m0019(arg, _options = {})
|
|
137
|
+
@node.verify_security_code 2, arg['securityCode']
|
|
138
|
+
input = arg['input'].to_i
|
|
139
|
+
force = string_to_bool arg['status']
|
|
140
|
+
forced_value = string_to_bool arg['inputValue']
|
|
141
|
+
raise MessageRejected, "Input must be in the range 1-#{@inputs.size}" unless input.between?(1, @inputs.size)
|
|
142
|
+
|
|
143
|
+
if force
|
|
144
|
+
log "Forcing input #{input} to #{forced_value}", level: :info
|
|
145
|
+
else
|
|
146
|
+
log "Releasing input #{input}", level: :info
|
|
147
|
+
end
|
|
148
|
+
change = @inputs.set_forcing input, force: force, forced_value: forced_value
|
|
149
|
+
|
|
150
|
+
input_logic input, change unless change.nil?
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# S0003 - Input status
|
|
154
|
+
def handle_s0003(_status_code, status_name = nil, _options = {})
|
|
155
|
+
case status_name
|
|
156
|
+
when 'inputstatus'
|
|
157
|
+
TrafficControllerSite.make_status @inputs.actual_string
|
|
158
|
+
when 'extendedinputstatus'
|
|
159
|
+
TrafficControllerSite.make_status 0.to_s
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# S0029 - Forced input status
|
|
164
|
+
def handle_s0029(_status_code, status_name = nil, _options = {})
|
|
165
|
+
case status_name
|
|
166
|
+
when 'status'
|
|
167
|
+
TrafficControllerSite.make_status @inputs.forced_string
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|