waterfurnace_aurora 0.1.1 → 0.3.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/exe/aurora_fetch +26 -0
- data/{bin → exe}/aurora_mock +23 -20
- data/{bin → exe}/aurora_monitor +20 -18
- data/exe/aurora_mqtt_bridge +189 -0
- data/{bin → exe}/registers.yml +0 -0
- data/lib/aurora/abc_client.rb +64 -42
- data/lib/aurora/core_ext/string.rb +10 -0
- data/lib/aurora/iz2_zone.rb +38 -52
- data/lib/aurora/modbus/server.rb +22 -15
- data/lib/aurora/modbus/slave.rb +27 -20
- data/lib/aurora/registers.rb +216 -205
- data/lib/aurora/thermostat.rb +57 -0
- data/lib/aurora/version.rb +3 -1
- data/lib/aurora.rb +9 -6
- data/lib/waterfurnace_aurora.rb +3 -1
- metadata +29 -24
- data/bin/aurora_fetch +0 -25
- data/bin/aurora_mqtt_bridge +0 -289
data/lib/aurora/iz2_zone.rb
CHANGED
@@ -1,29 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "aurora/thermostat"
|
4
|
+
|
1
5
|
module Aurora
|
2
|
-
class IZ2Zone
|
6
|
+
class IZ2Zone < Thermostat
|
3
7
|
attr_reader :zone_number,
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
:priority,
|
11
|
-
:size, :normalized_size,
|
12
|
-
:ambient_temperature,
|
13
|
-
:cooling_target_temperature,
|
14
|
-
:heating_target_temperature
|
8
|
+
:current_mode,
|
9
|
+
:current_fan_mode,
|
10
|
+
:fan_intermittent_on,
|
11
|
+
:fan_intermittent_off,
|
12
|
+
:priority,
|
13
|
+
:size, :normalized_size
|
15
14
|
|
16
15
|
def initialize(abc, zone_number)
|
17
|
-
|
16
|
+
super(abc)
|
18
17
|
@zone_number = zone_number
|
19
18
|
end
|
20
19
|
|
21
20
|
def refresh(registers)
|
22
|
-
@ambient_temperature = registers[
|
21
|
+
@ambient_temperature = registers[31_007 + (zone_number - 1) * 3]
|
23
22
|
|
24
|
-
config1 = registers[
|
25
|
-
config2 = registers[
|
26
|
-
config3 = registers[
|
23
|
+
config1 = registers[31_008 + (zone_number - 1) * 3]
|
24
|
+
config2 = registers[31_009 + (zone_number - 1) * 3]
|
25
|
+
config3 = registers[31_200 + (zone_number - 1) * 3]
|
27
26
|
|
28
27
|
@target_fan_mode = config1[:fan]
|
29
28
|
@fan_intermittent_on = config1[:on_time]
|
@@ -40,60 +39,47 @@ module Aurora
|
|
40
39
|
end
|
41
40
|
|
42
41
|
def target_mode=(value)
|
43
|
-
|
44
|
-
|
45
|
-
@abc.modbus_slave.holding_registers[
|
46
|
-
@target_mode =
|
42
|
+
return unless (raw_value = Aurora::HEATING_MODE.invert[value])
|
43
|
+
|
44
|
+
@abc.modbus_slave.holding_registers[21_202 + (zone_number - 1) * 9] = raw_value
|
45
|
+
@target_mode = value
|
47
46
|
end
|
48
47
|
|
49
48
|
def target_fan_mode=(value)
|
50
|
-
|
51
|
-
|
52
|
-
@abc.modbus_slave.holding_registers[
|
53
|
-
|
54
|
-
Aurora.transform_registers(registers)
|
55
|
-
@target_fan_mode = registers.first.last[:fan]
|
49
|
+
return unless (raw_value = Aurora::FAN_MODE.invert[value])
|
50
|
+
|
51
|
+
@abc.modbus_slave.holding_registers[21_205 + (zone_number - 1) * 9] = raw_value
|
52
|
+
@target_fan_mode = value
|
56
53
|
end
|
57
54
|
|
58
55
|
def fan_intermittent_on=(value)
|
59
|
-
return unless value >= 0 && value <= 25 && value % 5
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
@fan_intermittent_on = registers.first.last[:on_time]
|
56
|
+
return unless value >= 0 && value <= 25 && (value % 5).zero?
|
57
|
+
|
58
|
+
@abc.modbus_slave.holding_registers[21_206 + (zone_number - 1) * 9] = value
|
59
|
+
@fan_intermittent_on = value
|
64
60
|
end
|
65
61
|
|
66
62
|
def fan_intermittent_off=(value)
|
67
|
-
return unless value >= 0 && value <= 40 && value % 5
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
@fan_intermittent_on = registers.first.last[:off_time]
|
63
|
+
return unless value >= 0 && value <= 40 && (value % 5).zero?
|
64
|
+
|
65
|
+
@abc.modbus_slave.holding_registers[21_207 + (zone_number - 1) * 9] = value
|
66
|
+
@fan_intermittent_off = value
|
72
67
|
end
|
73
68
|
|
74
69
|
def heating_target_temperature=(value)
|
75
70
|
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
71
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
registers[base + 1][:heating_target_temperature]
|
72
|
+
raw_value = (value * 10).to_i
|
73
|
+
@abc.modbus_slave.holding_registers[21_203 + (zone_number - 1) * 9] = raw_value
|
74
|
+
@heating_target_temperature = value
|
83
75
|
end
|
84
76
|
|
85
77
|
def cooling_target_temperature=(value)
|
86
78
|
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
79
|
|
95
|
-
|
96
|
-
|
80
|
+
raw_value = (value * 10).to_i
|
81
|
+
@abc.modbus_slave.holding_registers[21_204 + (zone_number - 1) * 9] = value
|
82
|
+
@cooling_target_temperature = raw_value
|
97
83
|
end
|
98
84
|
end
|
99
85
|
end
|
data/lib/aurora/modbus/server.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Aurora
|
2
4
|
module ModBus
|
3
5
|
module Server
|
@@ -5,26 +7,30 @@ module Aurora
|
|
5
7
|
case func
|
6
8
|
when 65
|
7
9
|
# 1 function register, a multiple of two words
|
8
|
-
return unless (req.length - 1) % 4
|
10
|
+
return unless ((req.length - 1) % 4).zero?
|
11
|
+
|
9
12
|
params = []
|
10
|
-
req[1
|
13
|
+
req[1..].unpack("n*").each_slice(2) do |(addr, quant)|
|
11
14
|
params << { addr: addr, quant: quant }
|
12
15
|
end
|
13
16
|
params
|
14
17
|
when 66
|
15
|
-
return unless (req.length - 1) % 2
|
16
|
-
|
18
|
+
return unless ((req.length - 1) % 2).zero?
|
19
|
+
|
20
|
+
req[1..].unpack("n*")
|
17
21
|
when 67
|
18
22
|
# 1 function register, a multiple of two words
|
19
|
-
return unless (req.length - 1) % 4
|
23
|
+
return unless ((req.length - 1) % 4).zero?
|
24
|
+
|
20
25
|
params = []
|
21
|
-
req[1
|
26
|
+
req[1..].unpack("n*").each_slice(2) do |(addr, val)|
|
22
27
|
params << { addr: addr, val: val }
|
23
28
|
end
|
24
29
|
params
|
25
30
|
when 68
|
26
31
|
return unless req.length == 5
|
27
|
-
|
32
|
+
|
33
|
+
{ noidea1: req[1, 2].unpack("n"), noidea2: req[3, 2].unpack("n") }
|
28
34
|
else
|
29
35
|
super
|
30
36
|
end
|
@@ -33,7 +39,8 @@ module Aurora
|
|
33
39
|
def parse_response(func, res)
|
34
40
|
return {} if func == 67 && res.length == 1
|
35
41
|
return { noidea: res[-1].ord } if func == 68 && res.length == 2
|
36
|
-
|
42
|
+
|
43
|
+
func = 3 if [65, 66].include?(func)
|
37
44
|
super
|
38
45
|
end
|
39
46
|
|
@@ -46,17 +53,17 @@ module Aurora
|
|
46
53
|
return (func | 0x80).chr + err.chr
|
47
54
|
end
|
48
55
|
|
49
|
-
pdu += slave.holding_registers[param[:addr],param[:quant]].pack(
|
56
|
+
pdu += slave.holding_registers[param[:addr], param[:quant]].pack("n*")
|
50
57
|
end
|
51
|
-
|
52
|
-
|
58
|
+
func.chr + pdu.length.chr + pdu
|
59
|
+
|
53
60
|
when 66
|
54
|
-
pdu = params.map { |addr| slave.holding_registers[addr] }.pack(
|
55
|
-
|
56
|
-
|
61
|
+
pdu = params.map { |addr| slave.holding_registers[addr] }.pack("n*")
|
62
|
+
func.chr + pdu.length.chr + pdu
|
63
|
+
|
57
64
|
when 67
|
58
65
|
slave.holding_registers[param[:addr]] = param[:val]
|
59
|
-
pdu = req[0,2]
|
66
|
+
pdu = req[0, 2]
|
60
67
|
else
|
61
68
|
super
|
62
69
|
end
|
data/lib/aurora/modbus/slave.rb
CHANGED
@@ -1,13 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Aurora
|
2
4
|
module ModBus
|
3
5
|
module Slave
|
4
6
|
def read_multiple_holding_registers(*ranges)
|
5
7
|
values = if ranges.any? { |r| r.is_a?(Range) }
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
addrs_and_lengths = ranges.map do |r|
|
9
|
+
r = Array(r)
|
10
|
+
[r.first, r.last - r.first + 1]
|
11
|
+
end.flatten
|
12
|
+
query("A#{addrs_and_lengths.pack('n*')}").unpack("n*")
|
13
|
+
else
|
14
|
+
query("B#{ranges.pack('n*')}").unpack("n*")
|
15
|
+
end
|
11
16
|
ranges.map { |r| Array(r) }.flatten.zip(values).to_h
|
12
17
|
end
|
13
18
|
|
@@ -19,6 +24,7 @@ module Aurora
|
|
19
24
|
class WFProxy < ::ModBus::ReadWriteProxy
|
20
25
|
def [](*keys)
|
21
26
|
return super if keys.length == 1
|
27
|
+
|
22
28
|
@slave.read_multiple_holding_registers(*keys)
|
23
29
|
end
|
24
30
|
end
|
@@ -28,24 +34,25 @@ module Aurora
|
|
28
34
|
# Read the slave_id and function code
|
29
35
|
msg = read(io, 2)
|
30
36
|
log logging_bytes(msg)
|
31
|
-
|
37
|
+
|
32
38
|
function_code = msg.getbyte(1)
|
33
39
|
case function_code
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
40
|
+
when 1, 2, 3, 4, 65, 66
|
41
|
+
# read the third byte to find out how much more
|
42
|
+
# we need to read + CRC
|
43
|
+
msg += read(io, 1)
|
44
|
+
msg += read(io, msg.getbyte(2) + 2)
|
45
|
+
when 5, 6, 15, 16
|
46
|
+
# We just read in an additional 6 bytes
|
47
|
+
msg += read(io, 6)
|
48
|
+
when 22
|
49
|
+
msg += read(io, 8)
|
50
|
+
when 0x80..0xff
|
51
|
+
msg += read(io, 3)
|
52
|
+
else
|
53
|
+
raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
|
48
54
|
end
|
55
|
+
msg
|
49
56
|
end
|
50
57
|
end
|
51
58
|
end
|
data/lib/aurora/registers.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Aurora
|
2
|
-
|
4
|
+
module_function
|
3
5
|
|
4
6
|
def normalize_ranges(ranges)
|
5
7
|
registers = ranges.map { |r| Array(r) }.flatten.sort.uniq
|
@@ -9,26 +11,26 @@ module Aurora
|
|
9
11
|
count = 0
|
10
12
|
registers.each_with_index do |r, i|
|
11
13
|
run_start ||= r
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
count += range.count
|
28
|
-
result << range
|
14
|
+
next unless i + 1 == registers.length || r + 1 != registers[i + 1]
|
15
|
+
|
16
|
+
if r == run_start
|
17
|
+
result << r
|
18
|
+
if (count += 1) == 100
|
19
|
+
totals << result
|
20
|
+
result = []
|
21
|
+
count = 0
|
22
|
+
end
|
23
|
+
else
|
24
|
+
range = run_start..r
|
25
|
+
if count + range.count > 100
|
26
|
+
totals << result
|
27
|
+
result = []
|
28
|
+
count = 0
|
29
29
|
end
|
30
|
-
|
30
|
+
count += range.count
|
31
|
+
result << range
|
31
32
|
end
|
33
|
+
run_start = nil
|
32
34
|
end
|
33
35
|
totals << result unless result.empty?
|
34
36
|
totals
|
@@ -39,13 +41,13 @@ module Aurora
|
|
39
41
|
TO_LAST_LOCKOUT = ->(v) { v & 0x8000 == 0x8000 ? v & 0x7fff : nil }
|
40
42
|
NEGATABLE = ->(v) { v & 0x8000 == 0x8000 ? v - 0x10000 : v }
|
41
43
|
|
42
|
-
def from_bitmask(
|
44
|
+
def from_bitmask(value, flags)
|
43
45
|
result = []
|
44
46
|
flags.each do |(bit, flag)|
|
45
|
-
result << flag if
|
46
|
-
|
47
|
+
result << flag if value & bit == bit
|
48
|
+
value &= ~bit
|
47
49
|
end
|
48
|
-
result << "0x%04x"
|
50
|
+
result << format("0x%04x", value) unless value.zero?
|
49
51
|
result
|
50
52
|
end
|
51
53
|
|
@@ -53,7 +55,7 @@ module Aurora
|
|
53
55
|
puts "converting #{idx} of length #{length}"
|
54
56
|
(idx...(idx + length)).map do |i|
|
55
57
|
(registers[i] >> 8).chr + (registers[i] & 0xff).chr
|
56
|
-
end.join.sub(/[ \0]+$/,
|
58
|
+
end.join.sub(/[ \0]+$/, "")
|
57
59
|
end
|
58
60
|
|
59
61
|
FAULTS = {
|
@@ -106,26 +108,27 @@ module Aurora
|
|
106
108
|
75 => "Charge Loss",
|
107
109
|
76 => "Suction Temperatur Sensor Limit",
|
108
110
|
77 => "Leaving Air Temperature Sensor Limit",
|
109
|
-
78 => "Maximum Operating Pressure Limit"
|
110
|
-
}
|
111
|
+
78 => "Maximum Operating Pressure Limit"
|
112
|
+
}.freeze
|
111
113
|
|
112
114
|
AR_SETTINGS = {
|
113
115
|
0 => "Cycle with Compressor",
|
114
116
|
1 => "Cycle with Thermostat Humidification Call",
|
115
117
|
2 => "Slow Opening Water Valve",
|
116
118
|
3 => "Cycle with Blower"
|
117
|
-
}
|
119
|
+
}.freeze
|
120
|
+
|
121
|
+
def dipswitch_settings(value)
|
122
|
+
return :manual if value == 0x7fff
|
118
123
|
|
119
|
-
def dipswitch_settings(v)
|
120
|
-
return :manual if v == 0x7fff
|
121
124
|
{
|
122
|
-
fp1:
|
123
|
-
fp2:
|
124
|
-
rv:
|
125
|
-
ar: AR_SETTINGS[(
|
126
|
-
cc:
|
127
|
-
lo:
|
128
|
-
dh_rh:
|
125
|
+
fp1: value & 0x01 == 0x01 ? "30ºF" : "15ºF",
|
126
|
+
fp2: value & 0x02 == 0x02 ? "30ºF" : "15ºF",
|
127
|
+
rv: value & 0x04 == 0x04 ? "O" : "B",
|
128
|
+
ar: AR_SETTINGS[(value >> 3) & 0x7],
|
129
|
+
cc: value & 0x20 == 0x20 ? "Single Stage" : "Dual Stage",
|
130
|
+
lo: value & 0x40 == 0x40 ? "Continouous" : "Pulse",
|
131
|
+
dh_rh: value & 0x80 == 0x80 ? "Dehumdifier On" : "Reheat On"
|
129
132
|
}
|
130
133
|
end
|
131
134
|
|
@@ -138,8 +141,8 @@ module Aurora
|
|
138
141
|
0x20 => :eh2,
|
139
142
|
0x200 => :accessory,
|
140
143
|
0x400 => :lockout,
|
141
|
-
0x800 => :alarm
|
142
|
-
}
|
144
|
+
0x800 => :alarm
|
145
|
+
}.freeze
|
143
146
|
|
144
147
|
SYSTEM_INPUTS = {
|
145
148
|
0x01 => "Y1",
|
@@ -149,18 +152,18 @@ module Aurora
|
|
149
152
|
0x10 => "G",
|
150
153
|
0x20 => "Dehumidifer",
|
151
154
|
0x40 => "Emergency Shutdown",
|
152
|
-
0x200 => "Load Shed"
|
153
|
-
}
|
155
|
+
0x200 => "Load Shed"
|
156
|
+
}.freeze
|
154
157
|
|
155
|
-
def status(
|
158
|
+
def status(value)
|
156
159
|
result = {
|
157
|
-
lps:
|
158
|
-
hps:
|
160
|
+
lps: value & 0x80 == 0x80 ? :closed : :open,
|
161
|
+
hps: value & 0x100 == 0x100 ? :closed : :open
|
159
162
|
}
|
160
|
-
result[:load_shed] = true if
|
161
|
-
result[:emergency_shutdown] = true if
|
162
|
-
leftover =
|
163
|
-
result[:unknown] = "0x%04x"
|
163
|
+
result[:load_shed] = true if value & 0x0200 == 0x0200
|
164
|
+
result[:emergency_shutdown] = true if value & 0x0040 == 0x0040
|
165
|
+
leftover = value & ~0x03c0
|
166
|
+
result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
|
164
167
|
result
|
165
168
|
end
|
166
169
|
|
@@ -169,18 +172,18 @@ module Aurora
|
|
169
172
|
0x04 => "Low Suction Pressure",
|
170
173
|
0x10 => "Low Discharge Pressure",
|
171
174
|
0x20 => "High Discharge Pressure",
|
172
|
-
0x40 => "Output Power Limit"
|
173
|
-
}
|
175
|
+
0x40 => "Output Power Limit"
|
176
|
+
}.freeze
|
174
177
|
|
175
178
|
VS_SAFE_MODE = {
|
176
179
|
0x01 => "EEV Indoor Failed",
|
177
180
|
0x02 => "EEV Outdoor Failed",
|
178
|
-
0x04 => "Invalid Ambient Temp"
|
179
|
-
}
|
181
|
+
0x04 => "Invalid Ambient Temp"
|
182
|
+
}.freeze
|
180
183
|
|
181
184
|
VS_ALARM1 = {
|
182
|
-
0x8000 => "Internal Error"
|
183
|
-
}
|
185
|
+
0x8000 => "Internal Error"
|
186
|
+
}.freeze
|
184
187
|
|
185
188
|
VS_ALARM2 = {
|
186
189
|
0x0001 => "Multi Safe Modes",
|
@@ -196,24 +199,24 @@ module Aurora
|
|
196
199
|
0x0400 => "DC Under Voltage",
|
197
200
|
0x0800 => "Invalid Suction Pressure",
|
198
201
|
0x1000 => "Invalid Discharge Pressure",
|
199
|
-
0x2000 => "Low Discharge Pressure"
|
200
|
-
}
|
202
|
+
0x2000 => "Low Discharge Pressure"
|
203
|
+
}.freeze
|
201
204
|
|
202
205
|
VS_EEV2 = {
|
203
206
|
0x0010 => "Invalid Suction Temperature",
|
204
207
|
0x0020 => "Invalid Leaving Air Temperature",
|
205
|
-
0x0040 => "Invalid Suction Pressure"
|
206
|
-
|
207
|
-
}
|
208
|
+
0x0040 => "Invalid Suction Pressure"
|
209
|
+
|
210
|
+
}.freeze
|
208
211
|
|
209
212
|
AXB_INPUTS = {
|
210
|
-
}
|
213
|
+
}.freeze
|
211
214
|
|
212
215
|
AXB_OUTPUTS = {
|
213
216
|
0x10 => "Accessory 2",
|
214
217
|
0x02 => "Loop Pump",
|
215
218
|
0x01 => "DHW"
|
216
|
-
}
|
219
|
+
}.freeze
|
217
220
|
|
218
221
|
HEATING_MODE = {
|
219
222
|
0 => :off,
|
@@ -221,30 +224,30 @@ module Aurora
|
|
221
224
|
2 => :cool,
|
222
225
|
3 => :heat,
|
223
226
|
4 => :eheat
|
224
|
-
}
|
227
|
+
}.freeze
|
225
228
|
|
226
229
|
FAN_MODE = {
|
227
230
|
0 => :auto,
|
228
231
|
1 => :continuous,
|
229
232
|
2 => :intermittent
|
230
|
-
}
|
233
|
+
}.freeze
|
231
234
|
|
232
235
|
HUMIDIFIER_SETTINGS = {
|
233
236
|
0x4000 => :auto_dehumidification,
|
234
|
-
0x8000 => :auto_humidification
|
235
|
-
}
|
237
|
+
0x8000 => :auto_humidification
|
238
|
+
}.freeze
|
236
239
|
|
237
240
|
INVERSE_HUMIDIFIER_SETTINGS = {
|
238
241
|
0x4000 => :manual_dehumidification,
|
239
|
-
0x8000 => :manual_humidification
|
240
|
-
}
|
242
|
+
0x8000 => :manual_humidification
|
243
|
+
}.freeze
|
241
244
|
|
242
245
|
ZONE_SIZES = {
|
243
246
|
0 => 0,
|
244
247
|
1 => 25,
|
245
248
|
2 => 45,
|
246
|
-
3 => 70
|
247
|
-
}
|
249
|
+
3 => 70
|
250
|
+
}.freeze
|
248
251
|
|
249
252
|
CALLS = {
|
250
253
|
0x0 => :standby,
|
@@ -254,39 +257,39 @@ module Aurora
|
|
254
257
|
0x4 => :h3,
|
255
258
|
0x5 => :c1,
|
256
259
|
0x6 => :c2,
|
257
|
-
0x7 => :unknown7
|
258
|
-
}
|
260
|
+
0x7 => :unknown7
|
261
|
+
}.freeze
|
259
262
|
|
260
|
-
def iz2_demand(
|
263
|
+
def iz2_demand(value)
|
261
264
|
{
|
262
|
-
fan_demand:
|
263
|
-
unit_demand:
|
265
|
+
fan_demand: value >> 8,
|
266
|
+
unit_demand: value & 0xff
|
264
267
|
}
|
265
268
|
end
|
266
269
|
|
267
|
-
def zone_configuration1(
|
268
|
-
fan = if
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
270
|
+
def zone_configuration1(value)
|
271
|
+
fan = if value & 0x80 == 0x80
|
272
|
+
:continuous
|
273
|
+
elsif value & 0x100 == 0x100
|
274
|
+
:intermittent
|
275
|
+
else
|
276
|
+
:auto
|
277
|
+
end
|
275
278
|
result = {
|
276
279
|
fan: fan,
|
277
|
-
on_time: ((
|
278
|
-
off_time: (((
|
279
|
-
cooling_target_temperature: ((
|
280
|
-
heating_target_temperature_carry:
|
280
|
+
on_time: ((value >> 9) & 0x7) * 5,
|
281
|
+
off_time: (((value >> 12) & 0x7) + 1) * 5,
|
282
|
+
cooling_target_temperature: ((value & 0x7e) >> 1) + 36,
|
283
|
+
heating_target_temperature_carry: value & 0o1
|
281
284
|
}
|
282
|
-
leftover =
|
283
|
-
result[:unknown] = "0x%04x"
|
285
|
+
leftover = value & ~0x7fff
|
286
|
+
result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
|
284
287
|
result
|
285
288
|
end
|
286
289
|
|
287
|
-
def zone_configuration2(registers,
|
288
|
-
prior_v = registers[
|
289
|
-
v = registers[
|
290
|
+
def zone_configuration2(registers, key)
|
291
|
+
prior_v = registers[key - 1] if registers.key?(key - 1)
|
292
|
+
v = registers[key]
|
290
293
|
result = {
|
291
294
|
call: CALLS[(v >> 1) & 0x7],
|
292
295
|
mode: HEATING_MODE[(v >> 8) & 0x03],
|
@@ -297,20 +300,20 @@ module Aurora
|
|
297
300
|
result[:heating_target_temperature] = ((carry << 5) | ((v & 0xf800) >> 11)) + 36
|
298
301
|
end
|
299
302
|
leftover = v & ~0xfb1e
|
300
|
-
result[:unknown] = "0x%04x"
|
303
|
+
result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
|
301
304
|
result
|
302
305
|
end
|
303
306
|
|
304
307
|
# hi order byte is normalized zone size
|
305
|
-
def zone_configuration3(
|
306
|
-
size = (
|
308
|
+
def zone_configuration3(value)
|
309
|
+
size = (value >> 3) & 0x3
|
307
310
|
result = {
|
308
|
-
zone_priority: (
|
311
|
+
zone_priority: (value & 0x20) == 0x20 ? :economy : :comfort,
|
309
312
|
zone_size: ZONE_SIZES[size],
|
310
|
-
normalized_size:
|
313
|
+
normalized_size: value >> 8
|
311
314
|
}
|
312
|
-
leftover =
|
313
|
-
result[:unknown] = "0x%04x"
|
315
|
+
leftover = value & ~0xff38
|
316
|
+
result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
|
314
317
|
result
|
315
318
|
end
|
316
319
|
|
@@ -320,15 +323,16 @@ module Aurora
|
|
320
323
|
REGISTER_CONVERTERS = {
|
321
324
|
TO_HUNDREDTHS => [2, 3, 807, 813, 816, 817, 819, 820, 825, 828],
|
322
325
|
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
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
326
|
+
TO_TENTHS => [19, 20, 401, 567, 740, 745, 746, 747, 900, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117, 1134, 1136,
|
327
|
+
12_619, 12_620,
|
328
|
+
21_203, 21_204,
|
329
|
+
21_212, 21_213,
|
330
|
+
21_221, 21_222,
|
331
|
+
21_230, 22_131,
|
332
|
+
21_239, 21_240,
|
333
|
+
21_248, 21_249,
|
334
|
+
31_003,
|
335
|
+
31_007, 31_010, 31_013, 31_016, 31_019, 31_022],
|
332
336
|
TO_LAST_LOCKOUT => [26],
|
333
337
|
->(v) { from_bitmask(v, SYSTEM_OUTPUTS) } => [27, 30],
|
334
338
|
->(v) { from_bitmask(v, SYSTEM_INPUTS) } => [28],
|
@@ -345,41 +349,42 @@ module Aurora
|
|
345
349
|
->(v) { from_bitmask(v, AXB_INPUTS) } => [1103],
|
346
350
|
->(v) { from_bitmask(v, AXB_OUTPUTS) } => [1104],
|
347
351
|
->(v) { TO_TENTHS.call(NEGATABLE.call(v)) } => [1136],
|
348
|
-
->(v) { HEATING_MODE[v] } => [
|
349
|
-
->(v) { FAN_MODE[v] } => [
|
350
|
-
->(v) { from_bitmask(v, HUMIDIFIER_SETTINGS) } => [
|
351
|
-
->(v) { { humidification_target: v >> 8, dehumidification_target: v & 0xff } } => [
|
352
|
-
method(:iz2_demand) => [
|
353
|
-
method(:zone_configuration1) => [
|
354
|
-
method(:zone_configuration2) => [
|
355
|
-
method(:zone_configuration3) => [
|
356
|
-
->(registers, idx) { to_string(registers, idx, 13) } => [
|
357
|
-
->(registers, idx) { to_string(registers, idx, 8) } => [
|
358
|
-
->(registers, idx) { to_string(registers, idx, 13) } => [
|
359
|
-
->(registers, idx) { to_string(registers, idx, 13) } => [
|
360
|
-
->(registers, idx) { to_string(registers, idx, 13) } => [
|
361
|
-
->(registers, idx) { to_string(registers, idx, 13) } => [
|
362
|
-
}
|
352
|
+
->(v) { HEATING_MODE[v] } => [12_602, 21_202, 21_211, 21_220, 21_229, 21_238, 21_247],
|
353
|
+
->(v) { FAN_MODE[v] } => [12_621, 21_205, 21_214, 21_223, 21_232, 21_241, 21_250],
|
354
|
+
->(v) { from_bitmask(v, HUMIDIFIER_SETTINGS) } => [31_109],
|
355
|
+
->(v) { { humidification_target: v >> 8, dehumidification_target: v & 0xff } } => [31_110],
|
356
|
+
method(:iz2_demand) => [31_005],
|
357
|
+
method(:zone_configuration1) => [31_008, 31_011, 31_014, 31_017, 31_020, 31_023],
|
358
|
+
method(:zone_configuration2) => [31_009, 31_012, 31_015, 31_018, 31_021, 31_024],
|
359
|
+
method(:zone_configuration3) => [31_200, 31_203, 31_206, 31_209, 31_212, 31_215],
|
360
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31_400],
|
361
|
+
->(registers, idx) { to_string(registers, idx, 8) } => [31_413],
|
362
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31_421],
|
363
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31_434],
|
364
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31_447],
|
365
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31_460]
|
366
|
+
}.freeze
|
363
367
|
|
364
368
|
REGISTER_FORMATS = {
|
365
369
|
"%ds" => [1, 6, 9, 15, 84, 85],
|
366
370
|
"%dV" => [16, 112],
|
367
|
-
"%0.1fºF" => [19, 20, 401, 567, 740, 745, 746, 900, 1110, 1111, 1114, 1134, 1136,
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
371
|
+
"%0.1fºF" => [19, 20, 401, 567, 740, 745, 746, 747, 900, 1110, 1111, 1114, 1134, 1136,
|
372
|
+
12_619, 12_620,
|
373
|
+
21_203, 21_204,
|
374
|
+
21_212, 21_213,
|
375
|
+
21_221, 21_222,
|
376
|
+
21_230, 21_231,
|
377
|
+
21_239, 21_240,
|
378
|
+
21_248, 21_249,
|
379
|
+
31_003,
|
380
|
+
31_007, 31_010, 31_013, 31_016, 31_019, 31_022],
|
376
381
|
"E%d" => [25, 26],
|
377
382
|
"%d%%" => [282, 321, 322, 346, 565, 741],
|
378
383
|
"%0.1fA" => [1105, 1106, 1107, 1108],
|
379
384
|
"%0.1fgpm" => [1117],
|
380
385
|
"%dW" => [1147, 1149, 1151, 1153, 1165],
|
381
|
-
"%dBtuh" => [1157]
|
382
|
-
}
|
386
|
+
"%dBtuh" => [1157]
|
387
|
+
}.freeze
|
383
388
|
|
384
389
|
def ignore(range)
|
385
390
|
range.zip(Array.new(range.count)).to_h
|
@@ -391,11 +396,11 @@ module Aurora
|
|
391
396
|
|
392
397
|
def zone_registers
|
393
398
|
(1..6).map do |i|
|
394
|
-
base1 =
|
395
|
-
base2 =
|
396
|
-
base3 =
|
399
|
+
base1 = 21_202 + (i - 1) * 9
|
400
|
+
base2 = 31_007 + (i - 1) * 3
|
401
|
+
base3 = 31_200 + (i - 1) * 3
|
397
402
|
{
|
398
|
-
base1 => "Zone #{i} Heating Mode",
|
403
|
+
base1 => "Zone #{i} Heating Mode (write)",
|
399
404
|
(base1 + 1) => "Zone #{i} Heating Setpoint (write)",
|
400
405
|
(base1 + 2) => "Zone #{i} Cooling Setpoint (write)",
|
401
406
|
(base1 + 3) => "Zone #{i} Fan Mode (write)",
|
@@ -404,12 +409,12 @@ module Aurora
|
|
404
409
|
base2 => "Zone #{i} Ambient Temperature",
|
405
410
|
(base2 + 1) => "Zone #{i} Configuration 1",
|
406
411
|
(base2 + 2) => "Zone #{i} Configuration 2",
|
407
|
-
base3 => "Zone #{i} Configuration 3"
|
412
|
+
base3 => "Zone #{i} Configuration 3"
|
408
413
|
}
|
409
414
|
end.inject({}, &:merge)
|
410
415
|
end
|
411
416
|
|
412
|
-
WRITEABLE = [112, 340, 341, 342, 346, 347]
|
417
|
+
WRITEABLE = [112, 340, 341, 342, 346, 347].freeze
|
413
418
|
|
414
419
|
# these are the valid ranges (i.e. the ABC will return _some_ value)
|
415
420
|
# * means 6 sequential ranges of equal size (i.e. must be repeated for each
|
@@ -453,42 +458,42 @@ module Aurora
|
|
453
458
|
3800..3809,
|
454
459
|
3818..3834,
|
455
460
|
3900..3914,
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
]
|
461
|
+
12_000..12_019,
|
462
|
+
12_098..12_099,
|
463
|
+
12_100..12_119,
|
464
|
+
12_200..12_239,
|
465
|
+
12_300..12_319,
|
466
|
+
12_400..12_569,
|
467
|
+
12_600..12_639,
|
468
|
+
12_700..12_799,
|
469
|
+
20_000..20_099,
|
470
|
+
21_100..21_136,
|
471
|
+
21_200..21_265,
|
472
|
+
21_400..21_472,
|
473
|
+
21_500..21_589,
|
474
|
+
22_100..22_162, # *
|
475
|
+
22_200..22_262, # *
|
476
|
+
22_300..22_362, # *
|
477
|
+
22_400..22_462, # *
|
478
|
+
22_500..22_562, # *
|
479
|
+
22_600..22_662, # *
|
480
|
+
30_000..30_099,
|
481
|
+
31_000..31_034,
|
482
|
+
31_100..31_129,
|
483
|
+
31_200..31_229,
|
484
|
+
31_300..31_329,
|
485
|
+
31_400..31_472,
|
486
|
+
32_100..32_162, # *
|
487
|
+
32_200..32_262, # *
|
488
|
+
32_300..32_362, # *
|
489
|
+
32_400..32_462, # *
|
490
|
+
32_500..32_562, # *
|
491
|
+
32_600..32_662, # *
|
492
|
+
60_050..60_053,
|
493
|
+
60_100..60_109,
|
494
|
+
60_200..60_200,
|
495
|
+
61_000..61_009
|
496
|
+
].freeze
|
492
497
|
|
493
498
|
def read_all_registers(modbus_slave)
|
494
499
|
result = []
|
@@ -501,10 +506,10 @@ module Aurora
|
|
501
506
|
REGISTER_RANGES.map(&:to_a).flatten.zip(result).to_h
|
502
507
|
end
|
503
508
|
|
504
|
-
def diff_registers(
|
509
|
+
def diff_registers(lhs, rhs)
|
505
510
|
diff = {}
|
506
|
-
|
507
|
-
diff[k] = [
|
511
|
+
lhs.each_key do |k|
|
512
|
+
diff[k] = [lhs[k], rhs[k]] if lhs[k] != rhs[k]
|
508
513
|
end
|
509
514
|
diff
|
510
515
|
end
|
@@ -539,7 +544,7 @@ module Aurora
|
|
539
544
|
92 => "Model Number",
|
540
545
|
105 => "Serial Number",
|
541
546
|
112 => "Setup Line Voltage",
|
542
|
-
201 => "Discharge Pressure", # I can't figure out how this number is represented;
|
547
|
+
201 => "Discharge Pressure", # I can't figure out how this number is represented;
|
543
548
|
203 => "Suction Pressure",
|
544
549
|
205 => "Discharge Temperature",
|
545
550
|
207 => "Loop Entering Water Temperature",
|
@@ -565,7 +570,7 @@ module Aurora
|
|
565
570
|
344 => "ECM Speed",
|
566
571
|
346 => "Cooling Airflow Adjustment",
|
567
572
|
347 => "Aux Heat ECM Speed",
|
568
|
-
362 => "Active Dehumidify", # any value is true
|
573
|
+
362 => "Active Dehumidify", # any value is true
|
569
574
|
401 => "DHW Setpoint",
|
570
575
|
414 => "On Peak/SmartGrid 2", # 0x0001 only
|
571
576
|
483 => "Number of IZ2 Zones",
|
@@ -576,6 +581,7 @@ module Aurora
|
|
576
581
|
741 => "Relative Humidity",
|
577
582
|
745 => "Heating Set Point",
|
578
583
|
746 => "Cooling Set Point",
|
584
|
+
747 => "Ambient Temperature",
|
579
585
|
807 => "AXB Version",
|
580
586
|
813 => "IZ2 Version?",
|
581
587
|
816 => "AOC Version 1?",
|
@@ -604,28 +610,32 @@ module Aurora
|
|
604
610
|
1153 => "Total Watts",
|
605
611
|
1157 => "Ht of Rej",
|
606
612
|
1165 => "VS Pump Watts",
|
613
|
+
12_602 => "Heating Mode (write)",
|
614
|
+
12_619 => "Heating Setpoint (write)",
|
615
|
+
12_620 => "Cooling Setpoint (write)",
|
616
|
+
12_621 => "Fan Mode (write)",
|
607
617
|
3027 => "Compressor Speed",
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
}.merge(ignore(89..91))
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
618
|
+
31_003 => "Outdoor Temp",
|
619
|
+
31_005 => "IZ2 Demand",
|
620
|
+
31_109 => "Humidifier Mode", # write to 21114
|
621
|
+
31_110 => "Manual De/Humidification Target", # write to 21115
|
622
|
+
31_400 => "Dealer Name",
|
623
|
+
31_413 => "Dealer Phone",
|
624
|
+
31_421 => "Dealer Address 1",
|
625
|
+
31_434 => "Dealer Address 2",
|
626
|
+
31_447 => "Dealer Email",
|
627
|
+
31_460 => "Dealer Website"
|
628
|
+
}.merge(ignore(89..91))
|
629
|
+
.merge(ignore(93..104))
|
630
|
+
.merge(ignore(106..109))
|
631
|
+
.merge(faults(601..699))
|
632
|
+
.merge(zone_registers)
|
633
|
+
.merge(ignore(31_401..31_412))
|
634
|
+
.merge(ignore(31_414..31_420))
|
635
|
+
.merge(ignore(31_422..31_433))
|
636
|
+
.merge(ignore(31_435..31_446))
|
637
|
+
.merge(ignore(31_447..31_459))
|
638
|
+
.merge(ignore(31_461..31_472))
|
629
639
|
|
630
640
|
def transform_registers(registers)
|
631
641
|
registers.each do |(k, v)|
|
@@ -640,19 +650,20 @@ module Aurora
|
|
640
650
|
|
641
651
|
def print_registers(registers)
|
642
652
|
result = []
|
643
|
-
registers.each do |(k,
|
653
|
+
registers.each do |(k, value)|
|
644
654
|
# ignored
|
645
655
|
next if REGISTER_NAMES.key?(k) && REGISTER_NAMES[k].nil?
|
656
|
+
|
646
657
|
name = REGISTER_NAMES[k]
|
647
658
|
value_proc = REGISTER_CONVERTERS.find { |(_, z)| z.include?(k) }&.first || ->(v) { v }
|
648
659
|
format = REGISTER_FORMATS.find { |(_, z)| z.include?(k) }&.first || "%s"
|
649
660
|
format = "%1$d (0x%1$04x)" unless name
|
650
661
|
name ||= "???"
|
651
|
-
|
652
|
-
value = value_proc.arity == 2 ? value_proc.call(registers, k) : value_proc.call(
|
662
|
+
|
663
|
+
value = value_proc.arity == 2 ? value_proc.call(registers, k) : value_proc.call(value)
|
653
664
|
value = value.join(", ") if value.is_a?(Array)
|
654
|
-
value =
|
655
|
-
|
665
|
+
value = format(format, value) if value
|
666
|
+
|
656
667
|
result << "#{name} (#{k}): #{value}"
|
657
668
|
end
|
658
669
|
result.join("\n")
|