waterfurnace_aurora 0.2.0 → 0.3.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 +4 -4
- data/exe/aurora_fetch +26 -0
- data/{bin → exe}/aurora_mock +23 -20
- data/exe/aurora_monitor +120 -0
- 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 +235 -223
- 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_monitor +0 -76
- data/bin/aurora_mqtt_bridge +0 -179
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,21 +41,20 @@ 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
|
|
52
54
|
def to_string(registers, idx, length)
|
53
|
-
puts "converting #{idx} of length #{length}"
|
54
55
|
(idx...(idx + length)).map do |i|
|
55
56
|
(registers[i] >> 8).chr + (registers[i] & 0xff).chr
|
56
|
-
end.join.sub(/[ \0]+$/,
|
57
|
+
end.join.sub(/[ \0]+$/, "")
|
57
58
|
end
|
58
59
|
|
59
60
|
FAULTS = {
|
@@ -106,26 +107,27 @@ module Aurora
|
|
106
107
|
75 => "Charge Loss",
|
107
108
|
76 => "Suction Temperatur Sensor Limit",
|
108
109
|
77 => "Leaving Air Temperature Sensor Limit",
|
109
|
-
78 => "Maximum Operating Pressure Limit"
|
110
|
-
}
|
110
|
+
78 => "Maximum Operating Pressure Limit"
|
111
|
+
}.freeze
|
111
112
|
|
112
113
|
AR_SETTINGS = {
|
113
114
|
0 => "Cycle with Compressor",
|
114
115
|
1 => "Cycle with Thermostat Humidification Call",
|
115
116
|
2 => "Slow Opening Water Valve",
|
116
117
|
3 => "Cycle with Blower"
|
117
|
-
}
|
118
|
+
}.freeze
|
119
|
+
|
120
|
+
def dipswitch_settings(value)
|
121
|
+
return :manual if value == 0x7fff
|
118
122
|
|
119
|
-
def dipswitch_settings(v)
|
120
|
-
return :manual if v == 0x7fff
|
121
123
|
{
|
122
|
-
fp1:
|
123
|
-
fp2:
|
124
|
-
rv:
|
125
|
-
ar: AR_SETTINGS[(
|
126
|
-
cc:
|
127
|
-
lo:
|
128
|
-
dh_rh:
|
124
|
+
fp1: value & 0x01 == 0x01 ? "30ºF" : "15ºF",
|
125
|
+
fp2: value & 0x02 == 0x02 ? "30ºF" : "15ºF",
|
126
|
+
rv: value & 0x04 == 0x04 ? "O" : "B",
|
127
|
+
ar: AR_SETTINGS[(value >> 3) & 0x7],
|
128
|
+
cc: value & 0x20 == 0x20 ? "Single Stage" : "Dual Stage",
|
129
|
+
lo: value & 0x40 == 0x40 ? "Continouous" : "Pulse",
|
130
|
+
dh_rh: value & 0x80 == 0x80 ? "Dehumdifier On" : "Reheat On"
|
129
131
|
}
|
130
132
|
end
|
131
133
|
|
@@ -138,8 +140,8 @@ module Aurora
|
|
138
140
|
0x20 => :eh2,
|
139
141
|
0x200 => :accessory,
|
140
142
|
0x400 => :lockout,
|
141
|
-
0x800 => :alarm
|
142
|
-
}
|
143
|
+
0x800 => :alarm
|
144
|
+
}.freeze
|
143
145
|
|
144
146
|
SYSTEM_INPUTS = {
|
145
147
|
0x01 => "Y1",
|
@@ -149,18 +151,18 @@ module Aurora
|
|
149
151
|
0x10 => "G",
|
150
152
|
0x20 => "Dehumidifer",
|
151
153
|
0x40 => "Emergency Shutdown",
|
152
|
-
0x200 => "Load Shed"
|
153
|
-
}
|
154
|
+
0x200 => "Load Shed"
|
155
|
+
}.freeze
|
154
156
|
|
155
|
-
def status(
|
157
|
+
def status(value)
|
156
158
|
result = {
|
157
|
-
lps:
|
158
|
-
hps:
|
159
|
+
lps: value & 0x80 == 0x80 ? :closed : :open,
|
160
|
+
hps: value & 0x100 == 0x100 ? :closed : :open
|
159
161
|
}
|
160
|
-
result[:load_shed] = true if
|
161
|
-
result[:emergency_shutdown] = true if
|
162
|
-
leftover =
|
163
|
-
result[:unknown] = "0x%04x"
|
162
|
+
result[:load_shed] = true if value & 0x0200 == 0x0200
|
163
|
+
result[:emergency_shutdown] = true if value & 0x0040 == 0x0040
|
164
|
+
leftover = value & ~0x03c0
|
165
|
+
result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
|
164
166
|
result
|
165
167
|
end
|
166
168
|
|
@@ -169,18 +171,18 @@ module Aurora
|
|
169
171
|
0x04 => "Low Suction Pressure",
|
170
172
|
0x10 => "Low Discharge Pressure",
|
171
173
|
0x20 => "High Discharge Pressure",
|
172
|
-
0x40 => "Output Power Limit"
|
173
|
-
}
|
174
|
+
0x40 => "Output Power Limit"
|
175
|
+
}.freeze
|
174
176
|
|
175
177
|
VS_SAFE_MODE = {
|
176
178
|
0x01 => "EEV Indoor Failed",
|
177
179
|
0x02 => "EEV Outdoor Failed",
|
178
|
-
0x04 => "Invalid Ambient Temp"
|
179
|
-
}
|
180
|
+
0x04 => "Invalid Ambient Temp"
|
181
|
+
}.freeze
|
180
182
|
|
181
183
|
VS_ALARM1 = {
|
182
|
-
0x8000 => "Internal Error"
|
183
|
-
}
|
184
|
+
0x8000 => "Internal Error"
|
185
|
+
}.freeze
|
184
186
|
|
185
187
|
VS_ALARM2 = {
|
186
188
|
0x0001 => "Multi Safe Modes",
|
@@ -196,24 +198,24 @@ module Aurora
|
|
196
198
|
0x0400 => "DC Under Voltage",
|
197
199
|
0x0800 => "Invalid Suction Pressure",
|
198
200
|
0x1000 => "Invalid Discharge Pressure",
|
199
|
-
0x2000 => "Low Discharge Pressure"
|
200
|
-
}
|
201
|
+
0x2000 => "Low Discharge Pressure"
|
202
|
+
}.freeze
|
201
203
|
|
202
204
|
VS_EEV2 = {
|
203
205
|
0x0010 => "Invalid Suction Temperature",
|
204
206
|
0x0020 => "Invalid Leaving Air Temperature",
|
205
|
-
0x0040 => "Invalid Suction Pressure"
|
206
|
-
|
207
|
-
}
|
207
|
+
0x0040 => "Invalid Suction Pressure"
|
208
|
+
|
209
|
+
}.freeze
|
208
210
|
|
209
211
|
AXB_INPUTS = {
|
210
|
-
}
|
212
|
+
}.freeze
|
211
213
|
|
212
214
|
AXB_OUTPUTS = {
|
213
215
|
0x10 => "Accessory 2",
|
214
216
|
0x02 => "Loop Pump",
|
215
217
|
0x01 => "DHW"
|
216
|
-
}
|
218
|
+
}.freeze
|
217
219
|
|
218
220
|
HEATING_MODE = {
|
219
221
|
0 => :off,
|
@@ -221,30 +223,30 @@ module Aurora
|
|
221
223
|
2 => :cool,
|
222
224
|
3 => :heat,
|
223
225
|
4 => :eheat
|
224
|
-
}
|
226
|
+
}.freeze
|
225
227
|
|
226
228
|
FAN_MODE = {
|
227
229
|
0 => :auto,
|
228
230
|
1 => :continuous,
|
229
231
|
2 => :intermittent
|
230
|
-
}
|
232
|
+
}.freeze
|
231
233
|
|
232
234
|
HUMIDIFIER_SETTINGS = {
|
233
235
|
0x4000 => :auto_dehumidification,
|
234
|
-
0x8000 => :auto_humidification
|
235
|
-
}
|
236
|
+
0x8000 => :auto_humidification
|
237
|
+
}.freeze
|
236
238
|
|
237
239
|
INVERSE_HUMIDIFIER_SETTINGS = {
|
238
240
|
0x4000 => :manual_dehumidification,
|
239
|
-
0x8000 => :manual_humidification
|
240
|
-
}
|
241
|
+
0x8000 => :manual_humidification
|
242
|
+
}.freeze
|
241
243
|
|
242
244
|
ZONE_SIZES = {
|
243
245
|
0 => 0,
|
244
246
|
1 => 25,
|
245
247
|
2 => 45,
|
246
|
-
3 => 70
|
247
|
-
}
|
248
|
+
3 => 70
|
249
|
+
}.freeze
|
248
250
|
|
249
251
|
CALLS = {
|
250
252
|
0x0 => :standby,
|
@@ -254,39 +256,39 @@ module Aurora
|
|
254
256
|
0x4 => :h3,
|
255
257
|
0x5 => :c1,
|
256
258
|
0x6 => :c2,
|
257
|
-
0x7 => :unknown7
|
258
|
-
}
|
259
|
+
0x7 => :unknown7
|
260
|
+
}.freeze
|
259
261
|
|
260
|
-
def iz2_demand(
|
262
|
+
def iz2_demand(value)
|
261
263
|
{
|
262
|
-
fan_demand:
|
263
|
-
unit_demand:
|
264
|
+
fan_demand: value >> 8,
|
265
|
+
unit_demand: value & 0xff
|
264
266
|
}
|
265
267
|
end
|
266
268
|
|
267
|
-
def zone_configuration1(
|
268
|
-
fan = if
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
269
|
+
def zone_configuration1(value)
|
270
|
+
fan = if value & 0x80 == 0x80
|
271
|
+
:continuous
|
272
|
+
elsif value & 0x100 == 0x100
|
273
|
+
:intermittent
|
274
|
+
else
|
275
|
+
:auto
|
276
|
+
end
|
275
277
|
result = {
|
276
278
|
fan: fan,
|
277
|
-
on_time: ((
|
278
|
-
off_time: (((
|
279
|
-
cooling_target_temperature: ((
|
280
|
-
heating_target_temperature_carry:
|
279
|
+
on_time: ((value >> 9) & 0x7) * 5,
|
280
|
+
off_time: (((value >> 12) & 0x7) + 1) * 5,
|
281
|
+
cooling_target_temperature: ((value & 0x7e) >> 1) + 36,
|
282
|
+
heating_target_temperature_carry: value & 0o1
|
281
283
|
}
|
282
|
-
leftover =
|
283
|
-
result[:unknown] = "0x%04x"
|
284
|
+
leftover = value & ~0x7fff
|
285
|
+
result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
|
284
286
|
result
|
285
287
|
end
|
286
288
|
|
287
|
-
def zone_configuration2(registers,
|
288
|
-
prior_v = registers[
|
289
|
-
v = registers[
|
289
|
+
def zone_configuration2(registers, key)
|
290
|
+
prior_v = registers[key - 1] if registers.key?(key - 1)
|
291
|
+
v = registers[key]
|
290
292
|
result = {
|
291
293
|
call: CALLS[(v >> 1) & 0x7],
|
292
294
|
mode: HEATING_MODE[(v >> 8) & 0x03],
|
@@ -297,20 +299,20 @@ module Aurora
|
|
297
299
|
result[:heating_target_temperature] = ((carry << 5) | ((v & 0xf800) >> 11)) + 36
|
298
300
|
end
|
299
301
|
leftover = v & ~0xfb1e
|
300
|
-
result[:unknown] = "0x%04x"
|
302
|
+
result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
|
301
303
|
result
|
302
304
|
end
|
303
305
|
|
304
306
|
# hi order byte is normalized zone size
|
305
|
-
def zone_configuration3(
|
306
|
-
size = (
|
307
|
+
def zone_configuration3(value)
|
308
|
+
size = (value >> 3) & 0x3
|
307
309
|
result = {
|
308
|
-
zone_priority: (
|
310
|
+
zone_priority: (value & 0x20) == 0x20 ? :economy : :comfort,
|
309
311
|
zone_size: ZONE_SIZES[size],
|
310
|
-
normalized_size:
|
312
|
+
normalized_size: value >> 8
|
311
313
|
}
|
312
|
-
leftover =
|
313
|
-
result[:unknown] = "0x%04x"
|
314
|
+
leftover = value & ~0xff38
|
315
|
+
result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
|
314
316
|
result
|
315
317
|
end
|
316
318
|
|
@@ -320,15 +322,16 @@ module Aurora
|
|
320
322
|
REGISTER_CONVERTERS = {
|
321
323
|
TO_HUNDREDTHS => [2, 3, 807, 813, 816, 817, 819, 820, 825, 828],
|
322
324
|
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
|
-
|
325
|
+
TO_TENTHS => [19, 20, 401, 567, 740, 745, 746, 747, 900, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117, 1134, 1136,
|
326
|
+
12_619, 12_620,
|
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,42 @@ 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] } => [12_602, 21_202, 21_211, 21_220, 21_229, 21_238, 21_247],
|
352
|
+
->(v) { FAN_MODE[v] } => [12_621, 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
|
-
"%0.1fºF" => [19, 20, 401, 567, 740, 745, 746, 900, 1110, 1111, 1114, 1134, 1136,
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
370
|
+
"%0.1fºF" => [19, 20, 401, 567, 740, 745, 746, 747, 900, 1110, 1111, 1114, 1134, 1136,
|
371
|
+
12_619, 12_620,
|
372
|
+
21_203, 21_204,
|
373
|
+
21_212, 21_213,
|
374
|
+
21_221, 21_222,
|
375
|
+
21_230, 21_231,
|
376
|
+
21_239, 21_240,
|
377
|
+
21_248, 21_249,
|
378
|
+
31_003,
|
379
|
+
31_007, 31_010, 31_013, 31_016, 31_019, 31_022],
|
376
380
|
"E%d" => [25, 26],
|
377
381
|
"%d%%" => [282, 321, 322, 346, 565, 741],
|
378
382
|
"%0.1fA" => [1105, 1106, 1107, 1108],
|
379
383
|
"%0.1fgpm" => [1117],
|
380
384
|
"%dW" => [1147, 1149, 1151, 1153, 1165],
|
381
|
-
"%dBtuh" => [1157]
|
382
|
-
}
|
385
|
+
"%dBtuh" => [1157]
|
386
|
+
}.freeze
|
383
387
|
|
384
388
|
def ignore(range)
|
385
389
|
range.zip(Array.new(range.count)).to_h
|
@@ -391,11 +395,11 @@ module Aurora
|
|
391
395
|
|
392
396
|
def zone_registers
|
393
397
|
(1..6).map do |i|
|
394
|
-
base1 =
|
395
|
-
base2 =
|
396
|
-
base3 =
|
398
|
+
base1 = 21_202 + (i - 1) * 9
|
399
|
+
base2 = 31_007 + (i - 1) * 3
|
400
|
+
base3 = 31_200 + (i - 1) * 3
|
397
401
|
{
|
398
|
-
base1 => "Zone #{i} Heating Mode",
|
402
|
+
base1 => "Zone #{i} Heating Mode (write)",
|
399
403
|
(base1 + 1) => "Zone #{i} Heating Setpoint (write)",
|
400
404
|
(base1 + 2) => "Zone #{i} Cooling Setpoint (write)",
|
401
405
|
(base1 + 3) => "Zone #{i} Fan Mode (write)",
|
@@ -404,12 +408,12 @@ module Aurora
|
|
404
408
|
base2 => "Zone #{i} Ambient Temperature",
|
405
409
|
(base2 + 1) => "Zone #{i} Configuration 1",
|
406
410
|
(base2 + 2) => "Zone #{i} Configuration 2",
|
407
|
-
base3 => "Zone #{i} Configuration 3"
|
411
|
+
base3 => "Zone #{i} Configuration 3"
|
408
412
|
}
|
409
413
|
end.inject({}, &:merge)
|
410
414
|
end
|
411
415
|
|
412
|
-
WRITEABLE = [112, 340, 341, 342, 346, 347]
|
416
|
+
WRITEABLE = [112, 340, 341, 342, 346, 347].freeze
|
413
417
|
|
414
418
|
# these are the valid ranges (i.e. the ABC will return _some_ value)
|
415
419
|
# * means 6 sequential ranges of equal size (i.e. must be repeated for each
|
@@ -453,61 +457,42 @@ module Aurora
|
|
453
457
|
3800..3809,
|
454
458
|
3818..3834,
|
455
459
|
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
|
-
]
|
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
|
460
|
+
12_000..12_019,
|
461
|
+
12_098..12_099,
|
462
|
+
12_100..12_119,
|
463
|
+
12_200..12_239,
|
464
|
+
12_300..12_319,
|
465
|
+
12_400..12_569,
|
466
|
+
12_600..12_639,
|
467
|
+
12_700..12_799,
|
468
|
+
20_000..20_099,
|
469
|
+
21_100..21_136,
|
470
|
+
21_200..21_265,
|
471
|
+
21_400..21_472,
|
472
|
+
21_500..21_589,
|
473
|
+
22_100..22_162, # *
|
474
|
+
22_200..22_262, # *
|
475
|
+
22_300..22_362, # *
|
476
|
+
22_400..22_462, # *
|
477
|
+
22_500..22_562, # *
|
478
|
+
22_600..22_662, # *
|
479
|
+
30_000..30_099,
|
480
|
+
31_000..31_034,
|
481
|
+
31_100..31_129,
|
482
|
+
31_200..31_229,
|
483
|
+
31_300..31_329,
|
484
|
+
31_400..31_472,
|
485
|
+
32_100..32_162, # *
|
486
|
+
32_200..32_262, # *
|
487
|
+
32_300..32_362, # *
|
488
|
+
32_400..32_462, # *
|
489
|
+
32_500..32_562, # *
|
490
|
+
32_600..32_662, # *
|
491
|
+
60_050..60_053,
|
492
|
+
60_100..60_109,
|
493
|
+
60_200..60_200,
|
494
|
+
61_000..61_009
|
495
|
+
].freeze
|
511
496
|
|
512
497
|
REGISTER_NAMES = {
|
513
498
|
1 => "Random Start Delay",
|
@@ -539,7 +524,7 @@ module Aurora
|
|
539
524
|
92 => "Model Number",
|
540
525
|
105 => "Serial Number",
|
541
526
|
112 => "Setup Line Voltage",
|
542
|
-
201 => "Discharge Pressure", # I can't figure out how this number is represented;
|
527
|
+
201 => "Discharge Pressure", # I can't figure out how this number is represented;
|
543
528
|
203 => "Suction Pressure",
|
544
529
|
205 => "Discharge Temperature",
|
545
530
|
207 => "Loop Entering Water Temperature",
|
@@ -565,7 +550,7 @@ module Aurora
|
|
565
550
|
344 => "ECM Speed",
|
566
551
|
346 => "Cooling Airflow Adjustment",
|
567
552
|
347 => "Aux Heat ECM Speed",
|
568
|
-
362 => "Active Dehumidify", # any value is true
|
553
|
+
362 => "Active Dehumidify", # any value is true
|
569
554
|
401 => "DHW Setpoint",
|
570
555
|
414 => "On Peak/SmartGrid 2", # 0x0001 only
|
571
556
|
483 => "Number of IZ2 Zones",
|
@@ -576,6 +561,7 @@ module Aurora
|
|
576
561
|
741 => "Relative Humidity",
|
577
562
|
745 => "Heating Set Point",
|
578
563
|
746 => "Cooling Set Point",
|
564
|
+
747 => "Ambient Temperature",
|
579
565
|
807 => "AXB Version",
|
580
566
|
813 => "IZ2 Version?",
|
581
567
|
816 => "AOC Version 1?",
|
@@ -604,28 +590,32 @@ module Aurora
|
|
604
590
|
1153 => "Total Watts",
|
605
591
|
1157 => "Ht of Rej",
|
606
592
|
1165 => "VS Pump Watts",
|
593
|
+
12_602 => "Heating Mode (write)",
|
594
|
+
12_619 => "Heating Setpoint (write)",
|
595
|
+
12_620 => "Cooling Setpoint (write)",
|
596
|
+
12_621 => "Fan Mode (write)",
|
607
597
|
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
|
-
|
598
|
+
31_003 => "Outdoor Temp",
|
599
|
+
31_005 => "IZ2 Demand",
|
600
|
+
31_109 => "Humidifier Mode", # write to 21114
|
601
|
+
31_110 => "Manual De/Humidification Target", # write to 21115
|
602
|
+
31_400 => "Dealer Name",
|
603
|
+
31_413 => "Dealer Phone",
|
604
|
+
31_421 => "Dealer Address 1",
|
605
|
+
31_434 => "Dealer Address 2",
|
606
|
+
31_447 => "Dealer Email",
|
607
|
+
31_460 => "Dealer Website"
|
608
|
+
}.merge(ignore(89..91))
|
609
|
+
.merge(ignore(93..104))
|
610
|
+
.merge(ignore(106..109))
|
611
|
+
.merge(faults(601..699))
|
612
|
+
.merge(zone_registers)
|
613
|
+
.merge(ignore(31_401..31_412))
|
614
|
+
.merge(ignore(31_414..31_420))
|
615
|
+
.merge(ignore(31_422..31_433))
|
616
|
+
.merge(ignore(31_435..31_446))
|
617
|
+
.merge(ignore(31_447..31_459))
|
618
|
+
.merge(ignore(31_461..31_472))
|
629
619
|
|
630
620
|
def transform_registers(registers)
|
631
621
|
registers.each do |(k, v)|
|
@@ -638,21 +628,43 @@ module Aurora
|
|
638
628
|
registers
|
639
629
|
end
|
640
630
|
|
631
|
+
def read_all_registers(modbus_slave)
|
632
|
+
result = []
|
633
|
+
REGISTER_RANGES.each do |range|
|
634
|
+
# read at most 100 at a time
|
635
|
+
range.each_slice(100) do |keys|
|
636
|
+
result.concat(modbus_slave.holding_registers[keys.first..keys.last])
|
637
|
+
end
|
638
|
+
end
|
639
|
+
REGISTER_RANGES.map(&:to_a).flatten.zip(result).to_h
|
640
|
+
end
|
641
|
+
|
642
|
+
def diff_registers(lhs, rhs)
|
643
|
+
diff = {}
|
644
|
+
(lhs.keys | rhs.keys).each do |k|
|
645
|
+
diff[k] = rhs[k] if lhs[k] != rhs[k]
|
646
|
+
end
|
647
|
+
diff
|
648
|
+
end
|
649
|
+
|
641
650
|
def print_registers(registers)
|
642
651
|
result = []
|
643
|
-
registers.each do |(k,
|
652
|
+
registers.each do |(k, value)|
|
644
653
|
# ignored
|
645
654
|
next if REGISTER_NAMES.key?(k) && REGISTER_NAMES[k].nil?
|
655
|
+
|
646
656
|
name = REGISTER_NAMES[k]
|
657
|
+
|
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
|
-
|
651
|
-
|
652
|
-
value = value_proc.arity == 2 ? value_proc.call(registers, k) : value_proc.call(v)
|
661
|
+
|
662
|
+
value = value_proc.arity == 2 ? value_proc.call(registers, k) : value_proc.call(value)
|
653
663
|
value = value.join(", ") if value.is_a?(Array)
|
654
|
-
value =
|
655
|
-
|
664
|
+
value = format(format, value) if value
|
665
|
+
|
666
|
+
name ||= "???"
|
667
|
+
|
656
668
|
result << "#{name} (#{k}): #{value}"
|
657
669
|
end
|
658
670
|
result.join("\n")
|