waterfurnace_aurora 0.1.1
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 +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
|