waterfurnace_aurora 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/aurora_fetch +25 -0
- data/bin/aurora_mock +89 -0
- data/bin/aurora_monitor +76 -0
- data/bin/aurora_mqtt_bridge +289 -0
- data/bin/registers.yml +3066 -0
- data/lib/aurora.rb +15 -0
- data/lib/aurora/abc_client.rb +82 -0
- data/lib/aurora/iz2_zone.rb +99 -0
- data/lib/aurora/modbus/server.rb +72 -0
- data/lib/aurora/modbus/slave.rb +52 -0
- data/lib/aurora/registers.rb +660 -0
- data/lib/aurora/version.rb +3 -0
- data/lib/waterfurnace_aurora.rb +1 -0
- metadata +154 -0
data/lib/aurora.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rmodbus'
|
2
|
+
|
3
|
+
require 'aurora/abc_client'
|
4
|
+
require 'aurora/iz2_zone'
|
5
|
+
require 'aurora/modbus/server'
|
6
|
+
require 'aurora/modbus/slave'
|
7
|
+
require 'aurora/registers'
|
8
|
+
|
9
|
+
# extend ModBus for WaterFurnace's custom functions
|
10
|
+
ModBus::RTUServer.include(Aurora::ModBus::Server)
|
11
|
+
ModBus::Client::Slave.prepend(Aurora::ModBus::Slave)
|
12
|
+
ModBus::RTUSlave.prepend(Aurora::ModBus::RTU)
|
13
|
+
|
14
|
+
module Aurora
|
15
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Aurora
|
2
|
+
class ABCClient
|
3
|
+
attr_reader :modbus_slave,
|
4
|
+
:iz2_zones,
|
5
|
+
:current_mode,
|
6
|
+
:fan_speed,
|
7
|
+
:entering_air_temperature,
|
8
|
+
:relative_humidity,
|
9
|
+
:leaving_air_temperature,
|
10
|
+
:leaving_water_temperature,
|
11
|
+
:entering_water_temperature,
|
12
|
+
:dhw_water_temperature,
|
13
|
+
:waterflow,
|
14
|
+
:compressor_speed,
|
15
|
+
:outdoor_temperature,
|
16
|
+
:fp1,
|
17
|
+
:fp2
|
18
|
+
|
19
|
+
def initialize(modbus_slave)
|
20
|
+
@modbus_slave = modbus_slave
|
21
|
+
@modbus_slave.read_retry_timeout = 15
|
22
|
+
@modbus_slave.read_retries = 2
|
23
|
+
iz2_zone_count = @modbus_slave.holding_registers[483]
|
24
|
+
@iz2_zones = (0...iz2_zone_count).map { |i| IZ2Zone.new(self, i + 1) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def refresh
|
28
|
+
registers_to_read = [19..20, 30, 344, 740..741, 900, 1110..1111, 1114, 1117, 3027, 31003]
|
29
|
+
# IZ2 zones
|
30
|
+
iz2_zones.each_with_index do |_z, i|
|
31
|
+
base1 = 21203 + i * 9
|
32
|
+
base2 = 31007 + i * 3
|
33
|
+
base3 = 31200 + i * 3
|
34
|
+
registers_to_read << (base1..(base1 + 1))
|
35
|
+
registers_to_read << (base2..(base2 + 2))
|
36
|
+
registers_to_read << base3
|
37
|
+
end
|
38
|
+
|
39
|
+
registers = @modbus_slave.holding_registers[*registers_to_read]
|
40
|
+
Aurora.transform_registers(registers)
|
41
|
+
|
42
|
+
@fan_speed = registers[344]
|
43
|
+
@entering_air_temperature = registers[740]
|
44
|
+
@relative_humidity = registers[741]
|
45
|
+
@leaving_air_temperature = registers[900]
|
46
|
+
@leaving_water_temperature = registers[1110]
|
47
|
+
@entering_water_temperature = registers[1111]
|
48
|
+
@dhw_water_temperature = registers[1114]
|
49
|
+
@waterflow = registers[1117]
|
50
|
+
@compressor_speed = registers[3027]
|
51
|
+
@outdoor_temperature = registers[31003]
|
52
|
+
@fp1 = registers[19]
|
53
|
+
@fp2 = registers[20]
|
54
|
+
@locked_out = registers[1117]
|
55
|
+
|
56
|
+
outputs = registers[30]
|
57
|
+
if outputs.include?(:lockout)
|
58
|
+
@current_mode = :lockout
|
59
|
+
elsif outputs.include?(:cc2)
|
60
|
+
@current_mode = outputs.include?(:rv) ? :c2 : :h2
|
61
|
+
elsif outputs.include?(:cc)
|
62
|
+
@current_mode = outputs.include?(:rv) ? :c1 : :h1
|
63
|
+
elsif outputs.include?(:eh2)
|
64
|
+
@current_mode = :eh2
|
65
|
+
elsif outputs.include?(:eh1)
|
66
|
+
@current_mode = :eh1
|
67
|
+
elsif outputs.include?(:blower)
|
68
|
+
@current_mode = :blower
|
69
|
+
else
|
70
|
+
@current_mode = :standby
|
71
|
+
end
|
72
|
+
|
73
|
+
iz2_zones.each do |z|
|
74
|
+
z.refresh(registers)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def inspect
|
79
|
+
"#<Aurora::ABCClient #{(instance_variables - [:@modbus_slave]).map { |iv| "#{iv}=#{instance_variable_get(iv).inspect}" }.join(', ')}>"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Aurora
|
2
|
+
class IZ2Zone
|
3
|
+
attr_reader :zone_number,
|
4
|
+
:target_mode,
|
5
|
+
:current_mode,
|
6
|
+
:target_fan_mode,
|
7
|
+
:current_fan_mode,
|
8
|
+
:fan_intermittent_on,
|
9
|
+
:fan_intermittent_off,
|
10
|
+
:priority,
|
11
|
+
:size, :normalized_size,
|
12
|
+
:ambient_temperature,
|
13
|
+
:cooling_target_temperature,
|
14
|
+
:heating_target_temperature
|
15
|
+
|
16
|
+
def initialize(abc, zone_number)
|
17
|
+
@abc = abc
|
18
|
+
@zone_number = zone_number
|
19
|
+
end
|
20
|
+
|
21
|
+
def refresh(registers)
|
22
|
+
@ambient_temperature = registers[31007 + (zone_number - 1) * 3]
|
23
|
+
|
24
|
+
config1 = registers[31008 + (zone_number - 1) * 3]
|
25
|
+
config2 = registers[31009 + (zone_number - 1) * 3]
|
26
|
+
config3 = registers[31200 + (zone_number - 1) * 3]
|
27
|
+
|
28
|
+
@target_fan_mode = config1[:fan]
|
29
|
+
@fan_intermittent_on = config1[:on_time]
|
30
|
+
@fan_intermittent_off = config1[:off_time]
|
31
|
+
@cooling_target_temperature = config1[:cooling_target_temperature]
|
32
|
+
@heating_target_temperature = config2[:heating_target_temperature]
|
33
|
+
@target_mode = config2[:mode]
|
34
|
+
@current_mode = config2[:call]
|
35
|
+
@current_fan_mode = config2[:damper] == :open
|
36
|
+
|
37
|
+
@priority = config3[:zone_priority]
|
38
|
+
@size = config3[:zone_size]
|
39
|
+
@normalized_size = config3[:normalized_size]
|
40
|
+
end
|
41
|
+
|
42
|
+
def target_mode=(value)
|
43
|
+
value = Aurora::HEATING_MODE.invert[value]
|
44
|
+
return unless value
|
45
|
+
@abc.modbus_slave.holding_registers[21202 + (zone_number - 1) * 9] = value
|
46
|
+
@target_mode = Aurora::HEATING_MODE[@abc.modbus_slave.holding_registers[21202 + (zone_number - 1) * 9]]
|
47
|
+
end
|
48
|
+
|
49
|
+
def target_fan_mode=(value)
|
50
|
+
value = Aurora::FAN_MODE.invert[value]
|
51
|
+
return unless value
|
52
|
+
@abc.modbus_slave.holding_registers[21205 + (zone_number - 1) * 9] = value
|
53
|
+
registers = @abc.modbus_slave.read_multiple_holding_registers(31008 + (zone_number - 1) * 3)
|
54
|
+
Aurora.transform_registers(registers)
|
55
|
+
@target_fan_mode = registers.first.last[:fan]
|
56
|
+
end
|
57
|
+
|
58
|
+
def fan_intermittent_on=(value)
|
59
|
+
return unless value >= 0 && value <= 25 && value % 5 == 0
|
60
|
+
@abc.modbus_slave.holding_registers[21206 + (zone_number - 1) * 9] = value
|
61
|
+
registers = @abc.modbus_slave.read_multiple_holding_registers(31008 + (zone_number - 1) * 3)
|
62
|
+
Aurora.transform_registers(registers)
|
63
|
+
@fan_intermittent_on = registers.first.last[:on_time]
|
64
|
+
end
|
65
|
+
|
66
|
+
def fan_intermittent_off=(value)
|
67
|
+
return unless value >= 0 && value <= 40 && value % 5 == 0
|
68
|
+
@abc.modbus_slave.holding_registers[21207 + (zone_number - 1) * 9] = value
|
69
|
+
registers = @abc.modbus_slave.read_multiple_holding_registers(31008 + (zone_number - 1) * 3)
|
70
|
+
Aurora.transform_registers(registers)
|
71
|
+
@fan_intermittent_on = registers.first.last[:off_time]
|
72
|
+
end
|
73
|
+
|
74
|
+
def heating_target_temperature=(value)
|
75
|
+
return unless value >= 40 && value <= 90
|
76
|
+
value = (value * 10).to_i
|
77
|
+
@abc.modbus_slave.holding_registers[21203 + (zone_number - 1) * 9] = value
|
78
|
+
|
79
|
+
base = 31008 + (zone_number - 1) * 3
|
80
|
+
registers = @abc.modbus_slave.read_multiple_holding_registers(base..(base + 1))
|
81
|
+
Aurora.transform_registers(registers)
|
82
|
+
registers[base + 1][:heating_target_temperature]
|
83
|
+
end
|
84
|
+
|
85
|
+
def cooling_target_temperature=(value)
|
86
|
+
return unless value >= 54 && value <= 99
|
87
|
+
value = (value * 10).to_i
|
88
|
+
@abc.modbus_slave.holding_registers[21204 + (zone_number - 1) * 9] = value
|
89
|
+
|
90
|
+
registers = @abc.modbus_slave.read_multiple_holding_registers(31008 + (zone_number - 1) * 3)
|
91
|
+
Aurora.transform_registers(registers)
|
92
|
+
registers.first.last[:cooling_target_temperature]
|
93
|
+
end
|
94
|
+
|
95
|
+
def inspect
|
96
|
+
"#<Aurora::IZ2Zone #{(instance_variables - [:@abc]).map { |iv| "#{iv}=#{instance_variable_get(iv).inspect}" }.join(', ')}>"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Aurora
|
2
|
+
module ModBus
|
3
|
+
module Server
|
4
|
+
def parse_request(func, req)
|
5
|
+
case func
|
6
|
+
when 65
|
7
|
+
# 1 function register, a multiple of two words
|
8
|
+
return unless (req.length - 1) % 4 == 0
|
9
|
+
params = []
|
10
|
+
req[1..-1].unpack("n*").each_slice(2) do |(addr, quant)|
|
11
|
+
params << { addr: addr, quant: quant }
|
12
|
+
end
|
13
|
+
params
|
14
|
+
when 66
|
15
|
+
return unless (req.length - 1) % 2 == 0
|
16
|
+
req[1..-1].unpack("n*")
|
17
|
+
when 67
|
18
|
+
# 1 function register, a multiple of two words
|
19
|
+
return unless (req.length - 1) % 4 == 0
|
20
|
+
params = []
|
21
|
+
req[1..-1].unpack("n*").each_slice(2) do |(addr, val)|
|
22
|
+
params << { addr: addr, val: val }
|
23
|
+
end
|
24
|
+
params
|
25
|
+
when 68
|
26
|
+
return unless req.length == 5
|
27
|
+
{ noidea1: req[1,2].unpack("n"), noidea2: req[3,2].unpack("n") }
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse_response(func, res)
|
34
|
+
return {} if func == 67 && res.length == 1
|
35
|
+
return { noidea: res[-1].ord } if func == 68 && res.length == 2
|
36
|
+
func = 3 if func == 65 || func == 66
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
def process_func(func, slave, req, params)
|
41
|
+
case func
|
42
|
+
when 65
|
43
|
+
pdu = ""
|
44
|
+
params.each do |param|
|
45
|
+
if (err = validate_read_func(param, slave.holding_registers))
|
46
|
+
return (func | 0x80).chr + err.chr
|
47
|
+
end
|
48
|
+
|
49
|
+
pdu += slave.holding_registers[param[:addr],param[:quant]].pack('n*')
|
50
|
+
end
|
51
|
+
pdu = func.chr + pdu.length.chr + pdu
|
52
|
+
pdu
|
53
|
+
when 66
|
54
|
+
pdu = params.map { |addr| slave.holding_registers[addr] }.pack('n*')
|
55
|
+
pdu = func.chr + pdu.length.chr + pdu
|
56
|
+
pdu
|
57
|
+
when 67
|
58
|
+
slave.holding_registers[param[:addr]] = param[:val]
|
59
|
+
pdu = req[0,2]
|
60
|
+
else
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# 65 => read multiple discontiguous register ranges (command is a list of pairs of addr and quant)
|
69
|
+
# 66 => read multiple discontiguous registers (command is a list of addrs)
|
70
|
+
# 67 => write multiple discontiguous registers (command is a list of pairs of addr and value; response has no content)
|
71
|
+
# 68 => ?? request has 4 bytes, response has 1 byte that seems to be 0 (for success?)
|
72
|
+
ModBus::Server::Funcs.concat([65, 66, 67, 68])
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Aurora
|
2
|
+
module ModBus
|
3
|
+
module Slave
|
4
|
+
def read_multiple_holding_registers(*ranges)
|
5
|
+
values = if ranges.any? { |r| r.is_a?(Range) }
|
6
|
+
addrs_and_lengths = ranges.map { |r| r = Array(r); [r.first, r.last - r.first + 1] }.flatten
|
7
|
+
query("\x41" + addrs_and_lengths.pack("n*")).unpack("n*")
|
8
|
+
else
|
9
|
+
query("\x42" + ranges.pack("n*")).unpack("n*")
|
10
|
+
end
|
11
|
+
ranges.map { |r| Array(r) }.flatten.zip(values).to_h
|
12
|
+
end
|
13
|
+
|
14
|
+
def holding_registers
|
15
|
+
WFProxy.new(self, :holding_register)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class WFProxy < ::ModBus::ReadWriteProxy
|
20
|
+
def [](*keys)
|
21
|
+
return super if keys.length == 1
|
22
|
+
@slave.read_multiple_holding_registers(*keys)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module RTU
|
27
|
+
def read_rtu_response(io)
|
28
|
+
# Read the slave_id and function code
|
29
|
+
msg = read(io, 2)
|
30
|
+
log logging_bytes(msg)
|
31
|
+
|
32
|
+
function_code = msg.getbyte(1)
|
33
|
+
case function_code
|
34
|
+
when 1,2,3,4,65,66 then
|
35
|
+
# read the third byte to find out how much more
|
36
|
+
# we need to read + CRC
|
37
|
+
msg += read(io, 1)
|
38
|
+
msg += read(io, msg.getbyte(2)+2)
|
39
|
+
when 5,6,15,16 then
|
40
|
+
# We just read in an additional 6 bytes
|
41
|
+
msg += read(io, 6)
|
42
|
+
when 22 then
|
43
|
+
msg += read(io, 8)
|
44
|
+
when 0x80..0xff then
|
45
|
+
msg += read(io, 3)
|
46
|
+
else
|
47
|
+
raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,660 @@
|
|
1
|
+
module Aurora
|
2
|
+
extend self
|
3
|
+
|
4
|
+
def normalize_ranges(ranges)
|
5
|
+
registers = ranges.map { |r| Array(r) }.flatten.sort.uniq
|
6
|
+
result = []
|
7
|
+
totals = []
|
8
|
+
run_start = nil
|
9
|
+
count = 0
|
10
|
+
registers.each_with_index do |r, i|
|
11
|
+
run_start ||= r
|
12
|
+
if i + 1 == registers.length || r + 1 != registers[i + 1]
|
13
|
+
if r == run_start
|
14
|
+
result << r
|
15
|
+
if (count += 1) == 100
|
16
|
+
totals << result
|
17
|
+
result = []
|
18
|
+
count = 0
|
19
|
+
end
|
20
|
+
else
|
21
|
+
range = run_start..r
|
22
|
+
if count + range.count > 100
|
23
|
+
totals << result
|
24
|
+
result = []
|
25
|
+
count = 0
|
26
|
+
end
|
27
|
+
count += range.count
|
28
|
+
result << range
|
29
|
+
end
|
30
|
+
run_start = nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
totals << result unless result.empty?
|
34
|
+
totals
|
35
|
+
end
|
36
|
+
|
37
|
+
TO_HUNDREDTHS = ->(v) { v.to_f / 100 }
|
38
|
+
TO_TENTHS = ->(v) { v.to_f / 10 }
|
39
|
+
TO_LAST_LOCKOUT = ->(v) { v & 0x8000 == 0x8000 ? v & 0x7fff : nil }
|
40
|
+
NEGATABLE = ->(v) { v & 0x8000 == 0x8000 ? v - 0x10000 : v }
|
41
|
+
|
42
|
+
def from_bitmask(v, flags)
|
43
|
+
result = []
|
44
|
+
flags.each do |(bit, flag)|
|
45
|
+
result << flag if v & bit == bit
|
46
|
+
v &= ~bit
|
47
|
+
end
|
48
|
+
result << "0x%04x" % v if v != 0
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_string(registers, idx, length)
|
53
|
+
puts "converting #{idx} of length #{length}"
|
54
|
+
(idx...(idx + length)).map do |i|
|
55
|
+
(registers[i] >> 8).chr + (registers[i] & 0xff).chr
|
56
|
+
end.join.sub(/[ \0]+$/, '')
|
57
|
+
end
|
58
|
+
|
59
|
+
FAULTS = {
|
60
|
+
1 => "Input Flt Limit",
|
61
|
+
2 => "High Pressure",
|
62
|
+
3 => "Low Pressure",
|
63
|
+
4 => "FP2",
|
64
|
+
5 => "FP1",
|
65
|
+
7 => "Condensate Limit",
|
66
|
+
8 => "Over/Under Voltage",
|
67
|
+
9 => "AirF/RPM",
|
68
|
+
10 => "CompMon",
|
69
|
+
11 => "FP1/2 Snr Limit",
|
70
|
+
12 => "RefPerfrm Limit",
|
71
|
+
13 => "NCrtAxbSr Limit",
|
72
|
+
14 => "CrtAxbSnr Limit",
|
73
|
+
15 => "HW Limit",
|
74
|
+
16 => "VSPumpFlt Limit",
|
75
|
+
17 => "CommTStat Limit",
|
76
|
+
18 => "NCritComm Limit",
|
77
|
+
19 => "Crit Comm Limit",
|
78
|
+
21 => "Low Loop Pressure",
|
79
|
+
22 => "ComEcmErr Limit",
|
80
|
+
23 => "HAGenAlrm1 Limit",
|
81
|
+
24 => "HAGenAlrm2 Limit",
|
82
|
+
25 => "AxbEevErr Limit",
|
83
|
+
41 => "High Drive Temp Limit",
|
84
|
+
42 => "High Discharge Temp Limit",
|
85
|
+
43 => "Low Suction Pressure Limit",
|
86
|
+
44 => "Low Con Pressure Limit",
|
87
|
+
45 => "High Con Pressure Limit",
|
88
|
+
46 => "Out Power Limit",
|
89
|
+
47 => "EevIDComm Limit",
|
90
|
+
48 => "EevODComm Limit",
|
91
|
+
49 => "CabTmpSnr Limit",
|
92
|
+
51 => "Discharge Temp Sensor Limit",
|
93
|
+
52 => "Suction Presure Sensor Limit",
|
94
|
+
53 => "Con Pressure Sensor Limit",
|
95
|
+
54 => "Low Supply Voltage Limit",
|
96
|
+
55 => "OutEnvelp Limit",
|
97
|
+
56 => "Suction Pressure Sensor Limit",
|
98
|
+
57 => "Drive Over/Under Voltage Limit",
|
99
|
+
58 => "High Drive Temp Limit",
|
100
|
+
59 => "Internal Drive Error Limit",
|
101
|
+
61 => "MultSafm",
|
102
|
+
71 => "ChrgLoss",
|
103
|
+
72 => "Suction Temp Sensor Limit",
|
104
|
+
73 => "Leaving Air Temp Sensor Limit",
|
105
|
+
74 => "Maximum Operating Pressure Limit",
|
106
|
+
75 => "Charge Loss",
|
107
|
+
76 => "Suction Temperatur Sensor Limit",
|
108
|
+
77 => "Leaving Air Temperature Sensor Limit",
|
109
|
+
78 => "Maximum Operating Pressure Limit",
|
110
|
+
}
|
111
|
+
|
112
|
+
AR_SETTINGS = {
|
113
|
+
0 => "Cycle with Compressor",
|
114
|
+
1 => "Cycle with Thermostat Humidification Call",
|
115
|
+
2 => "Slow Opening Water Valve",
|
116
|
+
3 => "Cycle with Blower"
|
117
|
+
}
|
118
|
+
|
119
|
+
def dipswitch_settings(v)
|
120
|
+
return :manual if v == 0x7fff
|
121
|
+
{
|
122
|
+
fp1: v & 0x01 == 0x01 ? "30ºF" : "15ºF",
|
123
|
+
fp2: v & 0x02 == 0x02 ? "30ºF" : "15ºF",
|
124
|
+
rv: v & 0x04 == 0x04 ? "O" : "B",
|
125
|
+
ar: AR_SETTINGS[(v >> 3) & 0x7],
|
126
|
+
cc: v & 0x20 == 0x20 ? "Single Stage" : "Dual Stage",
|
127
|
+
lo: v & 0x40 == 0x40 ? "Continouous" : "Pulse",
|
128
|
+
dh_rh: v & 0x80 == 0x80 ? "Dehumdifier On" : "Reheat On",
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
SYSTEM_OUTPUTS = {
|
133
|
+
0x01 => :cc, # compressor stage 1
|
134
|
+
0x02 => :cc2, # compressor stage 2
|
135
|
+
0x04 => :rv, # reversing valve (cool instead of heat)
|
136
|
+
0x08 => :blower,
|
137
|
+
0x10 => :eh1,
|
138
|
+
0x20 => :eh2,
|
139
|
+
0x200 => :accessory,
|
140
|
+
0x400 => :lockout,
|
141
|
+
0x800 => :alarm,
|
142
|
+
}
|
143
|
+
|
144
|
+
SYSTEM_INPUTS = {
|
145
|
+
0x01 => "Y1",
|
146
|
+
0x02 => "Y2",
|
147
|
+
0x04 => "W",
|
148
|
+
0x08 => "O",
|
149
|
+
0x10 => "G",
|
150
|
+
0x20 => "Dehumidifer",
|
151
|
+
0x40 => "Emergency Shutdown",
|
152
|
+
0x200 => "Load Shed",
|
153
|
+
}
|
154
|
+
|
155
|
+
def status(v)
|
156
|
+
result = {
|
157
|
+
lps: v & 0x80 == 0x80 ? :closed : :open,
|
158
|
+
hps: v & 0x100 == 0x100 ? :closed : :open,
|
159
|
+
}
|
160
|
+
result[:load_shed] = true if v & 0x0200 == 0x0200
|
161
|
+
result[:emergency_shutdown] = true if v & 0x0040 == 0x0040
|
162
|
+
leftover = v & ~0x03c0
|
163
|
+
result[:unknown] = "0x%04x" % leftover unless leftover == 0
|
164
|
+
result
|
165
|
+
end
|
166
|
+
|
167
|
+
VS_DRIVE_DERATE = {
|
168
|
+
0x01 => "Drive Over Temp",
|
169
|
+
0x04 => "Low Suction Pressure",
|
170
|
+
0x10 => "Low Discharge Pressure",
|
171
|
+
0x20 => "High Discharge Pressure",
|
172
|
+
0x40 => "Output Power Limit",
|
173
|
+
}
|
174
|
+
|
175
|
+
VS_SAFE_MODE = {
|
176
|
+
0x01 => "EEV Indoor Failed",
|
177
|
+
0x02 => "EEV Outdoor Failed",
|
178
|
+
0x04 => "Invalid Ambient Temp",
|
179
|
+
}
|
180
|
+
|
181
|
+
VS_ALARM1 = {
|
182
|
+
0x8000 => "Internal Error",
|
183
|
+
}
|
184
|
+
|
185
|
+
VS_ALARM2 = {
|
186
|
+
0x0001 => "Multi Safe Modes",
|
187
|
+
0x0002 => "Out of Envelope",
|
188
|
+
0x0004 => "Over Current",
|
189
|
+
0x0008 => "Over Voltage",
|
190
|
+
0x0010 => "Drive Over Temp",
|
191
|
+
0x0020 => "Under Voltage",
|
192
|
+
0x0040 => "High Discharge Temp",
|
193
|
+
0x0080 => "Invalid Discharge Temp",
|
194
|
+
0x0100 => "OEM Communications Timeout",
|
195
|
+
0x0200 => "MOC Safety",
|
196
|
+
0x0400 => "DC Under Voltage",
|
197
|
+
0x0800 => "Invalid Suction Pressure",
|
198
|
+
0x1000 => "Invalid Discharge Pressure",
|
199
|
+
0x2000 => "Low Discharge Pressure",
|
200
|
+
}
|
201
|
+
|
202
|
+
VS_EEV2 = {
|
203
|
+
0x0010 => "Invalid Suction Temperature",
|
204
|
+
0x0020 => "Invalid Leaving Air Temperature",
|
205
|
+
0x0040 => "Invalid Suction Pressure",
|
206
|
+
|
207
|
+
}
|
208
|
+
|
209
|
+
AXB_INPUTS = {
|
210
|
+
}
|
211
|
+
|
212
|
+
AXB_OUTPUTS = {
|
213
|
+
0x10 => "Accessory 2",
|
214
|
+
0x02 => "Loop Pump",
|
215
|
+
0x01 => "DHW"
|
216
|
+
}
|
217
|
+
|
218
|
+
HEATING_MODE = {
|
219
|
+
0 => :off,
|
220
|
+
1 => :auto,
|
221
|
+
2 => :cool,
|
222
|
+
3 => :heat,
|
223
|
+
4 => :eheat
|
224
|
+
}
|
225
|
+
|
226
|
+
FAN_MODE = {
|
227
|
+
0 => :auto,
|
228
|
+
1 => :continuous,
|
229
|
+
2 => :intermittent
|
230
|
+
}
|
231
|
+
|
232
|
+
HUMIDIFIER_SETTINGS = {
|
233
|
+
0x4000 => :auto_dehumidification,
|
234
|
+
0x8000 => :auto_humidification,
|
235
|
+
}
|
236
|
+
|
237
|
+
INVERSE_HUMIDIFIER_SETTINGS = {
|
238
|
+
0x4000 => :manual_dehumidification,
|
239
|
+
0x8000 => :manual_humidification,
|
240
|
+
}
|
241
|
+
|
242
|
+
ZONE_SIZES = {
|
243
|
+
0 => 0,
|
244
|
+
1 => 25,
|
245
|
+
2 => 45,
|
246
|
+
3 => 70,
|
247
|
+
}
|
248
|
+
|
249
|
+
CALLS = {
|
250
|
+
0x0 => :standby,
|
251
|
+
0x1 => :unknown1,
|
252
|
+
0x2 => :h1,
|
253
|
+
0x3 => :h2,
|
254
|
+
0x4 => :h3,
|
255
|
+
0x5 => :c1,
|
256
|
+
0x6 => :c2,
|
257
|
+
0x7 => :unknown7,
|
258
|
+
}
|
259
|
+
|
260
|
+
def iz2_demand(v)
|
261
|
+
{
|
262
|
+
fan_demand: v >> 8,
|
263
|
+
unit_demand: v & 0xff,
|
264
|
+
}
|
265
|
+
end
|
266
|
+
|
267
|
+
def zone_configuration1(v)
|
268
|
+
fan = if v & 0x80 == 0x80
|
269
|
+
:continuous
|
270
|
+
elsif v & 0x100 == 0x100
|
271
|
+
:intermittent
|
272
|
+
else
|
273
|
+
:auto
|
274
|
+
end
|
275
|
+
result = {
|
276
|
+
fan: fan,
|
277
|
+
on_time: ((v >> 9) & 0x7) * 5,
|
278
|
+
off_time: (((v >> 12) & 0x7) + 1) * 5,
|
279
|
+
cooling_target_temperature: ((v & 0x7e) >> 1) + 36,
|
280
|
+
heating_target_temperature_carry: v & 01
|
281
|
+
}
|
282
|
+
leftover = v & ~0x7fff
|
283
|
+
result[:unknown] = "0x%04x" % leftover unless leftover == 0
|
284
|
+
result
|
285
|
+
end
|
286
|
+
|
287
|
+
def zone_configuration2(registers, k)
|
288
|
+
prior_v = registers[k - 1] if registers.key?(k - 1)
|
289
|
+
v = registers[k]
|
290
|
+
result = {
|
291
|
+
call: CALLS[(v >> 1) & 0x7],
|
292
|
+
mode: HEATING_MODE[(v >> 8) & 0x03],
|
293
|
+
damper: v & 0x10 == 0x10 ? :open : :closed
|
294
|
+
}
|
295
|
+
if prior_v
|
296
|
+
carry = prior_v.is_a?(Hash) ? prior_v[:heating_target_temperature_carry] : v & 0x01
|
297
|
+
result[:heating_target_temperature] = ((carry << 5) | ((v & 0xf800) >> 11)) + 36
|
298
|
+
end
|
299
|
+
leftover = v & ~0xfb1e
|
300
|
+
result[:unknown] = "0x%04x" % leftover unless leftover == 0
|
301
|
+
result
|
302
|
+
end
|
303
|
+
|
304
|
+
# hi order byte is normalized zone size
|
305
|
+
def zone_configuration3(v)
|
306
|
+
size = (v >> 3 ) & 0x3
|
307
|
+
result = {
|
308
|
+
zone_priority: (v & 0x20) == 0x20 ? :economy : :comfort,
|
309
|
+
zone_size: ZONE_SIZES[size],
|
310
|
+
normalized_size: v >> 8,
|
311
|
+
}
|
312
|
+
leftover = v & ~0xff38
|
313
|
+
result[:unknown] = "0x%04x" % leftover unless leftover == 0
|
314
|
+
result
|
315
|
+
end
|
316
|
+
|
317
|
+
# intermittent on time allowed: 0, 5, 10, 15, 20
|
318
|
+
# intermittent off time allowed: 5, 10, 15, 20, 25, 30, 35, 40
|
319
|
+
|
320
|
+
REGISTER_CONVERTERS = {
|
321
|
+
TO_HUNDREDTHS => [2, 3, 807, 813, 816, 817, 819, 820, 825, 828],
|
322
|
+
method(:dipswitch_settings) => [4, 33],
|
323
|
+
TO_TENTHS => [19, 20, 401, 567, 740, 745, 746, 900, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117, 1134, 1136,
|
324
|
+
21203, 21204,
|
325
|
+
21212, 21213,
|
326
|
+
21221, 21222,
|
327
|
+
21230, 22131,
|
328
|
+
21239, 21240,
|
329
|
+
21248, 21249,
|
330
|
+
31003,
|
331
|
+
31007, 31010, 31013, 31016, 31019, 31022],
|
332
|
+
TO_LAST_LOCKOUT => [26],
|
333
|
+
->(v) { from_bitmask(v, SYSTEM_OUTPUTS) } => [27, 30],
|
334
|
+
->(v) { from_bitmask(v, SYSTEM_INPUTS) } => [28],
|
335
|
+
method(:status) => [31],
|
336
|
+
->(registers, idx) { to_string(registers, idx, 4) } => [88],
|
337
|
+
->(registers, idx) { to_string(registers, idx, 12) } => [92],
|
338
|
+
->(registers, idx) { to_string(registers, idx, 5) } => [105],
|
339
|
+
->(v) { from_bitmask(v, VS_DRIVE_DERATE) } => [214],
|
340
|
+
->(v) { from_bitmask(v, VS_SAFE_MODE) } => [216],
|
341
|
+
->(v) { from_bitmask(v, VS_ALARM1) } => [217],
|
342
|
+
->(v) { from_bitmask(v, VS_ALARM2) } => [218],
|
343
|
+
->(v) { from_bitmask(v, VS_EEV2) } => [280],
|
344
|
+
NEGATABLE => [346, 1146],
|
345
|
+
->(v) { from_bitmask(v, AXB_INPUTS) } => [1103],
|
346
|
+
->(v) { from_bitmask(v, AXB_OUTPUTS) } => [1104],
|
347
|
+
->(v) { TO_TENTHS.call(NEGATABLE.call(v)) } => [1136],
|
348
|
+
->(v) { HEATING_MODE[v] } => [21202, 21211, 21220, 21229, 21238, 21247],
|
349
|
+
->(v) { FAN_MODE[v] } => [21205, 21214, 21223, 21232, 21241, 21250],
|
350
|
+
->(v) { from_bitmask(v, HUMIDIFIER_SETTINGS) } => [31109],
|
351
|
+
->(v) { { humidification_target: v >> 8, dehumidification_target: v & 0xff } } => [31110],
|
352
|
+
method(:iz2_demand) => [31005],
|
353
|
+
method(:zone_configuration1) => [31008, 31011, 31014, 31017, 31020, 31023],
|
354
|
+
method(:zone_configuration2) => [31009, 31012, 31015, 31018, 31021, 31024],
|
355
|
+
method(:zone_configuration3) => [31200, 31203, 31206, 31209, 31212, 31215],
|
356
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31400],
|
357
|
+
->(registers, idx) { to_string(registers, idx, 8) } => [31413],
|
358
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31421],
|
359
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31434],
|
360
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31447],
|
361
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31460],
|
362
|
+
}
|
363
|
+
|
364
|
+
REGISTER_FORMATS = {
|
365
|
+
"%ds" => [1, 6, 9, 15, 84, 85],
|
366
|
+
"%dV" => [16, 112],
|
367
|
+
"%0.1fºF" => [19, 20, 401, 567, 740, 745, 746, 900, 1110, 1111, 1114, 1134, 1136,
|
368
|
+
21203, 21204,
|
369
|
+
21212, 21213,
|
370
|
+
21221, 21222,
|
371
|
+
21230, 21231,
|
372
|
+
21239, 21240,
|
373
|
+
21248, 21249,
|
374
|
+
31003,
|
375
|
+
31007, 31010, 31013, 31016, 31019, 31022],
|
376
|
+
"E%d" => [25, 26],
|
377
|
+
"%d%%" => [282, 321, 322, 346, 565, 741],
|
378
|
+
"%0.1fA" => [1105, 1106, 1107, 1108],
|
379
|
+
"%0.1fgpm" => [1117],
|
380
|
+
"%dW" => [1147, 1149, 1151, 1153, 1165],
|
381
|
+
"%dBtuh" => [1157],
|
382
|
+
}
|
383
|
+
|
384
|
+
def ignore(range)
|
385
|
+
range.zip(Array.new(range.count)).to_h
|
386
|
+
end
|
387
|
+
|
388
|
+
def faults(range)
|
389
|
+
range.map { |i| [i, "E#{i % 100}"] }.to_h
|
390
|
+
end
|
391
|
+
|
392
|
+
def zone_registers
|
393
|
+
(1..6).map do |i|
|
394
|
+
base1 = 21202 + (i - 1) * 9
|
395
|
+
base2 = 31007 + (i - 1) * 3
|
396
|
+
base3 = 31200 + (i - 1) * 3
|
397
|
+
{
|
398
|
+
base1 => "Zone #{i} Heating Mode",
|
399
|
+
(base1 + 1) => "Zone #{i} Heating Setpoint (write)",
|
400
|
+
(base1 + 2) => "Zone #{i} Cooling Setpoint (write)",
|
401
|
+
(base1 + 3) => "Zone #{i} Fan Mode (write)",
|
402
|
+
(base1 + 4) => "Zone #{i} Intermittent Fan On Time (write)",
|
403
|
+
(base1 + 5) => "Zone #{i} Intermittent Fan Off Time (write)",
|
404
|
+
base2 => "Zone #{i} Ambient Temperature",
|
405
|
+
(base2 + 1) => "Zone #{i} Configuration 1",
|
406
|
+
(base2 + 2) => "Zone #{i} Configuration 2",
|
407
|
+
base3 => "Zone #{i} Configuration 3",
|
408
|
+
}
|
409
|
+
end.inject({}, &:merge)
|
410
|
+
end
|
411
|
+
|
412
|
+
WRITEABLE = [112, 340, 341, 342, 346, 347]
|
413
|
+
|
414
|
+
# these are the valid ranges (i.e. the ABC will return _some_ value)
|
415
|
+
# * means 6 sequential ranges of equal size (i.e. must be repeated for each
|
416
|
+
# IZ2 zone)
|
417
|
+
# ==================================================================
|
418
|
+
REGISTER_RANGES = [
|
419
|
+
0..155,
|
420
|
+
170..253,
|
421
|
+
260..260,
|
422
|
+
280..288,
|
423
|
+
300..301,
|
424
|
+
320..326,
|
425
|
+
340..348,
|
426
|
+
360..368,
|
427
|
+
400..419,
|
428
|
+
440..516,
|
429
|
+
550..573,
|
430
|
+
600..749,
|
431
|
+
800..913,
|
432
|
+
1090..1165,
|
433
|
+
1200..1263,
|
434
|
+
2000..2026,
|
435
|
+
2100..2129,
|
436
|
+
2800..2849,
|
437
|
+
2900..2915,
|
438
|
+
2950..2959,
|
439
|
+
3000..3003,
|
440
|
+
3020..3030,
|
441
|
+
3040..3049,
|
442
|
+
3060..3063,
|
443
|
+
3100..3105,
|
444
|
+
3108..3115,
|
445
|
+
3118..3119,
|
446
|
+
3200..3253,
|
447
|
+
3300..3332,
|
448
|
+
3400..3431,
|
449
|
+
3500..3524,
|
450
|
+
3600..3609,
|
451
|
+
3618..3634,
|
452
|
+
3700..3714,
|
453
|
+
3800..3809,
|
454
|
+
3818..3834,
|
455
|
+
3900..3914,
|
456
|
+
12000..12019,
|
457
|
+
12098..12099,
|
458
|
+
12100..12119,
|
459
|
+
12200..12239,
|
460
|
+
12300..12319,
|
461
|
+
12400..12569,
|
462
|
+
12600..12639,
|
463
|
+
12700..12799,
|
464
|
+
20000..20099,
|
465
|
+
21100..21136,
|
466
|
+
21200..21265,
|
467
|
+
21400..21472,
|
468
|
+
21500..21589,
|
469
|
+
22100..22162, # *
|
470
|
+
22200..22262, # *
|
471
|
+
22300..22362, # *
|
472
|
+
22400..22462, # *
|
473
|
+
22500..22562, # *
|
474
|
+
22600..22662, # *
|
475
|
+
30000..30099,
|
476
|
+
31000..31034,
|
477
|
+
31100..31129,
|
478
|
+
31200..31229,
|
479
|
+
31300..31329,
|
480
|
+
31400..31472,
|
481
|
+
32100..32162, # *
|
482
|
+
32200..32262, # *
|
483
|
+
32300..32362, # *
|
484
|
+
32400..32462, # *
|
485
|
+
32500..32562, # *
|
486
|
+
32600..32662, # *
|
487
|
+
60050..60053,
|
488
|
+
60100..60109,
|
489
|
+
60200..60200,
|
490
|
+
61000..61009
|
491
|
+
]
|
492
|
+
|
493
|
+
def read_all_registers(modbus_slave)
|
494
|
+
result = []
|
495
|
+
REGISTER_RANGES.each do |range|
|
496
|
+
# read at most 100 at a time
|
497
|
+
range.each_slice(100) do |keys|
|
498
|
+
result.concat(modbus_slave.holding_registers[keys.first..keys.last])
|
499
|
+
end
|
500
|
+
end
|
501
|
+
REGISTER_RANGES.map(&:to_a).flatten.zip(result).to_h
|
502
|
+
end
|
503
|
+
|
504
|
+
def diff_registers(r1, r2)
|
505
|
+
diff = {}
|
506
|
+
r1.each_key do |k|
|
507
|
+
diff[k] = [r1[k], r2[k]] if r1[k] != r2[k]
|
508
|
+
end
|
509
|
+
diff
|
510
|
+
end
|
511
|
+
|
512
|
+
REGISTER_NAMES = {
|
513
|
+
1 => "Random Start Delay",
|
514
|
+
2 => "ABC Program Version",
|
515
|
+
3 => "IZ2 Version?",
|
516
|
+
4 => "DIP Switch Override",
|
517
|
+
6 => "Compressor Anti-Short Cycle Delay",
|
518
|
+
8 => "Unit Type?",
|
519
|
+
9 => "Compressor Minimum Run Time",
|
520
|
+
15 => "Blower Off Delay",
|
521
|
+
16 => "Line Voltage",
|
522
|
+
19 => "FP1",
|
523
|
+
20 => "FP2",
|
524
|
+
21 => "Condensate", # >= 270 normal, otherwise fault
|
525
|
+
25 => "Last Fault Number",
|
526
|
+
26 => "Last Lockout",
|
527
|
+
27 => "System Outputs (At Last Lockout)",
|
528
|
+
28 => "System Inputs (At Last Lockout)",
|
529
|
+
30 => "System Outputs",
|
530
|
+
31 => "Status",
|
531
|
+
33 => "DIP Switch Status",
|
532
|
+
50 => "ECM Speed Low (== 5)",
|
533
|
+
51 => "ECM Speed Med (== 5)",
|
534
|
+
52 => "ECM Speed High (== 5)",
|
535
|
+
54 => "ECM Speed Actual",
|
536
|
+
84 => "Slow Opening Water Valve Delay",
|
537
|
+
85 => "Test Mode Timer",
|
538
|
+
88 => "ABC Program",
|
539
|
+
92 => "Model Number",
|
540
|
+
105 => "Serial Number",
|
541
|
+
112 => "Setup Line Voltage",
|
542
|
+
201 => "Discharge Pressure", # I can't figure out how this number is represented;
|
543
|
+
203 => "Suction Pressure",
|
544
|
+
205 => "Discharge Temperature",
|
545
|
+
207 => "Loop Entering Water Temperature",
|
546
|
+
209 => "Compressor Ambient Temperature",
|
547
|
+
211 => "VS Drive Details (General 1)",
|
548
|
+
212 => "VS Drive Details (General 2)",
|
549
|
+
213 => "VS Drive Details (Derate 1)",
|
550
|
+
214 => "VS Drive Details (Derate 2)",
|
551
|
+
215 => "VS Drive Details (Safemode 1)",
|
552
|
+
216 => "VS Drive Details (Safemode 2)",
|
553
|
+
217 => "VS Drive Details (Alarm 1)",
|
554
|
+
218 => "VS Drive Details (Alarm 2)",
|
555
|
+
280 => "EEV2 Ctl",
|
556
|
+
281 => "EEV Superheat", # ?? data format
|
557
|
+
282 => "EEV Open %",
|
558
|
+
283 => "Suction Temperature", ## ?? data format
|
559
|
+
284 => "Saturated Suction Temperature", ## ?? data format
|
560
|
+
321 => "VS Pump Min",
|
561
|
+
322 => "VS Pump Max",
|
562
|
+
340 => "Blower Only Speed",
|
563
|
+
341 => "Lo Compressor ECM Speed",
|
564
|
+
342 => "Hi Compressor ECM Speed",
|
565
|
+
344 => "ECM Speed",
|
566
|
+
346 => "Cooling Airflow Adjustment",
|
567
|
+
347 => "Aux Heat ECM Speed",
|
568
|
+
362 => "Active Dehumidify", # any value is true
|
569
|
+
401 => "DHW Setpoint",
|
570
|
+
414 => "On Peak/SmartGrid 2", # 0x0001 only
|
571
|
+
483 => "Number of IZ2 Zones",
|
572
|
+
564 => "IZ2 Compressor Speed Desired",
|
573
|
+
565 => "IZ2 Blower % Desired",
|
574
|
+
567 => "Entering Air",
|
575
|
+
740 => "Entering Air",
|
576
|
+
741 => "Relative Humidity",
|
577
|
+
745 => "Heating Set Point",
|
578
|
+
746 => "Cooling Set Point",
|
579
|
+
807 => "AXB Version",
|
580
|
+
813 => "IZ2 Version?",
|
581
|
+
816 => "AOC Version 1?",
|
582
|
+
817 => "AOC Version 2?",
|
583
|
+
819 => "MOC Version 1?",
|
584
|
+
820 => "MOC Version 2?",
|
585
|
+
825 => "EEV2 Version",
|
586
|
+
828 => "AWL Version",
|
587
|
+
900 => "Leaving Air",
|
588
|
+
1103 => "AXB Inputs",
|
589
|
+
1104 => "AXB Outputs",
|
590
|
+
1105 => "Blower Amps",
|
591
|
+
1106 => "Aux Amps",
|
592
|
+
1107 => "Compressor 1 Amps",
|
593
|
+
1108 => "Compressor 2 Amps",
|
594
|
+
1109 => "Heating Liquid Line",
|
595
|
+
1110 => "Leaving Water",
|
596
|
+
1111 => "Entering Water",
|
597
|
+
1114 => "DHW Temp",
|
598
|
+
1117 => "Waterflow",
|
599
|
+
1134 => "Saturated Discharge Temperature",
|
600
|
+
1135 => "SubCooling",
|
601
|
+
1147 => "Compressor Watts",
|
602
|
+
1149 => "Blower Watts",
|
603
|
+
1151 => "Aux Watts",
|
604
|
+
1153 => "Total Watts",
|
605
|
+
1157 => "Ht of Rej",
|
606
|
+
1165 => "VS Pump Watts",
|
607
|
+
3027 => "Compressor Speed",
|
608
|
+
31003 => "Outdoor Temp",
|
609
|
+
31005 => "IZ2 Demand",
|
610
|
+
31109 => "Humidifier Mode", # write to 21114
|
611
|
+
31110 => "Manual De/Humidification Target", # write to 21115
|
612
|
+
31400 => "Dealer Name",
|
613
|
+
31413 => "Dealer Phone",
|
614
|
+
31421 => "Dealer Address 1",
|
615
|
+
31434 => "Dealer Address 2",
|
616
|
+
31447 => "Dealer Email",
|
617
|
+
31460 => "Dealer Website",
|
618
|
+
}.merge(ignore(89..91)).
|
619
|
+
merge(ignore(93..104)).
|
620
|
+
merge(ignore(106..109)).
|
621
|
+
merge(faults(601..699)).
|
622
|
+
merge(zone_registers).
|
623
|
+
merge(ignore(31401..31412)).
|
624
|
+
merge(ignore(31414..31420)).
|
625
|
+
merge(ignore(31422..31433)).
|
626
|
+
merge(ignore(31435..31446)).
|
627
|
+
merge(ignore(31447..31459)).
|
628
|
+
merge(ignore(31461..31472))
|
629
|
+
|
630
|
+
def transform_registers(registers)
|
631
|
+
registers.each do |(k, v)|
|
632
|
+
value_proc = REGISTER_CONVERTERS.find { |(_, z)| z.include?(k) }&.first
|
633
|
+
next unless value_proc
|
634
|
+
|
635
|
+
value = value_proc.arity == 2 ? value_proc.call(registers, k) : value_proc.call(v)
|
636
|
+
registers[k] = value
|
637
|
+
end
|
638
|
+
registers
|
639
|
+
end
|
640
|
+
|
641
|
+
def print_registers(registers)
|
642
|
+
result = []
|
643
|
+
registers.each do |(k, v)|
|
644
|
+
# ignored
|
645
|
+
next if REGISTER_NAMES.key?(k) && REGISTER_NAMES[k].nil?
|
646
|
+
name = REGISTER_NAMES[k]
|
647
|
+
value_proc = REGISTER_CONVERTERS.find { |(_, z)| z.include?(k) }&.first || ->(v) { v }
|
648
|
+
format = REGISTER_FORMATS.find { |(_, z)| z.include?(k) }&.first || "%s"
|
649
|
+
format = "%1$d (0x%1$04x)" unless name
|
650
|
+
name ||= "???"
|
651
|
+
|
652
|
+
value = value_proc.arity == 2 ? value_proc.call(registers, k) : value_proc.call(v)
|
653
|
+
value = value.join(", ") if value.is_a?(Array)
|
654
|
+
value = sprintf(format, value) if value
|
655
|
+
|
656
|
+
result << "#{name} (#{k}): #{value}"
|
657
|
+
end
|
658
|
+
result.join("\n")
|
659
|
+
end
|
660
|
+
end
|