waterfurnace_aurora 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/aurora_fetch +26 -0
- data/{bin → exe}/aurora_mock +23 -20
- data/{bin → exe}/aurora_monitor +21 -18
- data/{bin → exe}/aurora_mqtt_bridge +47 -47
- data/{bin → exe}/registers.yml +0 -0
- data/lib/aurora/abc_client.rb +39 -35
- data/lib/aurora/iz2_zone.rb +40 -30
- data/lib/aurora/modbus/server.rb +22 -15
- data/lib/aurora/modbus/slave.rb +27 -20
- data/lib/aurora/registers.rb +206 -202
- data/lib/aurora/version.rb +3 -1
- data/lib/aurora.rb +8 -6
- data/lib/waterfurnace_aurora.rb +3 -1
- metadata +26 -23
- data/bin/aurora_fetch +0 -25
data/lib/aurora/iz2_zone.rb
CHANGED
@@ -1,17 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Aurora
|
2
4
|
class IZ2Zone
|
3
5
|
attr_reader :zone_number,
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
:target_mode,
|
7
|
+
:current_mode,
|
8
|
+
:target_fan_mode,
|
9
|
+
:current_fan_mode,
|
10
|
+
:fan_intermittent_on,
|
11
|
+
:fan_intermittent_off,
|
12
|
+
:priority,
|
13
|
+
:size, :normalized_size,
|
14
|
+
:ambient_temperature,
|
15
|
+
:cooling_target_temperature,
|
16
|
+
:heating_target_temperature
|
15
17
|
|
16
18
|
def initialize(abc, zone_number)
|
17
19
|
@abc = abc
|
@@ -19,11 +21,11 @@ module Aurora
|
|
19
21
|
end
|
20
22
|
|
21
23
|
def refresh(registers)
|
22
|
-
@ambient_temperature = registers[
|
24
|
+
@ambient_temperature = registers[31_007 + (zone_number - 1) * 3]
|
23
25
|
|
24
|
-
config1 = registers[
|
25
|
-
config2 = registers[
|
26
|
-
config3 = registers[
|
26
|
+
config1 = registers[31_008 + (zone_number - 1) * 3]
|
27
|
+
config2 = registers[31_009 + (zone_number - 1) * 3]
|
28
|
+
config3 = registers[31_200 + (zone_number - 1) * 3]
|
27
29
|
|
28
30
|
@target_fan_mode = config1[:fan]
|
29
31
|
@fan_intermittent_on = config1[:on_time]
|
@@ -42,41 +44,46 @@ module Aurora
|
|
42
44
|
def target_mode=(value)
|
43
45
|
value = Aurora::HEATING_MODE.invert[value]
|
44
46
|
return unless value
|
45
|
-
|
46
|
-
@
|
47
|
+
|
48
|
+
@abc.modbus_slave.holding_registers[21_202 + (zone_number - 1) * 9] = value
|
49
|
+
@target_mode = Aurora::HEATING_MODE[@abc.modbus_slave.holding_registers[21_202 + (zone_number - 1) * 9]]
|
47
50
|
end
|
48
51
|
|
49
52
|
def target_fan_mode=(value)
|
50
53
|
value = Aurora::FAN_MODE.invert[value]
|
51
54
|
return unless value
|
52
|
-
|
53
|
-
|
55
|
+
|
56
|
+
@abc.modbus_slave.holding_registers[21_205 + (zone_number - 1) * 9] = value
|
57
|
+
registers = @abc.modbus_slave.read_multiple_holding_registers(31_008 + (zone_number - 1) * 3)
|
54
58
|
Aurora.transform_registers(registers)
|
55
59
|
@target_fan_mode = registers.first.last[:fan]
|
56
60
|
end
|
57
61
|
|
58
62
|
def fan_intermittent_on=(value)
|
59
|
-
return unless value >= 0 && value <= 25 && value % 5
|
60
|
-
|
61
|
-
|
63
|
+
return unless value >= 0 && value <= 25 && (value % 5).zero?
|
64
|
+
|
65
|
+
@abc.modbus_slave.holding_registers[21_206 + (zone_number - 1) * 9] = value
|
66
|
+
registers = @abc.modbus_slave.read_multiple_holding_registers(31_008 + (zone_number - 1) * 3)
|
62
67
|
Aurora.transform_registers(registers)
|
63
68
|
@fan_intermittent_on = registers.first.last[:on_time]
|
64
69
|
end
|
65
70
|
|
66
71
|
def fan_intermittent_off=(value)
|
67
|
-
return unless value >= 0 && value <= 40 && value % 5
|
68
|
-
|
69
|
-
|
72
|
+
return unless value >= 0 && value <= 40 && (value % 5).zero?
|
73
|
+
|
74
|
+
@abc.modbus_slave.holding_registers[21_207 + (zone_number - 1) * 9] = value
|
75
|
+
registers = @abc.modbus_slave.read_multiple_holding_registers(31_008 + (zone_number - 1) * 3)
|
70
76
|
Aurora.transform_registers(registers)
|
71
77
|
@fan_intermittent_on = registers.first.last[:off_time]
|
72
78
|
end
|
73
79
|
|
74
80
|
def heating_target_temperature=(value)
|
75
81
|
return unless value >= 40 && value <= 90
|
82
|
+
|
76
83
|
value = (value * 10).to_i
|
77
|
-
@abc.modbus_slave.holding_registers[
|
84
|
+
@abc.modbus_slave.holding_registers[21_203 + (zone_number - 1) * 9] = value
|
78
85
|
|
79
|
-
base =
|
86
|
+
base = 31_008 + (zone_number - 1) * 3
|
80
87
|
registers = @abc.modbus_slave.read_multiple_holding_registers(base..(base + 1))
|
81
88
|
Aurora.transform_registers(registers)
|
82
89
|
registers[base + 1][:heating_target_temperature]
|
@@ -84,16 +91,19 @@ module Aurora
|
|
84
91
|
|
85
92
|
def cooling_target_temperature=(value)
|
86
93
|
return unless value >= 54 && value <= 99
|
94
|
+
|
87
95
|
value = (value * 10).to_i
|
88
|
-
@abc.modbus_slave.holding_registers[
|
96
|
+
@abc.modbus_slave.holding_registers[21_204 + (zone_number - 1) * 9] = value
|
89
97
|
|
90
|
-
registers = @abc.modbus_slave.read_multiple_holding_registers(
|
98
|
+
registers = @abc.modbus_slave.read_multiple_holding_registers(31_008 + (zone_number - 1) * 3)
|
91
99
|
Aurora.transform_registers(registers)
|
92
100
|
registers.first.last[:cooling_target_temperature]
|
93
101
|
end
|
94
102
|
|
95
103
|
def inspect
|
96
|
-
"#<Aurora::IZ2Zone #{(instance_variables - [:@abc]).map
|
104
|
+
"#<Aurora::IZ2Zone #{(instance_variables - [:@abc]).map do |iv|
|
105
|
+
"#{iv}=#{instance_variable_get(iv).inspect}"
|
106
|
+
end.join(', ')}>"
|
97
107
|
end
|
98
108
|
end
|
99
109
|
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
|
|
@@ -321,14 +324,14 @@ module Aurora
|
|
321
324
|
TO_HUNDREDTHS => [2, 3, 807, 813, 816, 817, 819, 820, 825, 828],
|
322
325
|
method(:dipswitch_settings) => [4, 33],
|
323
326
|
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
|
-
|
327
|
+
21_203, 21_204,
|
328
|
+
21_212, 21_213,
|
329
|
+
21_221, 21_222,
|
330
|
+
21_230, 22_131,
|
331
|
+
21_239, 21_240,
|
332
|
+
21_248, 21_249,
|
333
|
+
31_003,
|
334
|
+
31_007, 31_010, 31_013, 31_016, 31_019, 31_022],
|
332
335
|
TO_LAST_LOCKOUT => [26],
|
333
336
|
->(v) { from_bitmask(v, SYSTEM_OUTPUTS) } => [27, 30],
|
334
337
|
->(v) { from_bitmask(v, SYSTEM_INPUTS) } => [28],
|
@@ -345,41 +348,41 @@ module Aurora
|
|
345
348
|
->(v) { from_bitmask(v, AXB_INPUTS) } => [1103],
|
346
349
|
->(v) { from_bitmask(v, AXB_OUTPUTS) } => [1104],
|
347
350
|
->(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
|
-
}
|
351
|
+
->(v) { HEATING_MODE[v] } => [21_202, 21_211, 21_220, 21_229, 21_238, 21_247],
|
352
|
+
->(v) { FAN_MODE[v] } => [21_205, 21_214, 21_223, 21_232, 21_241, 21_250],
|
353
|
+
->(v) { from_bitmask(v, HUMIDIFIER_SETTINGS) } => [31_109],
|
354
|
+
->(v) { { humidification_target: v >> 8, dehumidification_target: v & 0xff } } => [31_110],
|
355
|
+
method(:iz2_demand) => [31_005],
|
356
|
+
method(:zone_configuration1) => [31_008, 31_011, 31_014, 31_017, 31_020, 31_023],
|
357
|
+
method(:zone_configuration2) => [31_009, 31_012, 31_015, 31_018, 31_021, 31_024],
|
358
|
+
method(:zone_configuration3) => [31_200, 31_203, 31_206, 31_209, 31_212, 31_215],
|
359
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31_400],
|
360
|
+
->(registers, idx) { to_string(registers, idx, 8) } => [31_413],
|
361
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31_421],
|
362
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31_434],
|
363
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31_447],
|
364
|
+
->(registers, idx) { to_string(registers, idx, 13) } => [31_460]
|
365
|
+
}.freeze
|
363
366
|
|
364
367
|
REGISTER_FORMATS = {
|
365
368
|
"%ds" => [1, 6, 9, 15, 84, 85],
|
366
369
|
"%dV" => [16, 112],
|
367
370
|
"%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
|
+
21_203, 21_204,
|
372
|
+
21_212, 21_213,
|
373
|
+
21_221, 21_222,
|
374
|
+
21_230, 21_231,
|
375
|
+
21_239, 21_240,
|
376
|
+
21_248, 21_249,
|
377
|
+
31_003,
|
378
|
+
31_007, 31_010, 31_013, 31_016, 31_019, 31_022],
|
376
379
|
"E%d" => [25, 26],
|
377
380
|
"%d%%" => [282, 321, 322, 346, 565, 741],
|
378
381
|
"%0.1fA" => [1105, 1106, 1107, 1108],
|
379
382
|
"%0.1fgpm" => [1117],
|
380
383
|
"%dW" => [1147, 1149, 1151, 1153, 1165],
|
381
|
-
"%dBtuh" => [1157]
|
382
|
-
}
|
384
|
+
"%dBtuh" => [1157]
|
385
|
+
}.freeze
|
383
386
|
|
384
387
|
def ignore(range)
|
385
388
|
range.zip(Array.new(range.count)).to_h
|
@@ -391,9 +394,9 @@ module Aurora
|
|
391
394
|
|
392
395
|
def zone_registers
|
393
396
|
(1..6).map do |i|
|
394
|
-
base1 =
|
395
|
-
base2 =
|
396
|
-
base3 =
|
397
|
+
base1 = 21_202 + (i - 1) * 9
|
398
|
+
base2 = 31_007 + (i - 1) * 3
|
399
|
+
base3 = 31_200 + (i - 1) * 3
|
397
400
|
{
|
398
401
|
base1 => "Zone #{i} Heating Mode",
|
399
402
|
(base1 + 1) => "Zone #{i} Heating Setpoint (write)",
|
@@ -404,12 +407,12 @@ module Aurora
|
|
404
407
|
base2 => "Zone #{i} Ambient Temperature",
|
405
408
|
(base2 + 1) => "Zone #{i} Configuration 1",
|
406
409
|
(base2 + 2) => "Zone #{i} Configuration 2",
|
407
|
-
base3 => "Zone #{i} Configuration 3"
|
410
|
+
base3 => "Zone #{i} Configuration 3"
|
408
411
|
}
|
409
412
|
end.inject({}, &:merge)
|
410
413
|
end
|
411
414
|
|
412
|
-
WRITEABLE = [112, 340, 341, 342, 346, 347]
|
415
|
+
WRITEABLE = [112, 340, 341, 342, 346, 347].freeze
|
413
416
|
|
414
417
|
# these are the valid ranges (i.e. the ABC will return _some_ value)
|
415
418
|
# * means 6 sequential ranges of equal size (i.e. must be repeated for each
|
@@ -453,42 +456,42 @@ module Aurora
|
|
453
456
|
3800..3809,
|
454
457
|
3818..3834,
|
455
458
|
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
|
-
]
|
459
|
+
12_000..12_019,
|
460
|
+
12_098..12_099,
|
461
|
+
12_100..12_119,
|
462
|
+
12_200..12_239,
|
463
|
+
12_300..12_319,
|
464
|
+
12_400..12_569,
|
465
|
+
12_600..12_639,
|
466
|
+
12_700..12_799,
|
467
|
+
20_000..20_099,
|
468
|
+
21_100..21_136,
|
469
|
+
21_200..21_265,
|
470
|
+
21_400..21_472,
|
471
|
+
21_500..21_589,
|
472
|
+
22_100..22_162, # *
|
473
|
+
22_200..22_262, # *
|
474
|
+
22_300..22_362, # *
|
475
|
+
22_400..22_462, # *
|
476
|
+
22_500..22_562, # *
|
477
|
+
22_600..22_662, # *
|
478
|
+
30_000..30_099,
|
479
|
+
31_000..31_034,
|
480
|
+
31_100..31_129,
|
481
|
+
31_200..31_229,
|
482
|
+
31_300..31_329,
|
483
|
+
31_400..31_472,
|
484
|
+
32_100..32_162, # *
|
485
|
+
32_200..32_262, # *
|
486
|
+
32_300..32_362, # *
|
487
|
+
32_400..32_462, # *
|
488
|
+
32_500..32_562, # *
|
489
|
+
32_600..32_662, # *
|
490
|
+
60_050..60_053,
|
491
|
+
60_100..60_109,
|
492
|
+
60_200..60_200,
|
493
|
+
61_000..61_009
|
494
|
+
].freeze
|
492
495
|
|
493
496
|
def read_all_registers(modbus_slave)
|
494
497
|
result = []
|
@@ -501,10 +504,10 @@ module Aurora
|
|
501
504
|
REGISTER_RANGES.map(&:to_a).flatten.zip(result).to_h
|
502
505
|
end
|
503
506
|
|
504
|
-
def diff_registers(
|
507
|
+
def diff_registers(lhs, rhs)
|
505
508
|
diff = {}
|
506
|
-
|
507
|
-
diff[k] = [
|
509
|
+
lhs.each_key do |k|
|
510
|
+
diff[k] = [lhs[k], rhs[k]] if lhs[k] != rhs[k]
|
508
511
|
end
|
509
512
|
diff
|
510
513
|
end
|
@@ -539,7 +542,7 @@ module Aurora
|
|
539
542
|
92 => "Model Number",
|
540
543
|
105 => "Serial Number",
|
541
544
|
112 => "Setup Line Voltage",
|
542
|
-
201 => "Discharge Pressure", # I can't figure out how this number is represented;
|
545
|
+
201 => "Discharge Pressure", # I can't figure out how this number is represented;
|
543
546
|
203 => "Suction Pressure",
|
544
547
|
205 => "Discharge Temperature",
|
545
548
|
207 => "Loop Entering Water Temperature",
|
@@ -565,7 +568,7 @@ module Aurora
|
|
565
568
|
344 => "ECM Speed",
|
566
569
|
346 => "Cooling Airflow Adjustment",
|
567
570
|
347 => "Aux Heat ECM Speed",
|
568
|
-
362 => "Active Dehumidify", # any value is true
|
571
|
+
362 => "Active Dehumidify", # any value is true
|
569
572
|
401 => "DHW Setpoint",
|
570
573
|
414 => "On Peak/SmartGrid 2", # 0x0001 only
|
571
574
|
483 => "Number of IZ2 Zones",
|
@@ -605,27 +608,27 @@ module Aurora
|
|
605
608
|
1157 => "Ht of Rej",
|
606
609
|
1165 => "VS Pump Watts",
|
607
610
|
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
|
-
|
611
|
+
31_003 => "Outdoor Temp",
|
612
|
+
31_005 => "IZ2 Demand",
|
613
|
+
31_109 => "Humidifier Mode", # write to 21114
|
614
|
+
31_110 => "Manual De/Humidification Target", # write to 21115
|
615
|
+
31_400 => "Dealer Name",
|
616
|
+
31_413 => "Dealer Phone",
|
617
|
+
31_421 => "Dealer Address 1",
|
618
|
+
31_434 => "Dealer Address 2",
|
619
|
+
31_447 => "Dealer Email",
|
620
|
+
31_460 => "Dealer Website"
|
621
|
+
}.merge(ignore(89..91))
|
622
|
+
.merge(ignore(93..104))
|
623
|
+
.merge(ignore(106..109))
|
624
|
+
.merge(faults(601..699))
|
625
|
+
.merge(zone_registers)
|
626
|
+
.merge(ignore(31_401..31_412))
|
627
|
+
.merge(ignore(31_414..31_420))
|
628
|
+
.merge(ignore(31_422..31_433))
|
629
|
+
.merge(ignore(31_435..31_446))
|
630
|
+
.merge(ignore(31_447..31_459))
|
631
|
+
.merge(ignore(31_461..31_472))
|
629
632
|
|
630
633
|
def transform_registers(registers)
|
631
634
|
registers.each do |(k, v)|
|
@@ -640,19 +643,20 @@ module Aurora
|
|
640
643
|
|
641
644
|
def print_registers(registers)
|
642
645
|
result = []
|
643
|
-
registers.each do |(k,
|
646
|
+
registers.each do |(k, value)|
|
644
647
|
# ignored
|
645
648
|
next if REGISTER_NAMES.key?(k) && REGISTER_NAMES[k].nil?
|
649
|
+
|
646
650
|
name = REGISTER_NAMES[k]
|
647
651
|
value_proc = REGISTER_CONVERTERS.find { |(_, z)| z.include?(k) }&.first || ->(v) { v }
|
648
652
|
format = REGISTER_FORMATS.find { |(_, z)| z.include?(k) }&.first || "%s"
|
649
653
|
format = "%1$d (0x%1$04x)" unless name
|
650
654
|
name ||= "???"
|
651
|
-
|
652
|
-
value = value_proc.arity == 2 ? value_proc.call(registers, k) : value_proc.call(
|
655
|
+
|
656
|
+
value = value_proc.arity == 2 ? value_proc.call(registers, k) : value_proc.call(value)
|
653
657
|
value = value.join(", ") if value.is_a?(Array)
|
654
|
-
value =
|
655
|
-
|
658
|
+
value = format(format, value) if value
|
659
|
+
|
656
660
|
result << "#{name} (#{k}): #{value}"
|
657
661
|
end
|
658
662
|
result.join("\n")
|