waterfurnace_aurora 0.4.4 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/aurora_monitor +9 -5
- data/exe/aurora_mqtt_bridge +61 -38
- data/lib/aurora/abc_client.rb +31 -65
- data/lib/aurora/component.rb +6 -0
- data/lib/aurora/compressor.rb +65 -0
- data/lib/aurora/dhw.rb +31 -0
- data/lib/aurora/iz2_zone.rb +16 -12
- data/lib/aurora/mqtt_modbus.rb +47 -0
- data/lib/aurora/registers.rb +30 -11
- data/lib/aurora/thermostat.rb +37 -5
- data/lib/aurora/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c89c3d73941d7574fbe1088bf792676f772abfb98dab07be0de27a61323ec96
|
4
|
+
data.tar.gz: 13a5a82be919ee67bbbc87357eac1cf6aaba1b34514fbd808b9411dfc37bcb45
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce768bdc79fc4cd2a406ebf15757f587d373030cec21efc64076b3951a7c97f631f1e47a0e810005b24553fdd78eb4aec869022c8e0f6f33e409522f473e942b
|
7
|
+
data.tar.gz: ab5cba75479435f32a42bca6fd8155a5954674996b75a4a2afe397241bce519720a083aaaf0ccf3976a8c289170ec39553f63e9ba036248bdadcf408022e7ffb
|
data/exe/aurora_monitor
CHANGED
@@ -30,7 +30,7 @@ end
|
|
30
30
|
|
31
31
|
options.parse!
|
32
32
|
|
33
|
-
unless ARGV.length ==
|
33
|
+
unless ARGV.length == 1
|
34
34
|
puts options
|
35
35
|
exit 1
|
36
36
|
end
|
@@ -39,8 +39,8 @@ uri = URI.parse(ARGV[0])
|
|
39
39
|
|
40
40
|
last_registers = {}
|
41
41
|
|
42
|
-
SENSOR_REGISTERS = [16, 19, 20, 740, 900, 1109, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117,
|
43
|
-
1153, 1165].freeze
|
42
|
+
SENSOR_REGISTERS = [16, 19, 20, 740, 900, 901, 903, 908, 909, 1109, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117,
|
43
|
+
1134, 1148, 1149, 1150, 1151, 1152, 1153, 1164, 1165, 3326, 3330].freeze
|
44
44
|
|
45
45
|
io = case uri.scheme
|
46
46
|
when "tcp"
|
@@ -76,7 +76,11 @@ diff_and_print = lambda do |registers|
|
|
76
76
|
end
|
77
77
|
|
78
78
|
server.request_callback = lambda { |uid, func, req|
|
79
|
-
if func ==
|
79
|
+
if func == 6
|
80
|
+
puts "#{Time.now} ===== write register to #{uid}:"
|
81
|
+
registers = { req[:addr] => req[:val] }
|
82
|
+
puts Aurora.print_registers(registers)
|
83
|
+
elsif func == 68
|
80
84
|
puts "#{Time.now} ===== no idea to #{uid}: #{req.inspect}" unless diff_only
|
81
85
|
elsif func == 67
|
82
86
|
puts "#{Time.now} ===== write discontiguous registers to #{uid}:"
|
@@ -125,7 +129,7 @@ server.response_callback = lambda { |uid, func, res, req|
|
|
125
129
|
puts "#{Time.now} ===== read discontiguous registers from #{uid}" unless diff_only
|
126
130
|
registers = req.zip(res).to_h
|
127
131
|
diff_and_print.call(registers)
|
128
|
-
elsif [16, 67, 68].include?(func)
|
132
|
+
elsif [6, 16, 67, 68].include?(func)
|
129
133
|
# no output
|
130
134
|
else
|
131
135
|
puts "#{Time.now} **** new func #{func}"
|
data/exe/aurora_mqtt_bridge
CHANGED
@@ -46,8 +46,20 @@ class MQTTBridge
|
|
46
46
|
when /\$modbus$/
|
47
47
|
registers = @abc.query_registers(value)
|
48
48
|
Aurora.print_registers(registers) do |register, v|
|
49
|
-
@homie.mqtt.publish("#{@homie.topic}/$modbus/#{register}", v,
|
49
|
+
@homie.mqtt.publish("#{@homie.topic}/$modbus/#{register}", v, qos: 1)
|
50
50
|
end
|
51
|
+
when %r{\$modbus/getregs}
|
52
|
+
query_id, query = value.split(":")
|
53
|
+
queries = query.split(";").map do |range|
|
54
|
+
start, length = range.split(",").map(&:to_i)
|
55
|
+
next start if length.nil?
|
56
|
+
|
57
|
+
start...(start + length)
|
58
|
+
end
|
59
|
+
registers = @abc.modbus_slave.read_multiple_holding_registers(*queries)
|
60
|
+
result = registers.values.join(",")
|
61
|
+
@homie.mqtt.publish("#{@homie.topic}/$modbus/getregs/response",
|
62
|
+
"#{query_id}:#{result}", qos: 1)
|
51
63
|
when %r{\$modbus/(\d+)/set$}
|
52
64
|
register = $1.to_i
|
53
65
|
value = case value
|
@@ -59,7 +71,7 @@ class MQTTBridge
|
|
59
71
|
@abc.modbus_slave.holding_registers[register] = value if value
|
60
72
|
registers = { register => @abc.modbus_slave.holding_registers[register] }
|
61
73
|
Aurora.print_registers(registers) do |r, v|
|
62
|
-
@homie.mqtt.publish("#{@homie.topic}/$modbus/#{r}", v,
|
74
|
+
@homie.mqtt.publish("#{@homie.topic}/$modbus/#{r}", v, qos: 1)
|
63
75
|
end
|
64
76
|
end
|
65
77
|
end
|
@@ -76,8 +88,10 @@ class MQTTBridge
|
|
76
88
|
@abc.refresh
|
77
89
|
|
78
90
|
components = { @homie_abc => @abc,
|
91
|
+
@compressor => @abc.compressor,
|
79
92
|
@blower => @abc.blower,
|
80
|
-
@pump => @abc.pump
|
93
|
+
@pump => @abc.pump,
|
94
|
+
@dhw => @abc.dhw }.compact
|
81
95
|
@abc.zones.each_with_index do |z, idx|
|
82
96
|
homie_zone = @homie["zone#{idx + 1}"]
|
83
97
|
components[homie_zone] = z
|
@@ -89,12 +103,6 @@ class MQTTBridge
|
|
89
103
|
end
|
90
104
|
end
|
91
105
|
|
92
|
-
if @abc.dhw?
|
93
|
-
@dhw["enabled"].value = @abc.dhw_enabled
|
94
|
-
@dhw["water-temperature"].value = @abc.dhw_water_temperature
|
95
|
-
@dhw["set-point"].value = @abc.dhw_setpoint
|
96
|
-
end
|
97
|
-
|
98
106
|
@abc.faults.each_with_index do |fault_count, i|
|
99
107
|
@faults["e#{i + 1}"].value = fault_count
|
100
108
|
end
|
@@ -109,8 +117,6 @@ class MQTTBridge
|
|
109
117
|
|
110
118
|
def publish_basic_attributes
|
111
119
|
@homie_abc = @homie.node("abc", "Aurora Basic Control", "ABC") do |node|
|
112
|
-
node.property("compressor-speed", "Compressor Speed", :integer, @abc.compressor_speed,
|
113
|
-
format: @abc.vs_drive? ? 0..12 : 0..2)
|
114
120
|
node.property("current-mode", "Current Heating/Cooling Mode", :enum, @abc.current_mode,
|
115
121
|
format: %w[lockout standby blower heating cooling eh1 eh2 emergency waiting dehumidify])
|
116
122
|
node.property("entering-air-temperature", "Entering Air Temperature", :float, @abc.entering_air_temperature,
|
@@ -128,13 +134,29 @@ class MQTTBridge
|
|
128
134
|
format: 0..100)
|
129
135
|
node.property("fp1", "FP1 Sensor", :float, @abc.fp1, unit: "ºF")
|
130
136
|
node.property("fp2", "FP2 Sensor", :float, @abc.fp2, unit: "ºF")
|
131
|
-
%i[
|
137
|
+
%i[aux_heat total].each do |component|
|
132
138
|
component = "#{component}_watts"
|
133
139
|
node.property(component.tr("_", "-"), component.tr("_", " ").titleize, :integer,
|
134
140
|
@abc.public_send(component), unit: "W")
|
135
141
|
end
|
136
142
|
end
|
137
143
|
|
144
|
+
@compressor = @homie.node("compressor", "Compressor", @abc.compressor.type) do |node|
|
145
|
+
node.property("speed", "Current compressor speed", :integer, @abc.compressor.speed,
|
146
|
+
format: @abc.compressor.speed_range)
|
147
|
+
node.property("watts", "Energy Usage", :integer, @abc.compressor.watts, unit: "W") if @abc.energy_monitoring?
|
148
|
+
|
149
|
+
next unless @abc.compressor.is_a?(Aurora::Compressor::VSDrive)
|
150
|
+
|
151
|
+
node.property("ambient-temperature", "Compressor ambient temperature", :float,
|
152
|
+
@abc.compressor.ambient_temperature, unit: "ºF")
|
153
|
+
|
154
|
+
next unless @abc.iz2?
|
155
|
+
|
156
|
+
node.property("iz2-desired-speed", "IZ2 Desired Speed", :integer, @abc.compressor.iz2_desired_speed,
|
157
|
+
format: 0..12)
|
158
|
+
end
|
159
|
+
|
138
160
|
@blower = @homie.node("blower", "Blower", @abc.blower.type) do |node|
|
139
161
|
if @abc.blower.respond_to?(:running)
|
140
162
|
node.property("running", "Blower is running", :boolean, @abc.blower.running?)
|
@@ -154,10 +176,11 @@ class MQTTBridge
|
|
154
176
|
@mutex.synchronize { property.value = @abc.blower.public_send("#{field}=", value) }
|
155
177
|
end
|
156
178
|
end
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
179
|
+
|
180
|
+
next unless @abc.iz2?
|
181
|
+
|
182
|
+
node.property("iz2-desired-speed", "IZ2 Desired Speed", :integer, @abc.blower.iz2_desired_speed,
|
183
|
+
format: 0..100, unit: "%")
|
161
184
|
end
|
162
185
|
|
163
186
|
@pump = @homie.node("pump", "Loop Pump", @abc.pump.type) do |node|
|
@@ -183,21 +206,24 @@ class MQTTBridge
|
|
183
206
|
end
|
184
207
|
end
|
185
208
|
|
186
|
-
if @abc.dhw
|
209
|
+
if @abc.dhw
|
187
210
|
@dhw = @homie.node("dhw", "Domestic Hot Water Generator", "DHW") do |node|
|
188
|
-
node.property("enabled", "Enabled", :boolean, @abc.
|
189
|
-
@mutex.synchronize { property.value = @abc.
|
211
|
+
node.property("enabled", "Enabled", :boolean, @abc.dhw.enabled) do |value, property|
|
212
|
+
@mutex.synchronize { property.value = @abc.dhw.enabled = value }
|
190
213
|
end
|
191
|
-
node.property("water-temperature", "
|
192
|
-
@abc.
|
193
|
-
node.property("set-point", "
|
194
|
-
|
195
|
-
@mutex.synchronize { property.value = @abc.
|
214
|
+
node.property("water-temperature", "Water Temperature", :float,
|
215
|
+
@abc.dhw.water_temperature, unit: "ºF")
|
216
|
+
node.property("set-point", "Set Point", :float, @abc.dhw.set_point, format: 100..140,
|
217
|
+
unit: "ºF") do |value, property|
|
218
|
+
@mutex.synchronize { property.value = @abc.dhw.set_point = value }
|
196
219
|
end
|
197
220
|
end
|
198
221
|
end
|
199
222
|
|
200
223
|
@faults = @homie.node("faults", "Fault History", "ABC") do |node|
|
224
|
+
node.property("clear-history", "Reset fault counts", :enum, retained: false, format: "clear") do |_|
|
225
|
+
@mutex.synchronize { @abc.clear_fault_history }
|
226
|
+
end
|
201
227
|
@abc.faults.each_with_index do |count, i|
|
202
228
|
name = Aurora::FAULTS[i + 1]
|
203
229
|
node.property("e#{i + 1}", name || "E#{i + 1}", :integer, count)
|
@@ -213,25 +239,21 @@ class MQTTBridge
|
|
213
239
|
format: allowed_modes) do |value, property|
|
214
240
|
@mutex.synchronize { property.value = zone.target_mode = value.to_sym }
|
215
241
|
end
|
216
|
-
|
217
|
-
|
218
|
-
format: %w[standby h1 h2 h3 c1 c2])
|
219
|
-
end
|
242
|
+
node.property("current-mode", "Current Heating/Cooling Mode Requested", :enum, zone.current_mode,
|
243
|
+
format: %w[standby h1 h2 h3 c1 c2])
|
220
244
|
node.property("target-fan-mode", "Target Fan Mode", :enum, zone.target_fan_mode,
|
221
245
|
format: %w[auto continuous intermittent]) do |value, property|
|
222
246
|
@mutex.synchronize { property.value = zone.target_fan_mode = value.to_sym }
|
223
247
|
end
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
unit: "M", format: %w[0 5 10 15 20 25 30 35 40]) do |value, property|
|
232
|
-
@mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
|
233
|
-
end
|
248
|
+
node.property("fan-intermittent-on", "Fan Intermittent Mode On Duration", :enum, zone.fan_intermittent_on,
|
249
|
+
unit: "M", format: %w[0 5 10 15 20]) do |value, property|
|
250
|
+
@mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
|
251
|
+
end
|
252
|
+
node.property("fan-intermittent-off", "Fan Intermittent Mode Off Duration", :enum, zone.fan_intermittent_on,
|
253
|
+
unit: "M", format: %w[0 5 10 15 20 25 30 35 40]) do |value, property|
|
254
|
+
@mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
|
234
255
|
end
|
256
|
+
node.property("current-fan-mode", "Current Fan Status", :boolean, zone.current_fan_mode)
|
235
257
|
if zone.is_a?(Aurora::IZ2Zone)
|
236
258
|
node.property("priority", "Zone Priority", :enum, zone.priority, format: %w[economy comfort])
|
237
259
|
node.property("size", "Size", :enum, zone.size, format: %w[0 25 45 70])
|
@@ -251,6 +273,7 @@ class MQTTBridge
|
|
251
273
|
|
252
274
|
# direct access to modbus registers for debugging purposes
|
253
275
|
@homie.mqtt.subscribe("#{@homie.topic}/$modbus")
|
276
|
+
@homie.mqtt.subscribe("#{@homie.topic}/$modbus/getregs")
|
254
277
|
@homie.mqtt.subscribe("#{@homie.topic}/$modbus/+/set")
|
255
278
|
@homie.publish
|
256
279
|
end
|
data/lib/aurora/abc_client.rb
CHANGED
@@ -4,6 +4,8 @@ require "yaml"
|
|
4
4
|
require "uri"
|
5
5
|
|
6
6
|
require "aurora/blower"
|
7
|
+
require "aurora/compressor"
|
8
|
+
require "aurora/dhw"
|
7
9
|
require "aurora/iz2_zone"
|
8
10
|
require "aurora/pump"
|
9
11
|
require "aurora/thermostat"
|
@@ -24,7 +26,9 @@ module Aurora
|
|
24
26
|
port: uri.port || 23,
|
25
27
|
baud: 19_200,
|
26
28
|
parity: :even)
|
27
|
-
|
29
|
+
when "mqtt", "mqtts"
|
30
|
+
require "aurora/mqtt_modbus"
|
31
|
+
return Aurora::MQTTModBus.new(uri)
|
28
32
|
else
|
29
33
|
return Aurora::MockABC.new(YAML.load_file(uri.path)) if File.file?(uri.path)
|
30
34
|
|
@@ -40,23 +44,20 @@ module Aurora
|
|
40
44
|
attr_reader :modbus_slave,
|
41
45
|
:serial_number,
|
42
46
|
:zones,
|
47
|
+
:compressor,
|
43
48
|
:blower,
|
44
49
|
:pump,
|
50
|
+
:dhw,
|
45
51
|
:faults,
|
46
52
|
:current_mode,
|
47
|
-
:dhw_enabled,
|
48
|
-
:dhw_setpoint,
|
49
53
|
:entering_air_temperature,
|
50
54
|
:relative_humidity,
|
51
55
|
:leaving_air_temperature,
|
52
56
|
:leaving_water_temperature,
|
53
57
|
:entering_water_temperature,
|
54
|
-
:dhw_water_temperature,
|
55
|
-
:compressor_speed,
|
56
58
|
:outdoor_temperature,
|
57
59
|
:fp1,
|
58
60
|
:fp2,
|
59
|
-
:compressor_watts,
|
60
61
|
:aux_heat_watts,
|
61
62
|
:total_watts
|
62
63
|
|
@@ -68,9 +69,16 @@ module Aurora
|
|
68
69
|
registers = Aurora.transform_registers(raw_registers.dup)
|
69
70
|
@program = registers[88]
|
70
71
|
@serial_number = registers[105]
|
71
|
-
@dhw_water_temperature = registers[1114]
|
72
72
|
@energy_monitor = raw_registers[412]
|
73
73
|
|
74
|
+
@zones = if iz2?
|
75
|
+
iz2_zone_count = @modbus_slave.holding_registers[483]
|
76
|
+
(0...iz2_zone_count).map { |i| IZ2Zone.new(self, i + 1) }
|
77
|
+
else
|
78
|
+
[Thermostat.new(self)]
|
79
|
+
end
|
80
|
+
|
81
|
+
@compressor = @program == "ABCVSP" ? Compressor::VSDrive.new(self) : Compressor::GenericCompressor.new(self)
|
74
82
|
@blower = case raw_registers[404]
|
75
83
|
when 1, 2 then Blower::ECM.new(self, registers[404])
|
76
84
|
when 3 then Blower::FiveSpeed.new(self, registers[404])
|
@@ -83,13 +91,8 @@ module Aurora
|
|
83
91
|
Pump::GenericPump.new(self,
|
84
92
|
registers[413])
|
85
93
|
end
|
94
|
+
@dhw = DHW.new(self) if (-999..999).include?(registers[1114])
|
86
95
|
|
87
|
-
@zones = if iz2?
|
88
|
-
iz2_zone_count = @modbus_slave.holding_registers[483]
|
89
|
-
(0...iz2_zone_count).map { |i| IZ2Zone.new(self, i + 1) }
|
90
|
-
else
|
91
|
-
[Thermostat.new(self)]
|
92
|
-
end
|
93
96
|
@faults = []
|
94
97
|
end
|
95
98
|
|
@@ -128,51 +131,32 @@ module Aurora
|
|
128
131
|
end
|
129
132
|
|
130
133
|
def refresh
|
131
|
-
registers_to_read = [6, 19..20, 25, 30, 344, 740..741, 900, 1110..1111, 1114,
|
134
|
+
registers_to_read = [6, 19..20, 25, 30, 344, 740..741, 900, 1110..1111, 1114, 1150..1153, 1165,
|
132
135
|
31_003]
|
133
|
-
|
136
|
+
zones.each do |z|
|
137
|
+
registers_to_read.concat(z.registers_to_read)
|
138
|
+
end
|
139
|
+
registers_to_read.concat(compressor.registers_to_read)
|
134
140
|
registers_to_read.concat(blower.registers_to_read)
|
135
141
|
registers_to_read.concat(pump.registers_to_read)
|
136
|
-
registers_to_read.concat(
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
base1 = 21_203 + i * 9
|
141
|
-
base2 = 31_007 + i * 3
|
142
|
-
base3 = 31_200 + i * 3
|
143
|
-
registers_to_read << (base1..(base1 + 1))
|
144
|
-
registers_to_read << (base2..(base2 + 2))
|
145
|
-
registers_to_read << base3
|
146
|
-
end
|
147
|
-
else
|
148
|
-
registers_to_read << 502
|
149
|
-
registers_to_read << (745..746)
|
150
|
-
end
|
142
|
+
registers_to_read.concat(dhw.registers_to_read) if dhw
|
143
|
+
# need dehumidify mode to calculate final current mode;
|
144
|
+
# apparently non-VSD doesn't have this register at all?
|
145
|
+
registers_to_read.concat([362]) if compressor.is_a?(Compressor::VSDrive)
|
151
146
|
|
152
|
-
|
147
|
+
faults = @modbus_slave.read_multiple_holding_registers(601..699)
|
148
|
+
@faults = Aurora.transform_registers(faults).values
|
153
149
|
|
154
150
|
registers = @modbus_slave.holding_registers[*registers_to_read]
|
155
151
|
Aurora.transform_registers(registers)
|
156
152
|
|
157
153
|
outputs = registers[30]
|
158
154
|
|
159
|
-
@dhw_enabled = registers[400]
|
160
|
-
@dhw_setpoint = registers[401]
|
161
155
|
@entering_air_temperature = registers[740]
|
162
156
|
@relative_humidity = registers[741]
|
163
157
|
@leaving_air_temperature = registers[900]
|
164
158
|
@leaving_water_temperature = registers[1110]
|
165
159
|
@entering_water_temperature = registers[1111]
|
166
|
-
@dhw_water_temperature = registers[1114]
|
167
|
-
@compressor_speed = if vs_drive?
|
168
|
-
registers[3001]
|
169
|
-
elsif outputs.include?(:cc2)
|
170
|
-
2
|
171
|
-
elsif outputs.include?(:cc)
|
172
|
-
1
|
173
|
-
else
|
174
|
-
0
|
175
|
-
end
|
176
160
|
@outdoor_temperature = registers[31_003]
|
177
161
|
@fp1 = registers[19]
|
178
162
|
@fp2 = registers[20]
|
@@ -180,7 +164,6 @@ module Aurora
|
|
180
164
|
@error = registers[25] & 0x7fff
|
181
165
|
@derated = (41..46).include?(@error)
|
182
166
|
@safe_mode = [47, 48, 49, 72, 74].include?(@error)
|
183
|
-
@compressor_watts = registers[1147]
|
184
167
|
@aux_heat_watts = registers[1151]
|
185
168
|
@total_watts = registers[1153]
|
186
169
|
|
@@ -202,12 +185,13 @@ module Aurora
|
|
202
185
|
:standby
|
203
186
|
end
|
204
187
|
|
205
|
-
blower.refresh(registers)
|
206
|
-
pump.refresh(registers)
|
207
|
-
|
208
188
|
zones.each do |z|
|
209
189
|
z.refresh(registers)
|
210
190
|
end
|
191
|
+
compressor.refresh(registers)
|
192
|
+
blower.refresh(registers)
|
193
|
+
pump.refresh(registers)
|
194
|
+
dhw&.refresh(registers)
|
211
195
|
end
|
212
196
|
|
213
197
|
def cooling_airflow_adjustment=(value)
|
@@ -215,16 +199,6 @@ module Aurora
|
|
215
199
|
@modbus_slave.holding_registers[346] = value
|
216
200
|
end
|
217
201
|
|
218
|
-
def dhw_enabled=(value)
|
219
|
-
@modbus_slave.holding_registers[400] = value ? 1 : 0
|
220
|
-
end
|
221
|
-
|
222
|
-
def dhw_setpoint=(value)
|
223
|
-
raise ArgumentError unless (100..140).include?(value)
|
224
|
-
|
225
|
-
@modbus_slave.holding_registers[401] = (value * 10).to_i
|
226
|
-
end
|
227
|
-
|
228
202
|
def loop_pressure_trip=(value)
|
229
203
|
@modbus_slave.holding_registers[419] = (value * 10).to_i
|
230
204
|
end
|
@@ -270,14 +244,6 @@ module Aurora
|
|
270
244
|
@energy_monitor == 2
|
271
245
|
end
|
272
246
|
|
273
|
-
def vs_drive?
|
274
|
-
@program == "ABCVSP"
|
275
|
-
end
|
276
|
-
|
277
|
-
def dhw?
|
278
|
-
(-999..999).include?(dhw_water_temperature)
|
279
|
-
end
|
280
|
-
|
281
247
|
# config aurora system
|
282
248
|
{ thermostat: 800, axb: 806, iz2: 812, aoc: 815, moc: 818, eev2: 824 }.each do |(component, register)|
|
283
249
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
data/lib/aurora/component.rb
CHANGED
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "aurora/component"
|
4
|
+
|
5
|
+
module Aurora
|
6
|
+
module Compressor
|
7
|
+
class GenericCompressor < Component
|
8
|
+
attr_reader :speed, :watts
|
9
|
+
|
10
|
+
def type
|
11
|
+
"Compressor"
|
12
|
+
end
|
13
|
+
|
14
|
+
def speed_range
|
15
|
+
0..2
|
16
|
+
end
|
17
|
+
|
18
|
+
def registers_to_read
|
19
|
+
if abc.energy_monitoring?
|
20
|
+
[1146..1147]
|
21
|
+
else
|
22
|
+
[]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def refresh(registers)
|
27
|
+
outputs = registers[30]
|
28
|
+
@speed = if outputs.include?(:cc2)
|
29
|
+
2
|
30
|
+
elsif outputs.include?(:cc)
|
31
|
+
1
|
32
|
+
else
|
33
|
+
0
|
34
|
+
end
|
35
|
+
@watts = registers[1146] if abc.energy_monitoring?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class VSDrive < GenericCompressor
|
40
|
+
attr_reader :ambient_temperature, :iz2_desired_speed
|
41
|
+
|
42
|
+
def type
|
43
|
+
"Variable Speed Drive"
|
44
|
+
end
|
45
|
+
|
46
|
+
def speed_range
|
47
|
+
0..12
|
48
|
+
end
|
49
|
+
|
50
|
+
def registers_to_read
|
51
|
+
result = super + [209, 3001, 3326]
|
52
|
+
result << 564 if abc.iz2?
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
def refresh(registers)
|
57
|
+
super
|
58
|
+
|
59
|
+
@speed = registers[3001]
|
60
|
+
@ambient_temperature = registers[3326]
|
61
|
+
@iz2_desired_speed = registers[564] if abc.iz2?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/aurora/dhw.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "aurora/component"
|
4
|
+
|
5
|
+
module Aurora
|
6
|
+
class DHW < Component
|
7
|
+
attr_reader :enabled, :set_point, :water_temperature
|
8
|
+
|
9
|
+
def registers_to_read
|
10
|
+
[400..401, 1114]
|
11
|
+
end
|
12
|
+
|
13
|
+
def refresh(registers)
|
14
|
+
@enabled = registers[400]
|
15
|
+
@set_point = registers[401]
|
16
|
+
@water_temperature = registers[1114]
|
17
|
+
end
|
18
|
+
|
19
|
+
def enabled=(value)
|
20
|
+
holding_registers[400] = value ? 1 : 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_point=(value) # rubocop:disable Naming/AccessorMethodName
|
24
|
+
raise ArgumentError unless (100..140).include?(value)
|
25
|
+
|
26
|
+
raw_value = (value * 10).to_i
|
27
|
+
holding_registers[401] = raw_value
|
28
|
+
@set_point = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/aurora/iz2_zone.rb
CHANGED
@@ -5,18 +5,22 @@ require "aurora/thermostat"
|
|
5
5
|
module Aurora
|
6
6
|
class IZ2Zone < Thermostat
|
7
7
|
attr_reader :zone_number,
|
8
|
-
:current_mode,
|
9
|
-
:current_fan_mode,
|
10
|
-
:fan_intermittent_on,
|
11
|
-
:fan_intermittent_off,
|
12
8
|
:priority,
|
13
|
-
:size,
|
9
|
+
:size,
|
10
|
+
:normalized_size
|
14
11
|
|
15
12
|
def initialize(abc, zone_number)
|
16
13
|
super(abc)
|
17
14
|
@zone_number = zone_number
|
18
15
|
end
|
19
16
|
|
17
|
+
def registers_to_read
|
18
|
+
base1 = 21_203 + (zone_number - 1) * 9
|
19
|
+
base2 = 31_007 + (zone_number - 1) * 3
|
20
|
+
base3 = 31_200 + (zone_number - 1) * 3
|
21
|
+
[base1..(base1 + 1), base2..(base2 + 2), base3]
|
22
|
+
end
|
23
|
+
|
20
24
|
def refresh(registers)
|
21
25
|
@ambient_temperature = registers[31_007 + (zone_number - 1) * 3]
|
22
26
|
|
@@ -41,28 +45,28 @@ module Aurora
|
|
41
45
|
def target_mode=(value)
|
42
46
|
return unless (raw_value = Aurora::HEATING_MODE.invert[value])
|
43
47
|
|
44
|
-
|
48
|
+
holding_registers[21_202 + (zone_number - 1) * 9] = raw_value
|
45
49
|
@target_mode = value
|
46
50
|
end
|
47
51
|
|
48
52
|
def target_fan_mode=(value)
|
49
53
|
return unless (raw_value = Aurora::FAN_MODE.invert[value])
|
50
54
|
|
51
|
-
|
55
|
+
holding_registers[21_205 + (zone_number - 1) * 9] = raw_value
|
52
56
|
@target_fan_mode = value
|
53
57
|
end
|
54
58
|
|
55
59
|
def fan_intermittent_on=(value)
|
56
60
|
return unless value >= 0 && value <= 25 && (value % 5).zero?
|
57
61
|
|
58
|
-
|
62
|
+
holding_registers[21_206 + (zone_number - 1) * 9] = value
|
59
63
|
@fan_intermittent_on = value
|
60
64
|
end
|
61
65
|
|
62
66
|
def fan_intermittent_off=(value)
|
63
67
|
return unless value >= 0 && value <= 40 && (value % 5).zero?
|
64
68
|
|
65
|
-
|
69
|
+
holding_registers[21_207 + (zone_number - 1) * 9] = value
|
66
70
|
@fan_intermittent_off = value
|
67
71
|
end
|
68
72
|
|
@@ -70,7 +74,7 @@ module Aurora
|
|
70
74
|
return unless value >= 40 && value <= 90
|
71
75
|
|
72
76
|
raw_value = (value * 10).to_i
|
73
|
-
|
77
|
+
holding_registers[21_203 + (zone_number - 1) * 9] = raw_value
|
74
78
|
@heating_target_temperature = value
|
75
79
|
end
|
76
80
|
|
@@ -78,8 +82,8 @@ module Aurora
|
|
78
82
|
return unless value >= 54 && value <= 99
|
79
83
|
|
80
84
|
raw_value = (value * 10).to_i
|
81
|
-
|
82
|
-
@cooling_target_temperature =
|
85
|
+
holding_registers[21_204 + (zone_number - 1) * 9] = raw_value
|
86
|
+
@cooling_target_temperature = value
|
83
87
|
end
|
84
88
|
end
|
85
89
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mqtt"
|
4
|
+
require "securerandom"
|
5
|
+
|
6
|
+
module Aurora
|
7
|
+
class MQTTModBus
|
8
|
+
attr_accessor :logger
|
9
|
+
|
10
|
+
def initialize(uri)
|
11
|
+
@mqtt = MQTT::Client.new(uri)
|
12
|
+
@mqtt.connect
|
13
|
+
|
14
|
+
@base_topic = uri.path[1..]
|
15
|
+
@mqtt.subscribe("#{@base_topic}/getregs/response")
|
16
|
+
end
|
17
|
+
|
18
|
+
def read_multiple_holding_registers(*queries)
|
19
|
+
query_id = SecureRandom.uuid
|
20
|
+
mqtt_query = queries.map { |m| m.is_a?(Range) ? "#{m.begin},#{m.count}" : m }.join(";")
|
21
|
+
@mqtt.publish("#{@base_topic}/getregs", "#{query_id}:#{mqtt_query}", qos: 1)
|
22
|
+
Timeout.timeout(15) do
|
23
|
+
result = {}
|
24
|
+
loop do
|
25
|
+
packet = @mqtt.get
|
26
|
+
response_id, registers_flat = packet.payload.split(":")
|
27
|
+
next unless response_id == query_id
|
28
|
+
|
29
|
+
values = registers_flat.split(",").map(&:to_i)
|
30
|
+
result_index = 0
|
31
|
+
queries.each do |query|
|
32
|
+
Array(query).each do |i|
|
33
|
+
result[i] = values[result_index]
|
34
|
+
result_index += 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
break
|
38
|
+
end
|
39
|
+
result
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def write_holding_register(addr, value)
|
44
|
+
@mqtt.publish("#{@base_topic}/#{addr}/set", value, qos: 1)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/aurora/registers.rb
CHANGED
@@ -420,6 +420,15 @@ module Aurora
|
|
420
420
|
end
|
421
421
|
end
|
422
422
|
|
423
|
+
def thermostat_configuration2(value)
|
424
|
+
result = {
|
425
|
+
mode: HEATING_MODE[(value >> 8) & 0x07]
|
426
|
+
}
|
427
|
+
leftover = value & ~0x0700
|
428
|
+
result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
|
429
|
+
result
|
430
|
+
end
|
431
|
+
|
423
432
|
def zone_configuration1(value)
|
424
433
|
fan = if value & 0x80 == 0x80
|
425
434
|
:continuous
|
@@ -433,7 +442,7 @@ module Aurora
|
|
433
442
|
on_time: ((value >> 9) & 0x7) * 5,
|
434
443
|
off_time: (((value >> 12) & 0x7) + 1) * 5,
|
435
444
|
cooling_target_temperature: ((value & 0x7e) >> 1) + 36,
|
436
|
-
heating_target_temperature_carry: value &
|
445
|
+
heating_target_temperature_carry: value & 0x01
|
437
446
|
}
|
438
447
|
leftover = value & ~0x7fff
|
439
448
|
result[:unknown] = format("0x%04x", leftover) unless leftover.zero?
|
@@ -476,7 +485,7 @@ module Aurora
|
|
476
485
|
REGISTER_CONVERTERS = {
|
477
486
|
TO_HUNDREDTHS => [2, 3, 417, 418, 801, 804, 807, 813, 816, 817, 819, 820, 825, 828],
|
478
487
|
method(:dipswitch_settings) => [4, 33],
|
479
|
-
TO_TENTHS => [19, 20, 401, 419, 501, 502, 567, 740, 745, 746, 747, 900,
|
488
|
+
TO_TENTHS => [19, 20, 401, 419, 501, 502, 567, 740, 745, 746, 747, 900, 901, 903,
|
480
489
|
1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1119, 1124, 1125, 1134,
|
481
490
|
3322, 3323, 3325, 3326, 3327, 3330, 3522, 3903, 3905, 3906,
|
482
491
|
12_619, 12_620,
|
@@ -503,7 +512,7 @@ module Aurora
|
|
503
512
|
->(v) { from_bitmask(v, VS_ALARM2) } => [218, 3227],
|
504
513
|
->(v) { from_bitmask(v, VS_EEV2) } => [280, 3804],
|
505
514
|
method(:vs_manual_control) => [323],
|
506
|
-
NEGATABLE => [346
|
515
|
+
NEGATABLE => [346],
|
507
516
|
->(v) { BRINE_TYPE[v] } => [402],
|
508
517
|
->(v) { FLOW_METER_TYPE[v] } => [403],
|
509
518
|
->(v) { BLOWER_TYPE[v] } => [404],
|
@@ -514,6 +523,7 @@ module Aurora
|
|
514
523
|
->(v) { PUMP_TYPE[v] } => [413],
|
515
524
|
->(v) { PHASE_TYPE[v] } => [416],
|
516
525
|
method(:iz2_fan_desired) => [565],
|
526
|
+
->(v) { v == 0xffff ? 0 : v } => 601..699,
|
517
527
|
->(registers, idx) { to_string(registers, idx, 8) } => [710],
|
518
528
|
->(v) { COMPONENT_STATUS[v] } => [800, 803, 806, 812, 815, 818, 824, 827],
|
519
529
|
method(:axb_inputs) => [1103],
|
@@ -521,12 +531,13 @@ module Aurora
|
|
521
531
|
->(v) { TO_TENTHS.call(NEGATABLE.call(v)) } => [1135, 1136],
|
522
532
|
method(:to_int32) => [1146, 1148, 1150, 1152, 1154, 1156, 1164, 3422, 3424],
|
523
533
|
method(:manual_operation) => [3002],
|
534
|
+
method(:thermostat_configuration2) => [12_006],
|
524
535
|
->(v) { HEATING_MODE[v] } => [12_606, 21_202, 21_211, 21_220, 21_229, 21_238, 21_247],
|
525
536
|
->(v) { FAN_MODE[v] } => [12_621, 21_205, 21_214, 21_223, 21_232, 21_241, 21_250],
|
526
537
|
->(v) { from_bitmask(v, HUMIDIFIER_SETTINGS) } => [31_109],
|
527
538
|
->(v) { { humidification_target: v >> 8, dehumidification_target: v & 0xff } } => [31_110],
|
528
539
|
method(:iz2_demand) => [31_005],
|
529
|
-
method(:zone_configuration1) => [31_008, 31_011, 31_014, 31_017, 31_020, 31_023],
|
540
|
+
method(:zone_configuration1) => [12_005, 31_008, 31_011, 31_014, 31_017, 31_020, 31_023],
|
530
541
|
method(:zone_configuration2) => [31_009, 31_012, 31_015, 31_018, 31_021, 31_024],
|
531
542
|
method(:zone_configuration3) => [31_200, 31_203, 31_206, 31_209, 31_212, 31_215],
|
532
543
|
->(registers, idx) { to_string(registers, idx, 13) } => [31_400],
|
@@ -540,7 +551,7 @@ module Aurora
|
|
540
551
|
REGISTER_FORMATS = {
|
541
552
|
"%ds" => [1, 6, 9, 15, 84, 85, 110],
|
542
553
|
"%dV" => [16, 112, 3331, 3424, 3523],
|
543
|
-
"%0.1fºF" => [19, 20, 401, 501, 502, 567, 740, 745, 746, 747, 900,
|
554
|
+
"%0.1fºF" => [19, 20, 401, 501, 502, 567, 740, 745, 746, 747, 900, 903,
|
544
555
|
1109, 1110, 1111, 1112, 1113, 1114, 1124, 1125, 1134, 1135, 1136,
|
545
556
|
3325, 3326, 3327, 3330, 3522, 3903, 3905, 3906,
|
546
557
|
12_619, 12_620,
|
@@ -553,8 +564,8 @@ module Aurora
|
|
553
564
|
31_003,
|
554
565
|
31_007, 31_010, 31_013, 31_016, 31_019, 31_022],
|
555
566
|
"E%d" => [25, 26],
|
556
|
-
"%d%%" => [282, 321, 322, 325, 346, 565, 741, 1126, 3332, 3524, 3808],
|
557
|
-
"%0.1f psi" => [419, 1115, 1116, 1119, 3322, 3323],
|
567
|
+
"%d%%" => [282, 321, 322, 325, 346, 565, 741, 908, 1126, 3332, 3524, 3808],
|
568
|
+
"%0.1f psi" => [419, 901, 1115, 1116, 1119, 3322, 3323],
|
558
569
|
"%0.1fA" => [1105, 1106, 1107, 1108],
|
559
570
|
"%0.1fgpm" => [1117],
|
560
571
|
"%dW" => [1146, 1148, 1150, 1152, 1164, 3422],
|
@@ -812,6 +823,10 @@ module Aurora
|
|
812
823
|
828 => "AWL Version",
|
813
824
|
829 => "AWL Revision",
|
814
825
|
900 => "Leaving Air",
|
826
|
+
901 => "Suction Pressure",
|
827
|
+
903 => "SuperHeat Temperature",
|
828
|
+
908 => "EEV Open %",
|
829
|
+
909 => "SubCooling (Cooling)",
|
815
830
|
1103 => "AXB Inputs",
|
816
831
|
1104 => "AXB Outputs",
|
817
832
|
1105 => "Blower Amps",
|
@@ -841,10 +856,6 @@ module Aurora
|
|
841
856
|
1154 => "Heat of Extraction",
|
842
857
|
1156 => "Heat of Rejection",
|
843
858
|
1164 => "Pump Watts",
|
844
|
-
12_606 => "Heating Mode (write)",
|
845
|
-
12_619 => "Heating Setpoint (write)",
|
846
|
-
12_620 => "Cooling Setpoint (write)",
|
847
|
-
12_621 => "Fan Mode (write)",
|
848
859
|
3000 => "Compressor Speed Desired",
|
849
860
|
3001 => "Compressor Speed Actual",
|
850
861
|
3002 => "Manual Operation",
|
@@ -876,6 +887,14 @@ module Aurora
|
|
876
887
|
3904 => "VS Drive Leaving Air Temperature?",
|
877
888
|
3905 => "VS Drive Saturated Evaporator Discharge Temperature",
|
878
889
|
3906 => "VS Drive SuperHeat Temperature",
|
890
|
+
12_005 => "Fan Configuration",
|
891
|
+
12_006 => "Heating Mode",
|
892
|
+
12_606 => "Heating Mode (write)",
|
893
|
+
12_619 => "Heating Setpoint (write)",
|
894
|
+
12_620 => "Cooling Setpoint (write)",
|
895
|
+
12_621 => "Fan Mode (write)",
|
896
|
+
12_622 => "Intermittent Fan On Time (write)",
|
897
|
+
12_623 => "Intermittent Fan Off Time (write)",
|
879
898
|
31_003 => "Outdoor Temp",
|
880
899
|
31_005 => "IZ2 Demand",
|
881
900
|
31_109 => "Humidifier Mode", # write to 21114
|
data/lib/aurora/thermostat.rb
CHANGED
@@ -5,15 +5,39 @@ require "aurora/component"
|
|
5
5
|
module Aurora
|
6
6
|
class Thermostat < Component
|
7
7
|
attr_reader :target_mode,
|
8
|
+
:current_mode,
|
8
9
|
:target_fan_mode,
|
10
|
+
:current_fan_mode,
|
9
11
|
:ambient_temperature,
|
10
12
|
:cooling_target_temperature,
|
11
|
-
:heating_target_temperature
|
13
|
+
:heating_target_temperature,
|
14
|
+
:fan_intermittent_on,
|
15
|
+
:fan_intermittent_off
|
16
|
+
|
17
|
+
def registers_to_read
|
18
|
+
[31, 502, 745..746, 12_005..12_006]
|
19
|
+
end
|
12
20
|
|
13
21
|
def refresh(registers)
|
14
22
|
@ambient_temperature = registers[502]
|
15
23
|
@heating_target_temperature = registers[745]
|
16
24
|
@cooling_target_temperature = registers[746]
|
25
|
+
config1 = registers[12_005]
|
26
|
+
config2 = registers[12_006]
|
27
|
+
@target_fan_mode = config1[:fan]
|
28
|
+
@fan_intermittent_on = config1[:on_time]
|
29
|
+
@fan_intermittent_off = config1[:off_time]
|
30
|
+
@target_mode = config2[:mode]
|
31
|
+
|
32
|
+
inputs = registers[31]
|
33
|
+
@current_fan_mode = inputs.include?(:g)
|
34
|
+
@current_mode = if inputs[:y2]
|
35
|
+
inputs[:o] ? :c2 : :h2
|
36
|
+
elsif inputs[:y1]
|
37
|
+
inputs[:o] ? :c1 : :h1
|
38
|
+
else
|
39
|
+
:standby
|
40
|
+
end
|
17
41
|
end
|
18
42
|
|
19
43
|
def target_mode=(value)
|
@@ -46,10 +70,18 @@ module Aurora
|
|
46
70
|
@cooling_target_temperature = value
|
47
71
|
end
|
48
72
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
73
|
+
def fan_intermittent_on=(value)
|
74
|
+
return unless value >= 0 && value <= 25 && (value % 5).zero?
|
75
|
+
|
76
|
+
holding_registers[12_622] = value
|
77
|
+
@fan_intermittent_on = value
|
78
|
+
end
|
79
|
+
|
80
|
+
def fan_intermittent_off=(value)
|
81
|
+
return unless value >= 0 && value <= 40 && (value % 5).zero?
|
82
|
+
|
83
|
+
holding_registers[12_623] = value
|
84
|
+
@fan_intermittent_off = value
|
53
85
|
end
|
54
86
|
end
|
55
87
|
end
|
data/lib/aurora/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: waterfurnace_aurora
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cody Cutrer
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ccutrer-serialport
|
@@ -136,11 +136,14 @@ files:
|
|
136
136
|
- lib/aurora/abc_client.rb
|
137
137
|
- lib/aurora/blower.rb
|
138
138
|
- lib/aurora/component.rb
|
139
|
+
- lib/aurora/compressor.rb
|
139
140
|
- lib/aurora/core_ext/string.rb
|
141
|
+
- lib/aurora/dhw.rb
|
140
142
|
- lib/aurora/iz2_zone.rb
|
141
143
|
- lib/aurora/mock_abc.rb
|
142
144
|
- lib/aurora/modbus/server.rb
|
143
145
|
- lib/aurora/modbus/slave.rb
|
146
|
+
- lib/aurora/mqtt_modbus.rb
|
144
147
|
- lib/aurora/pump.rb
|
145
148
|
- lib/aurora/registers.rb
|
146
149
|
- lib/aurora/thermostat.rb
|